Compare commits

...
Author SHA1 Message Date
William Fu-Hinthorn 2f47d98b34 Update error message for missing dev command 2025-01-14 11:28:51 -08:00
Vadym BardaandGitHub b989502c24 checkpoint-sqlite/postgres: handle calling .list on async checkpointer (#3019) 2025-01-14 19:06:11 +00:00
Eugene YurtsevandGitHub 8507dc33f0 FIx docs: Update MULTIPLE_SUBGRAPHS.md (#3016) 2025-01-14 13:41:50 -05:00
Vadym BardaandGitHub a11ba2b38a docs: update replay in the concept docs (#3017) 2025-01-14 11:53:53 -05:00
Cesar William AlvarengaandGitHub a61ea101f6 fix: add missing END constant import (#3011) 2025-01-13 19:40:40 -05:00
Andrew NguonlyandGitHub c86155d3d3 docs: Add note about LangGraph Platform UI not available for self-hosted deployments (#3009)
Example screenshot:

![image](https://github.com/user-attachments/assets/8304b08d-bc9b-4cd3-8ff4-ed2471d3d6c7)
2025-01-13 13:05:18 -08:00
William FHandGitHub a03f1f7469 Bullseye (#3008) 2025-01-13 10:01:36 -08:00
Jimmy SambuoandGitHub d7199e5874 docs: use InjectedStore in semantic search guide (#2995)
When I tried to follow the How-to guide for [How to add semantic search
to your agent's
memory](https://langchain-ai.github.io/langgraph/how-tos/memory/semantic-search/#using-in-create_react_agent)
using `create_react_agent`, I got this error message when my agent used
the tool:

```python
1 validation error for upsert_memory
store
  Field required [type=missing, input_value={'content': '@jimmy works...ny.', 'memory_id': None}, input_type=dict]
    For further information visit https://errors.pydantic.dev/2.10/v/missingTraceback (most recent call last):

  File "/usr/local/lib/python3.9/site-packages/langchain_core/tools/base.py", line 688, in run
    tool_args, tool_kwargs = self._to_args_and_kwargs(tool_input, tool_call_id)

  File "/usr/local/lib/python3.9/site-packages/langchain_core/tools/base.py", line 611, in _to_args_and_kwargs
    tool_input = self._parse_input(tool_input, tool_call_id)

  File "/usr/local/lib/python3.9/site-packages/langchain_core/tools/base.py", line 532, in _parse_input
    result = input_args.model_validate(tool_input)

  File "/usr/local/lib/python3.9/site-packages/pydantic/main.py", line 627, in model_validate
    return cls.__pydantic_validator__.validate_python(

pydantic_core._pydantic_core.ValidationError: 1 validation error for upsert_memory
store
  Field required [type=missing, input_value={'content': '@jimmy works...ny.', 'memory_id': None}, input_type=dict]
    For further information visit https://errors.pydantic.dev/2.10/v/missing
```

I believe it’s because the graph did not inject the store into the tool
if we use `InjectedToolArg`.

When looking at the guide for [How to pass runtime values to
tools](https://langchain-ai.github.io/langgraph/how-tos/pass-run-time-values-to-tools/),
it suggests to use `InjectedStore` with `create_react_agent`. After
changing my code to use `InjectedStore`, my agent was able to save to
the store.
2025-01-13 09:59:46 -05:00
Siddhesh dosiandGitHub 61f2151df7 Update customer-support.ipynb minor spell (#3002)
There was spelling mistake.
2025-01-13 09:52:19 -05:00
William FHandGitHub 713528ffc3 Add admonition regarding dockerfile usage (#3001) 2025-01-12 09:52:13 -08:00
Andrew NguonlyandGitHub 7bd79c2509 docs: Add docs for LANGCHAIN_ENDPOINT for self-hosted deployments (#2988)
@langchain-infra, is this correct? Is this needed or is it
redundant/unnecessary?
2025-01-10 15:24:38 -08:00
Andrew NguonlyandGitHub 9974787df6 docs: Add API docs for POST /v1/projects/{project_id}/revisions/{revision_id}/deploy endpoint (#2994) 2025-01-10 15:16:48 -08:00
William FHandGitHub 638712a73b Add support for custom fetch implementation (#2993) 2025-01-10 23:08:33 +00:00
Vadym BardaandGitHub b8a54f6294 langgraph: release 0.2.62 (#2990) 2025-01-10 14:33:54 -05:00
Brace SproulandGitHub f2913fbcb6 fix(sdk-js): Release 0.0.35 (#2989) 2025-01-10 10:52:32 -08:00
bracesproul 8045e89e09 fix(sdk-js): Release 0.0.35 2025-01-10 10:43:44 -08:00
Brace SproulandGitHub 8355a1720a fix: Cron response types (#2987)
technically a breaking change, however the old response type was
incorrect.
2025-01-10 10:15:03 -08:00
bracesproul c302724394 fix cron create for thread return type 2025-01-10 10:05:57 -08:00
bracesproul c624ff69e1 fix: Cron response types 2025-01-10 10:01:51 -08:00
Vadym BardaandGitHub 10d46acc60 langgraph: add structured output to create_react_agent (#2848)
```python
class WeatherResponse(BaseModel):
    """Respond to the user with this"""

    temperature: float = Field(description="The temperature in fahrenheit")
    wind_direction: str = Field(
        description="The direction of the wind in abbreviated form"
    )
    wind_speed: float = Field(description="The speed of the wind in mph")

@tool
def get_weather(city: Literal["nyc", "sf"]):
    """Use this to get weather information."""
    if city == "nyc":
        return "It is cloudy in NYC, with 5 mph winds in the North-East direction and a temperature of 70 degrees"
    elif city == "sf":
        return "It is 75 degrees and sunny in SF, with 3 mph winds in the South-East direction"
    else:
        raise AssertionError("Unknown city")

model = ChatOpenAI()
tools = [get_weather]
agent_with_structured_output = create_react_agent(model, tools, response_format=WeatherResponse)
agent_with_structured_output.invoke({"messages": [("user", "what's the weather in nyc?")]})
```

```pycon
{
    'messages': [...],
    'structured_response': WeatherResponse(temperature=70.0, wind_directon='NE', wind_speed=5.0)
}
```
2025-01-10 16:06:59 +00:00
Vadym BardaandGitHub 35c3ba0104 docs: update how to for passing config to tools (#2986) 2025-01-10 11:05:15 -05:00
Hongbin MaoandGitHub 0e2cd9e289 Fix typo (#2984) 2025-01-10 10:52:52 -05:00
William FHandGitHub f4bd02da72 Make admonition more admonitiony (#2982) 2025-01-10 01:46:42 +00:00
William FHandGitHub ecfbfa1b90 Update auth docstrings (#2977) 2025-01-09 16:58:19 -08:00
William FHandGitHub e5b5f9510b Fix empty migration (#2978) 2025-01-09 23:14:02 +00:00
Andrew NguonlyandGitHub c6d7c80a99 docs: Add documentation for LANGSMITH_RUNS_ENDPOINTS env var (#2976) 2025-01-09 13:24:41 -08:00
William FHandGitHub 909190cede Update SDK registration (#2974) 2025-01-09 09:20:54 -08:00
William FHandGitHub 43c8578eef Unify type-naming of search param (#2973) 2025-01-09 08:56:05 -08:00
William Fu-Hinthorn 56f5edb9ba Unify naming of search param 2025-01-09 08:46:17 -08:00
William FHandGitHub 41f0fd504e Add store auth types (#2971) 2025-01-09 08:32:54 -08:00
William FHandGitHub 6357d496af Merge branch 'main' into wfh/auth/store 2025-01-09 08:08:04 -08:00
William Fu-Hinthorn 52bd5b13a7 Add store auth types 2025-01-09 08:03:30 -08:00
Andrew NguonlyandGitHub b633e0a4ed docs: Add docs for POSTGRES_URI_CUSTOM environment variable (#2951) 2025-01-08 15:27:32 -08:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
c14bcb6e9f build(deps-dev): bump jinja2 from 3.1.4 to 3.1.5 in /libs/langgraph (#2960)
Bumps [jinja2](https://github.com/pallets/jinja) from 3.1.4 to 3.1.5.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/pallets/jinja/releases">jinja2's
releases</a>.</em></p>
<blockquote>
<h2>3.1.5</h2>
<p>This is the Jinja 3.1.5 security fix release, which fixes security
issues and bugs but does not otherwise change behavior and should not
result in breaking changes compared to the latest feature release.</p>
<p>PyPI: <a
href="https://pypi.org/project/Jinja2/3.1.5/">https://pypi.org/project/Jinja2/3.1.5/</a>
Changes: <a
href="https://jinja.palletsprojects.com/changes/#version-3-1-5">https://jinja.palletsprojects.com/changes/#version-3-1-5</a>
Milestone: <a
href="https://github.com/pallets/jinja/milestone/16?closed=1">https://github.com/pallets/jinja/milestone/16?closed=1</a></p>
<ul>
<li>The sandboxed environment handles indirect calls to
<code>str.format</code>, such as by passing a stored reference to a
filter that calls its argument. <a
href="https://github.com/pallets/jinja/security/advisories/GHSA-q2x7-8rv6-6q7h">GHSA-q2x7-8rv6-6q7h</a></li>
<li>Escape template name before formatting it into error messages, to
avoid issues with names that contain f-string syntax. <a
href="https://redirect.github.com/pallets/jinja/issues/1792">#1792</a>,
<a
href="https://github.com/pallets/jinja/security/advisories/GHSA-gmj6-6f8f-6699">GHSA-gmj6-6f8f-6699</a></li>
<li>Sandbox does not allow <code>clear</code> and <code>pop</code> on
known mutable sequence types. <a
href="https://redirect.github.com/pallets/jinja/issues/2032">#2032</a></li>
<li>Calling sync <code>render</code> for an async template uses
<code>asyncio.run</code>. <a
href="https://redirect.github.com/pallets/jinja/issues/1952">#1952</a></li>
<li>Avoid unclosed <code>auto_aiter</code> warnings. <a
href="https://redirect.github.com/pallets/jinja/issues/1960">#1960</a></li>
<li>Return an <code>aclose</code>-able <code>AsyncGenerator</code> from
<code>Template.generate_async</code>. <a
href="https://redirect.github.com/pallets/jinja/issues/1960">#1960</a></li>
<li>Avoid leaving <code>root_render_func()</code> unclosed in
<code>Template.generate_async</code>. <a
href="https://redirect.github.com/pallets/jinja/issues/1960">#1960</a></li>
<li>Avoid leaving async generators unclosed in blocks, includes and
extends. <a
href="https://redirect.github.com/pallets/jinja/issues/1960">#1960</a></li>
<li>The runtime uses the correct <code>concat</code> function for the
current environment when calling block references. <a
href="https://redirect.github.com/pallets/jinja/issues/1701">#1701</a></li>
<li>Make <code>|unique</code> async-aware, allowing it to be used after
another async-aware filter. <a
href="https://redirect.github.com/pallets/jinja/issues/1781">#1781</a></li>
<li><code>|int</code> filter handles <code>OverflowError</code> from
scientific notation. <a
href="https://redirect.github.com/pallets/jinja/issues/1921">#1921</a></li>
<li>Make compiling deterministic for tuple unpacking in a <code>{% set
... %}</code> call. <a
href="https://redirect.github.com/pallets/jinja/issues/2021">#2021</a></li>
<li>Fix dunder protocol (<code>copy</code>/<code>pickle</code>/etc)
interaction with <code>Undefined</code> objects. <a
href="https://redirect.github.com/pallets/jinja/issues/2025">#2025</a></li>
<li>Fix <code>copy</code>/<code>pickle</code> support for the internal
<code>missing</code> object. <a
href="https://redirect.github.com/pallets/jinja/issues/2027">#2027</a></li>
<li><code>Environment.overlay(enable_async)</code> is applied correctly.
<a
href="https://redirect.github.com/pallets/jinja/issues/2061">#2061</a></li>
<li>The error message from <code>FileSystemLoader</code> includes the
paths that were searched. <a
href="https://redirect.github.com/pallets/jinja/issues/1661">#1661</a></li>
<li><code>PackageLoader</code> shows a clearer error message when the
package does not contain the templates directory. <a
href="https://redirect.github.com/pallets/jinja/issues/1705">#1705</a></li>
<li>Improve annotations for methods returning copies. <a
href="https://redirect.github.com/pallets/jinja/issues/1880">#1880</a></li>
<li><code>urlize</code> does not add <code>mailto:</code> to values like
<code>@a@b</code>. <a
href="https://redirect.github.com/pallets/jinja/issues/1870">#1870</a></li>
<li>Tests decorated with <code>@pass_context</code> can be used with the
<code>|select</code> filter. <a
href="https://redirect.github.com/pallets/jinja/issues/1624">#1624</a></li>
<li>Using <code>set</code> for multiple assignment (<code>a, b = 1,
2</code>) does not fail when the target is a namespace attribute. <a
href="https://redirect.github.com/pallets/jinja/issues/1413">#1413</a></li>
<li>Using <code>set</code> in all branches of <code>{% if %}{% elif %}{%
else %}</code> blocks does not cause the variable to be considered
initially undefined. <a
href="https://redirect.github.com/pallets/jinja/issues/1253">#1253</a></li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/pallets/jinja/blob/main/CHANGES.rst">jinja2's
changelog</a>.</em></p>
<blockquote>
<h2>Version 3.1.5</h2>
<p>Released 2024-12-21</p>
<ul>
<li>The sandboxed environment handles indirect calls to
<code>str.format</code>, such as
by passing a stored reference to a filter that calls its argument.
:ghsa:<code>q2x7-8rv6-6q7h</code></li>
<li>Escape template name before formatting it into error messages, to
avoid
issues with names that contain f-string syntax.
:issue:<code>1792</code>, :ghsa:<code>gmj6-6f8f-6699</code></li>
<li>Sandbox does not allow <code>clear</code> and <code>pop</code> on
known mutable sequence
types. :issue:<code>2032</code></li>
<li>Calling sync <code>render</code> for an async template uses
<code>asyncio.run</code>.
:pr:<code>1952</code></li>
<li>Avoid unclosed <code>auto_aiter</code> warnings.
:pr:<code>1960</code></li>
<li>Return an <code>aclose</code>-able <code>AsyncGenerator</code> from
<code>Template.generate_async</code>. :pr:<code>1960</code></li>
<li>Avoid leaving <code>root_render_func()</code> unclosed in
<code>Template.generate_async</code>. :pr:<code>1960</code></li>
<li>Avoid leaving async generators unclosed in blocks, includes and
extends.
:pr:<code>1960</code></li>
<li>The runtime uses the correct <code>concat</code> function for the
current environment
when calling block references. :issue:<code>1701</code></li>
<li>Make <code>|unique</code> async-aware, allowing it to be used after
another
async-aware filter. :issue:<code>1781</code></li>
<li><code>|int</code> filter handles <code>OverflowError</code> from
scientific notation.
:issue:<code>1921</code></li>
<li>Make compiling deterministic for tuple unpacking in a <code>{% set
... %}</code>
call. :issue:<code>2021</code></li>
<li>Fix dunder protocol (<code>copy</code>/<code>pickle</code>/etc)
interaction with <code>Undefined</code>
objects. :issue:<code>2025</code></li>
<li>Fix <code>copy</code>/<code>pickle</code> support for the internal
<code>missing</code> object.
:issue:<code>2027</code></li>
<li><code>Environment.overlay(enable_async)</code> is applied correctly.
:pr:<code>2061</code></li>
<li>The error message from <code>FileSystemLoader</code> includes the
paths that were
searched. :issue:<code>1661</code></li>
<li><code>PackageLoader</code> shows a clearer error message when the
package does not
contain the templates directory. :issue:<code>1705</code></li>
<li>Improve annotations for methods returning copies.
:pr:<code>1880</code></li>
<li><code>urlize</code> does not add <code>mailto:</code> to values like
<code>@a@b</code>. :pr:<code>1870</code></li>
<li>Tests decorated with <code>@pass_context`` can be used with the
``|select`` filter. :issue:</code>1624`</li>
<li>Using <code>set</code> for multiple assignment (<code>a, b = 1,
2</code>) does not fail when the
target is a namespace attribute. :issue:<code>1413</code></li>
<li>Using <code>set</code> in all branches of <code>{% if %}{% elif %}{%
else %}</code> blocks
does not cause the variable to be considered initially undefined.
:issue:<code>1253</code></li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/pallets/jinja/commit/877f6e51be8e1765b06d911cfaa9033775f051d1"><code>877f6e5</code></a>
release version 3.1.5</li>
<li><a
href="https://github.com/pallets/jinja/commit/8d588592653b052f957b720e1fc93196e06f207f"><code>8d58859</code></a>
remove test pypi</li>
<li><a
href="https://github.com/pallets/jinja/commit/eda8fe86fd716dfce24910294e9f1fc81fbc740c"><code>eda8fe8</code></a>
update dev dependencies</li>
<li><a
href="https://github.com/pallets/jinja/commit/c8fdce1e0333f1122b244b03a48535fdd7b03d91"><code>c8fdce1</code></a>
Fix bug involving calling set on a template parameter within all
branches of ...</li>
<li><a
href="https://github.com/pallets/jinja/commit/66587ce989e5a478e0bb165371fa2b9d42b7040f"><code>66587ce</code></a>
Fix bug where set would sometimes fail within if</li>
<li><a
href="https://github.com/pallets/jinja/commit/fbc3a696c729d177340cc089531de7e2e5b6f065"><code>fbc3a69</code></a>
Add support for namespaces in tuple parsing (<a
href="https://redirect.github.com/pallets/jinja/issues/1664">#1664</a>)</li>
<li><a
href="https://github.com/pallets/jinja/commit/b8f4831d41e6a7cb5c40d42f074ffd92d2daccfc"><code>b8f4831</code></a>
more comments about nsref assignment</li>
<li><a
href="https://github.com/pallets/jinja/commit/ee832194cd9f55f75e5a51359b709d535efe957f"><code>ee83219</code></a>
Add support for namespaces in tuple assignment</li>
<li><a
href="https://github.com/pallets/jinja/commit/1d55cddbb28e433779511f28f13a2d8c4ec45826"><code>1d55cdd</code></a>
Triple quotes in docs (<a
href="https://redirect.github.com/pallets/jinja/issues/2064">#2064</a>)</li>
<li><a
href="https://github.com/pallets/jinja/commit/8a8eafc6b992ba177f1d3dd483f8465f18a11116"><code>8a8eafc</code></a>
edit block assignment section</li>
<li>Additional commits viewable in <a
href="https://github.com/pallets/jinja/compare/3.1.4...3.1.5">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=jinja2&package-manager=pip&previous-version=3.1.4&new-version=3.1.5)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/langchain-ai/langgraph/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-08 17:31:13 -05:00
Vadym BardaandGitHub 8b29dc81e0 sdk-js: release 0.0.34 (#2959) 2025-01-08 10:59:40 -05:00
Vadym BardaandGitHub 1546eddfbe sdk-js: add stream mode and stream subgraphs to runs.create (#2958) 2025-01-08 10:58:07 -05:00
Vadym BardaandGitHub 9680e35beb docs: update tool in customer support tutorial (#2956)
Fixes #2952
2025-01-08 09:33:25 -05:00
Andrew NguonlyandGitHub 837f215857 docs: Rename Deploy Logs to Server Logs (#2922) 2025-01-07 17:13:06 -08:00
Vadym BardaandGitHub e13261ac0a ci: update langsmith and patch urllib3 vcr issues for notebook runner (#2949) 2025-01-07 20:09:57 -05:00
8ab206043c langgraph[patch]: fix create_react_agent inspectability (#2948)
Co-authored-by: vbarda <vadym@langchain.dev>
2025-01-07 18:47:31 +00:00
BagaturandGitHub 3dbe37041a docs: readme nit (#2899)
When looking at [docs](https://langchain-ai.github.io/langgraph/) this
sentence is confusing, not clear there's two separate links or why one
of them would lead to repo
2025-01-07 11:00:14 -05:00
e00284b386 Clarifying the docstring for the add_edge function (#2782)
Revised docstring for StateGraph's add_edge method to clarify recurring
confusion #2775 #1462

---------

Co-authored-by: ashirgaokar <abhishek.shirgaokar@zee.com>
Co-authored-by: vbarda <vadym@langchain.dev>
2025-01-07 10:04:50 -05:00
merdanandGitHub a36d2ac77d Update langgraph_agentic_rag.ipynb (#2885)
add missing package
2025-01-07 09:51:29 -05:00
tomo-abeandGitHub 2a46534286 Fix typo introduction.ipynb (#2894)
Fixing typo
2025-01-07 09:47:23 -05:00
VItto RivabellaandGitHub 4b06791b8c Update agent_supervisor.ipynb (#2888) 2025-01-07 09:45:41 -05:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
661e20eec4 build(deps-dev): bump jinja2 from 3.1.4 to 3.1.5 (#2914)
Bumps [jinja2](https://github.com/pallets/jinja) from 3.1.4 to 3.1.5.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/pallets/jinja/releases">jinja2's
releases</a>.</em></p>
<blockquote>
<h2>3.1.5</h2>
<p>This is the Jinja 3.1.5 security fix release, which fixes security
issues and bugs but does not otherwise change behavior and should not
result in breaking changes compared to the latest feature release.</p>
<p>PyPI: <a
href="https://pypi.org/project/Jinja2/3.1.5/">https://pypi.org/project/Jinja2/3.1.5/</a>
Changes: <a
href="https://jinja.palletsprojects.com/changes/#version-3-1-5">https://jinja.palletsprojects.com/changes/#version-3-1-5</a>
Milestone: <a
href="https://github.com/pallets/jinja/milestone/16?closed=1">https://github.com/pallets/jinja/milestone/16?closed=1</a></p>
<ul>
<li>The sandboxed environment handles indirect calls to
<code>str.format</code>, such as by passing a stored reference to a
filter that calls its argument. <a
href="https://github.com/pallets/jinja/security/advisories/GHSA-q2x7-8rv6-6q7h">GHSA-q2x7-8rv6-6q7h</a></li>
<li>Escape template name before formatting it into error messages, to
avoid issues with names that contain f-string syntax. <a
href="https://redirect.github.com/pallets/jinja/issues/1792">#1792</a>,
<a
href="https://github.com/pallets/jinja/security/advisories/GHSA-gmj6-6f8f-6699">GHSA-gmj6-6f8f-6699</a></li>
<li>Sandbox does not allow <code>clear</code> and <code>pop</code> on
known mutable sequence types. <a
href="https://redirect.github.com/pallets/jinja/issues/2032">#2032</a></li>
<li>Calling sync <code>render</code> for an async template uses
<code>asyncio.run</code>. <a
href="https://redirect.github.com/pallets/jinja/issues/1952">#1952</a></li>
<li>Avoid unclosed <code>auto_aiter</code> warnings. <a
href="https://redirect.github.com/pallets/jinja/issues/1960">#1960</a></li>
<li>Return an <code>aclose</code>-able <code>AsyncGenerator</code> from
<code>Template.generate_async</code>. <a
href="https://redirect.github.com/pallets/jinja/issues/1960">#1960</a></li>
<li>Avoid leaving <code>root_render_func()</code> unclosed in
<code>Template.generate_async</code>. <a
href="https://redirect.github.com/pallets/jinja/issues/1960">#1960</a></li>
<li>Avoid leaving async generators unclosed in blocks, includes and
extends. <a
href="https://redirect.github.com/pallets/jinja/issues/1960">#1960</a></li>
<li>The runtime uses the correct <code>concat</code> function for the
current environment when calling block references. <a
href="https://redirect.github.com/pallets/jinja/issues/1701">#1701</a></li>
<li>Make <code>|unique</code> async-aware, allowing it to be used after
another async-aware filter. <a
href="https://redirect.github.com/pallets/jinja/issues/1781">#1781</a></li>
<li><code>|int</code> filter handles <code>OverflowError</code> from
scientific notation. <a
href="https://redirect.github.com/pallets/jinja/issues/1921">#1921</a></li>
<li>Make compiling deterministic for tuple unpacking in a <code>{% set
... %}</code> call. <a
href="https://redirect.github.com/pallets/jinja/issues/2021">#2021</a></li>
<li>Fix dunder protocol (<code>copy</code>/<code>pickle</code>/etc)
interaction with <code>Undefined</code> objects. <a
href="https://redirect.github.com/pallets/jinja/issues/2025">#2025</a></li>
<li>Fix <code>copy</code>/<code>pickle</code> support for the internal
<code>missing</code> object. <a
href="https://redirect.github.com/pallets/jinja/issues/2027">#2027</a></li>
<li><code>Environment.overlay(enable_async)</code> is applied correctly.
<a
href="https://redirect.github.com/pallets/jinja/issues/2061">#2061</a></li>
<li>The error message from <code>FileSystemLoader</code> includes the
paths that were searched. <a
href="https://redirect.github.com/pallets/jinja/issues/1661">#1661</a></li>
<li><code>PackageLoader</code> shows a clearer error message when the
package does not contain the templates directory. <a
href="https://redirect.github.com/pallets/jinja/issues/1705">#1705</a></li>
<li>Improve annotations for methods returning copies. <a
href="https://redirect.github.com/pallets/jinja/issues/1880">#1880</a></li>
<li><code>urlize</code> does not add <code>mailto:</code> to values like
<code>@a@b</code>. <a
href="https://redirect.github.com/pallets/jinja/issues/1870">#1870</a></li>
<li>Tests decorated with <code>@pass_context</code> can be used with the
<code>|select</code> filter. <a
href="https://redirect.github.com/pallets/jinja/issues/1624">#1624</a></li>
<li>Using <code>set</code> for multiple assignment (<code>a, b = 1,
2</code>) does not fail when the target is a namespace attribute. <a
href="https://redirect.github.com/pallets/jinja/issues/1413">#1413</a></li>
<li>Using <code>set</code> in all branches of <code>{% if %}{% elif %}{%
else %}</code> blocks does not cause the variable to be considered
initially undefined. <a
href="https://redirect.github.com/pallets/jinja/issues/1253">#1253</a></li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/pallets/jinja/blob/main/CHANGES.rst">jinja2's
changelog</a>.</em></p>
<blockquote>
<h2>Version 3.1.5</h2>
<p>Released 2024-12-21</p>
<ul>
<li>The sandboxed environment handles indirect calls to
<code>str.format</code>, such as
by passing a stored reference to a filter that calls its argument.
:ghsa:<code>q2x7-8rv6-6q7h</code></li>
<li>Escape template name before formatting it into error messages, to
avoid
issues with names that contain f-string syntax.
:issue:<code>1792</code>, :ghsa:<code>gmj6-6f8f-6699</code></li>
<li>Sandbox does not allow <code>clear</code> and <code>pop</code> on
known mutable sequence
types. :issue:<code>2032</code></li>
<li>Calling sync <code>render</code> for an async template uses
<code>asyncio.run</code>.
:pr:<code>1952</code></li>
<li>Avoid unclosed <code>auto_aiter</code> warnings.
:pr:<code>1960</code></li>
<li>Return an <code>aclose</code>-able <code>AsyncGenerator</code> from
<code>Template.generate_async</code>. :pr:<code>1960</code></li>
<li>Avoid leaving <code>root_render_func()</code> unclosed in
<code>Template.generate_async</code>. :pr:<code>1960</code></li>
<li>Avoid leaving async generators unclosed in blocks, includes and
extends.
:pr:<code>1960</code></li>
<li>The runtime uses the correct <code>concat</code> function for the
current environment
when calling block references. :issue:<code>1701</code></li>
<li>Make <code>|unique</code> async-aware, allowing it to be used after
another
async-aware filter. :issue:<code>1781</code></li>
<li><code>|int</code> filter handles <code>OverflowError</code> from
scientific notation.
:issue:<code>1921</code></li>
<li>Make compiling deterministic for tuple unpacking in a <code>{% set
... %}</code>
call. :issue:<code>2021</code></li>
<li>Fix dunder protocol (<code>copy</code>/<code>pickle</code>/etc)
interaction with <code>Undefined</code>
objects. :issue:<code>2025</code></li>
<li>Fix <code>copy</code>/<code>pickle</code> support for the internal
<code>missing</code> object.
:issue:<code>2027</code></li>
<li><code>Environment.overlay(enable_async)</code> is applied correctly.
:pr:<code>2061</code></li>
<li>The error message from <code>FileSystemLoader</code> includes the
paths that were
searched. :issue:<code>1661</code></li>
<li><code>PackageLoader</code> shows a clearer error message when the
package does not
contain the templates directory. :issue:<code>1705</code></li>
<li>Improve annotations for methods returning copies.
:pr:<code>1880</code></li>
<li><code>urlize</code> does not add <code>mailto:</code> to values like
<code>@a@b</code>. :pr:<code>1870</code></li>
<li>Tests decorated with <code>@pass_context`` can be used with the
``|select`` filter. :issue:</code>1624`</li>
<li>Using <code>set</code> for multiple assignment (<code>a, b = 1,
2</code>) does not fail when the
target is a namespace attribute. :issue:<code>1413</code></li>
<li>Using <code>set</code> in all branches of <code>{% if %}{% elif %}{%
else %}</code> blocks
does not cause the variable to be considered initially undefined.
:issue:<code>1253</code></li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/pallets/jinja/commit/877f6e51be8e1765b06d911cfaa9033775f051d1"><code>877f6e5</code></a>
release version 3.1.5</li>
<li><a
href="https://github.com/pallets/jinja/commit/8d588592653b052f957b720e1fc93196e06f207f"><code>8d58859</code></a>
remove test pypi</li>
<li><a
href="https://github.com/pallets/jinja/commit/eda8fe86fd716dfce24910294e9f1fc81fbc740c"><code>eda8fe8</code></a>
update dev dependencies</li>
<li><a
href="https://github.com/pallets/jinja/commit/c8fdce1e0333f1122b244b03a48535fdd7b03d91"><code>c8fdce1</code></a>
Fix bug involving calling set on a template parameter within all
branches of ...</li>
<li><a
href="https://github.com/pallets/jinja/commit/66587ce989e5a478e0bb165371fa2b9d42b7040f"><code>66587ce</code></a>
Fix bug where set would sometimes fail within if</li>
<li><a
href="https://github.com/pallets/jinja/commit/fbc3a696c729d177340cc089531de7e2e5b6f065"><code>fbc3a69</code></a>
Add support for namespaces in tuple parsing (<a
href="https://redirect.github.com/pallets/jinja/issues/1664">#1664</a>)</li>
<li><a
href="https://github.com/pallets/jinja/commit/b8f4831d41e6a7cb5c40d42f074ffd92d2daccfc"><code>b8f4831</code></a>
more comments about nsref assignment</li>
<li><a
href="https://github.com/pallets/jinja/commit/ee832194cd9f55f75e5a51359b709d535efe957f"><code>ee83219</code></a>
Add support for namespaces in tuple assignment</li>
<li><a
href="https://github.com/pallets/jinja/commit/1d55cddbb28e433779511f28f13a2d8c4ec45826"><code>1d55cdd</code></a>
Triple quotes in docs (<a
href="https://redirect.github.com/pallets/jinja/issues/2064">#2064</a>)</li>
<li><a
href="https://github.com/pallets/jinja/commit/8a8eafc6b992ba177f1d3dd483f8465f18a11116"><code>8a8eafc</code></a>
edit block assignment section</li>
<li>Additional commits viewable in <a
href="https://github.com/pallets/jinja/compare/3.1.4...3.1.5">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=jinja2&package-manager=pip&previous-version=3.1.4&new-version=3.1.5)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/langchain-ai/langgraph/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-07 09:41:37 -05:00
William FHandGitHub a486eb5e75 0.2.61 (#2933) 2025-01-05 14:43:43 -08:00
William Fu-Hinthorn e9d62944d3 0.2.61 2025-01-05 14:42:25 -08:00
William FHandGitHub cbd09abe58 fix formatting (#2880) 2025-01-04 04:59:04 -08:00
William FHandGitHub 4798443e31 Import TypedDict from typing_extensions, add ruff rule (#2910)
Within `libs/langgraph`, change all `TypedDict` imports to come from
`typing_extensions` rather than `typing`, as `pydantic` doesn't like the
latter.

Additionally, add a ruff rule to ban these imports too (so this doesn't
regress).

Solves  #2909.
2025-01-04 04:58:43 -08:00
William FHandGitHub ce900864fa Fix typo in client.py (#2925)
Typo change in function doc
2025-01-04 04:58:23 -08:00
Ramón VargasandGitHub 577f95bd50 Merge branch 'main' into patch-1 2025-01-04 13:26:54 +01:00
William FHandGitHub 59a11c63b0 Support multiple args in @task's (#2923)
Also add support for just `@task` without the ()'s
2025-01-04 04:25:49 -08:00
Andrew NguonlyandGitHub 08098688d4 docs: Update docs for POST /v1/projects/{project_id}/revisions endpoint (#2926) 2025-01-03 10:36:38 -08:00
Ramón VargasandGitHub 687ee02509 Update client.py
Typo change in function doc
2025-01-03 18:40:15 +01:00
William Fu-Hinthorn 451bc038b6 Support multiple args in @task's 2025-01-03 07:56:27 -08:00
Nuno CamposandGitHub c865e8c070 Update README.md 2025-01-02 10:01:35 +00:00
Nuno CamposandGitHub d74ec2c2de Bubble up stack close task (#2913) 2025-01-02 09:54:43 +00:00
Nuno CamposandGitHub f70bfc6d87 Fix stream_mode=updates for cases where one node returns multiple updates for same key (#2903) 2025-01-02 09:54:06 +00:00
David DuongandGitHub c86f0af107 fix(sdk): Fix SDK Command.update type (#2901)
Align typing with JS:


https://github.com/langchain-ai/langgraphjs/blob/main/libs/langgraph/src/constants.ts#L226
2025-01-01 15:55:58 +01:00
William Fu-Hinthorn c6ee807de5 Bubble up stack close task 2025-01-01 05:41:57 -08:00
Johannes Mario Meissner 7256752f48 Import TypedDict from typing_extensions, add ruff rule 2024-12-31 22:14:16 +09:00
Nuno Campos dac84951aa Fix stream_mode=updates for cases where one node returns multiple updates for same key 2024-12-30 20:10:59 +00:00
Nuno CamposandGitHub 3aaa3e38a0 Add more tests for async cancellation (#2902) 2024-12-30 20:10:14 +00:00
Nuno Campos 400d83708a Fix 2024-12-30 20:01:35 +00:00
Nuno Campos 1d9c7ef461 Fix 2024-12-30 19:29:14 +00:00
Nuno Campos 01e5ecedfd Remove assertion of order 2024-12-30 19:26:18 +00:00
Nuno Campos 76199701b0 Add more tests for async cancellation 2024-12-30 18:56:56 +00:00
jacoblee93 2766fccb5b Bump Python version 2024-12-30 10:30:50 -08:00
jacoblee93 6aef3e0117 Update Python types too 2024-12-30 10:29:01 -08:00
jacoblee93 c5023ba147 Modify type 2024-12-30 10:22:06 -08:00
jacoblee93 9a9fe2fdec Update JS SDK command types 2024-12-30 10:14:51 -08:00
Andrew NguonlyandGitHub effddca494 docs: LangGraph Control Plane API (#2881) 2024-12-26 16:15:18 -08:00
Harrison Chase 2ab59840e7 cr 2024-12-26 12:48:08 -08:00
Eugene YurtsevandGitHub 0fb65f6e67 docs: fix accidental nav bar change (#2865) 2024-12-23 21:47:14 -05:00
Eugene Yurtsev 0ecd23eec6 fix accidental commit 2024-12-23 21:38:16 -05:00
Eugene YurtsevandGitHub e137dabf22 docs: memoize class resolution (#2861)
Main purpose is to avoid all the repeated log warnings which make the
builds hare to understand
2024-12-23 14:24:16 -05:00
Eugene YurtsevandGitHub fdc1e47aa1 docs: add edit uri (#2860) 2024-12-23 14:20:17 -05:00
Eugene Yurtsev 866780b477 x 2024-12-23 14:11:38 -05:00
Eugene Yurtsev 18d3fa2e15 x 2024-12-23 14:06:25 -05:00
Eugene YurtsevandGitHub 4b0c53fb5c docs: improve api reference generation in code blocks (#2857)
Generate api references from any markdown -- this will handle markdown
files in notebooks as well as code blocks in plain markdown
2024-12-23 13:35:08 -05:00
Eugene Yurtsev 5183484322 x 2024-12-22 22:10:29 -05:00
Eugene Yurtsev 8213e4719b qx 2024-12-22 22:10:06 -05:00
Eugene Yurtsev f993dfcfcb x 2024-12-22 22:07:59 -05:00
Eugene YurtsevandGitHub 9e31b82d8d docs: Add highlight-next-line (#2850)
Add highlight next line


![image](https://github.com/user-attachments/assets/cda3c9f2-1f98-489a-a487-d1ad9c6a1186)


![image](https://github.com/user-attachments/assets/bfa05f4d-50cc-490a-9598-5076068d8f4b)
2024-12-20 21:10:51 -05:00
Vadym BardaandGitHub a3c5b8fc37 checkpoint-postgres: release 2.0.9 (#2849) 2024-12-20 17:47:20 -05:00
Eugene Yurtsev 1e0aebc3ec x 2024-12-20 17:38:20 -05:00
Eugene Yurtsev 056f581342 x 2024-12-20 17:29:10 -05:00
Eugene Yurtsev a0d7323bec x 2024-12-20 17:14:22 -05:00
Vadym BardaandGitHub 44ee0199fd checkpoint postgres: add a shallow checkpointer (#2826)
This PR adds a "shallow" version of `PostgresSaver` checkpointer that
ONLY stores the most recent checkpoint and does NOT retain any history.
It is meant to be a light-weight drop-in replacement for the
PostgresSaver that supports most of the LangGraph persistence
functionality with the exception of time travel.
2024-12-20 17:51:20 +00:00
1de61f6fce docs: Improve explanation and fix grammar mistakes in agentic concepts guide (#2541)
Made some of the explanations more clear by rephrasing certain parts of
the sentence.

Fixed minor grammar mistakes also.

---------

Co-authored-by: Vadym Barda <vadim.barda@gmail.com>
2024-12-20 11:20:27 -05:00
Vadym BardaandGitHub f33db6cec4 docs: remove langgraph up references (#2847) 2024-12-20 11:00:04 -05:00
Vadym BardaandGitHub c72107177b docs: update custom agent in handoffs doc (#2846) 2024-12-20 14:59:38 +00:00
Yassin NouhandGitHub f520a38d30 docs: missing docstring for aupdate_state method (#2435) 2024-12-20 09:06:29 -05:00
Andrew NguonlyandGitHub d0278f520c docs: Update CPU for Production type deployments (#2845) 2024-12-19 21:04:31 -08:00
Vadym BardaandGitHub 506539ac9d langgraph: actually run test_large_cases_async (#2843) 2024-12-19 22:25:22 +00:00
JasonJandGitHub d6c6516f16 fix: minor modification, syntax error (#1905) 2024-12-19 15:29:10 -05:00
Sarthak GuptaandGitHub 6f5d6d9993 docs: remove Literal as it is not being used (#2149)
This PR removes the use of `from typing import Literal` since it is not
being used in the code implementation
2024-12-19 15:25:27 -05:00
Neeraj GandGitHub 8dcd058404 Formatting inconsistency in low_level.md (#1342) 2024-12-19 15:21:49 -05:00
William FHandGitHub e3050b3a3e [Docs] Show example payloads (#2839) 2024-12-19 10:38:17 -08:00
William Fu-Hinthorn 9e767afad7 Link 2024-12-19 09:09:36 -08:00
William Fu-Hinthorn 62b35277ec Warning more obvious 2024-12-19 09:01:50 -08:00
William Fu-Hinthorn 931419909b rm 2024-12-19 08:59:11 -08:00
William Fu-Hinthorn e849c869cc [Docs] Add example payloads to code 2024-12-19 08:52:31 -08:00
William FHandGitHub 5a580ae5ec [Docs] Add diagrams (#2834) 2024-12-19 06:37:55 -08:00
William Fu-Hinthorn 47a0e09513 Add prereq 2024-12-19 06:28:56 -08:00
William Fu-Hinthorn f37486efe2 Add images 2024-12-19 06:26:52 -08:00
William FHandGitHub fa61be9fbc [Docs] Bullet points (#2830) 2024-12-18 23:10:13 -08:00
William Fu-Hinthorn 08097a78bd [Docs] Bullet points 2024-12-18 23:09:06 -08:00
William FHandGitHub 43f610e9a6 [Doc] Fix env var name (#2828) 2024-12-18 21:32:08 -08:00
William Fu-Hinthorn aa1ddee67e [Doc] Fix env var name 2024-12-18 21:30:12 -08:00
William FHandGitHub 12b46e8a69 [Docs] Make example more illustrative (#2827) 2024-12-18 18:58:26 -08:00
William Fu-Hinthorn d90f69105a missed 2024-12-18 18:49:49 -08:00
William Fu-Hinthorn fbd3b67183 [Docs] Make example more illustrative 2024-12-18 18:47:35 -08:00
William FHandGitHub 6d8be543e7 Update syntax highlighting (#2825) 2024-12-18 17:53:33 -08:00
William Fu-Hinthorn 1c1772f7ec Update syntax highlighting 2024-12-18 17:51:07 -08:00
William FHandGitHub ce239c784a [Docs] Ignore linkcheck localhost on main (#2824) 2024-12-18 16:48:02 -08:00
William Fu-Hinthorn c14c978824 [Docs] Ignore localhost on main 2024-12-18 16:46:34 -08:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>Vadym Barda
ebb2823be0 build(deps-dev): bump tornado from 6.4.1 to 6.4.2 in /libs/langgraph (#2814)
Bumps [tornado](https://github.com/tornadoweb/tornado) from 6.4.1 to
6.4.2.
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/tornadoweb/tornado/blob/v6.4.2/docs/releases.rst">tornado's
changelog</a>.</em></p>
<blockquote>
<h1>Release notes</h1>
<p>.. toctree::
:maxdepth: 2</p>
<p>releases/v6.4.2
releases/v6.4.1
releases/v6.4.0
releases/v6.3.3
releases/v6.3.2
releases/v6.3.1
releases/v6.3.0
releases/v6.2.0
releases/v6.1.0
releases/v6.0.4
releases/v6.0.3
releases/v6.0.2
releases/v6.0.1
releases/v6.0.0
releases/v5.1.1
releases/v5.1.0
releases/v5.0.2
releases/v5.0.1
releases/v5.0.0
releases/v4.5.3
releases/v4.5.2
releases/v4.5.1
releases/v4.5.0
releases/v4.4.3
releases/v4.4.2
releases/v4.4.1
releases/v4.4.0
releases/v4.3.0
releases/v4.2.1
releases/v4.2.0
releases/v4.1.0
releases/v4.0.2
releases/v4.0.1
releases/v4.0.0
releases/v3.2.2
releases/v3.2.1
releases/v3.2.0
releases/v3.1.1
releases/v3.1.0
releases/v3.0.2
releases/v3.0.1
releases/v3.0.0
releases/v2.4.1
releases/v2.4.0</p>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/tornadoweb/tornado/commit/a5ecfab15e52202a46d34638aad93cddca86d87b"><code>a5ecfab</code></a>
Bump version to 6.4.2</li>
<li><a
href="https://github.com/tornadoweb/tornado/commit/bc7df6bafdec61155e7bf385081feb205463857d"><code>bc7df6b</code></a>
Fix tests with Twisted 24.7.0</li>
<li><a
href="https://github.com/tornadoweb/tornado/commit/d5ba4a1695fbf7c6a3e54313262639b198291533"><code>d5ba4a1</code></a>
httputil: Fix quadratic performance of cookie parsing</li>
<li>See full diff in <a
href="https://github.com/tornadoweb/tornado/compare/v6.4.1...v6.4.2">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=tornado&package-manager=pip&previous-version=6.4.1&new-version=6.4.2)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/langchain-ai/langgraph/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Vadym Barda <vadym@langchain.dev>
2024-12-18 19:36:20 -05:00
William FHandGitHub 25f0224f97 [Docs] Link glob (#2823) 2024-12-18 16:35:09 -08:00
William FHandGitHub fdf9b0ad48 Merge branch 'main' into wfh/expand_link_glob 2024-12-18 16:34:58 -08:00
William FHandGitHub 00ecccfd81 [docs] Remove show source: true in ref docs (#2821) 2024-12-18 16:34:46 -08:00
William Fu-Hinthorn 7d5452d039 [Docs] Link glob 2024-12-18 16:32:02 -08:00
William FHandGitHub 2d8de54e80 [Docs] Ignore 127.0.0 links (#2822) 2024-12-18 16:16:42 -08:00
William Fu-Hinthorn 32abc62bc1 [Docs] Ignore 127.0.0 links 2024-12-18 16:14:54 -08:00
William Fu-Hinthorn e36cf4111d [docs] Remove show source: true in ref docs 2024-12-18 16:04:35 -08:00
William FHandGitHub 53a2c2bdcd [Docs] Fix mkdocs.yml paths (#2820) 2024-12-18 15:58:30 -08:00
William Fu-Hinthorn 2cae9337a7 Fix broken index 2024-12-18 15:56:35 -08:00
William Fu-Hinthorn b056e17b38 Linkcheck 2024-12-18 15:46:45 -08:00
William FHandGitHub 2855caa3eb Fix config cli type from Path -> str (#2771)
Config parameter in `dev` was typed as pathlib.Path, but it is actually
a string. We need to manually create a Path from the string when parsing
the config.

FIxes #2647
2024-12-18 15:25:15 -08:00
William FHandGitHub 140566e662 [Docs] Add auth docs (#2797) 2024-12-18 15:22:26 -08:00
William FH b7f57b1375 Merge branch 'main' into wfh/docs/auth 2024-12-18 15:09:15 -08:00
Vadym BardaandGitHub 121c5863db docs: add a banner for langchain academy (#2818) 2024-12-18 23:08:51 +00:00
William Fu-Hinthorn 1709cd3fb5 concept 2024-12-18 15:08:19 -08:00
William Fu-Hinthorn d4a1fe5a03 concept 2024-12-18 15:03:13 -08:00
William Fu-Hinthorn 06e1e4ed12 Update cross-linking 2024-12-18 14:52:02 -08:00
Vadym BardaandGitHub 1e62b175ba docs: make the studio web UI docs more clear (#2812) 2024-12-18 22:40:22 +00:00
William FHandGitHub f89fe49f99 Merge branch 'main' into wfh/docs/auth 2024-12-18 14:26:39 -08:00
William Fu-Hinthorn 2e1971baf3 Remaining feedback 2024-12-18 14:25:25 -08:00
William Fu-Hinthorn 948027aef2 Notebook style 2024-12-18 14:22:15 -08:00
William Fu-Hinthorn 6954e63671 Numbering 2024-12-18 14:16:35 -08:00
William Fu-Hinthorn 7f0bfdc139 Feedback 2024-12-18 14:15:54 -08:00
William FHandGitHub 0496128e6b [CLI] Bump min-bound for langgraph-api (#2816) 2024-12-18 12:38:37 -08:00
William FHandGitHub d79b1a61e8 Merge branch 'main' into fix-cli-path 2024-12-18 12:34:39 -08:00
William Fu-Hinthorn e227f6ce83 Bump version 2024-12-18 12:30:06 -08:00
William Fu-Hinthorn 8a1a11fde5 [CLI] Update min bound for langgraph-api 2024-12-18 12:29:17 -08:00
Andrew NguonlyandGitHub c47fd171c6 docs(cloud): Add note about GitHub org/acc owner (#2815)
### Summary
Clarifying that in order to install the `hosted-langserve` GitHub app,
the GitHub user must be an owner of the organization or account.
2024-12-18 11:45:30 -08:00
William Fu-Hinthorn 1e07a9ac97 Add admonition 2024-12-18 11:41:52 -08:00
William Fu-Hinthorn d9cc227e75 Unpin install command in doc 2024-12-18 11:41:52 -08:00
6b45a281c1 fix example in docs of state_schema in create_react_agent (#2109)
because in
```python
    def call_model(
        state: AgentState,
        config: RunnableConfig,
    )
...
        if (
            (
                "remaining_steps" not in state
                and state["is_last_step"]
                and has_tool_calls
            )
```

https://github.com/langchain-ai/langgraph/blob/c0b56bf60d84ed435609c35b0691cd0305ceae78/libs/langgraph/langgraph/prebuilt/chat_agent_executor.py#L543
the AgentState requires is_last_step to have a default value, like
`False`, and `IsLastStep` can satisfy it.

---------

Co-authored-by: Vadym Barda <vadym@langchain.dev>
2024-12-18 11:41:52 -08:00
BagaturandWilliam Fu-Hinthorn 532bc71c11 langgraph[patch]: format messages in state (#2199)
Add `format` flag to `add_messages` which allows you to specify if the
contents of messages in state should be formatted in a particular way.
PR only adds support for OpenAI style contents. Helpful if you're using
different models at different nodes and want a unified messages format
to interact with when you manually update messages.
2024-12-18 11:41:52 -08:00
dependabot[bot]William Fu-Hinthorndependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>Vadym Barda
dabd75f1f9 build(deps-dev): bump tornado from 6.4.1 to 6.4.2 (#2519)
Bumps [tornado](https://github.com/tornadoweb/tornado) from 6.4.1 to
6.4.2.
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/tornadoweb/tornado/blob/v6.4.2/docs/releases.rst">tornado's
changelog</a>.</em></p>
<blockquote>
<h1>Release notes</h1>
<p>.. toctree::
:maxdepth: 2</p>
<p>releases/v6.4.2
releases/v6.4.1
releases/v6.4.0
releases/v6.3.3
releases/v6.3.2
releases/v6.3.1
releases/v6.3.0
releases/v6.2.0
releases/v6.1.0
releases/v6.0.4
releases/v6.0.3
releases/v6.0.2
releases/v6.0.1
releases/v6.0.0
releases/v5.1.1
releases/v5.1.0
releases/v5.0.2
releases/v5.0.1
releases/v5.0.0
releases/v4.5.3
releases/v4.5.2
releases/v4.5.1
releases/v4.5.0
releases/v4.4.3
releases/v4.4.2
releases/v4.4.1
releases/v4.4.0
releases/v4.3.0
releases/v4.2.1
releases/v4.2.0
releases/v4.1.0
releases/v4.0.2
releases/v4.0.1
releases/v4.0.0
releases/v3.2.2
releases/v3.2.1
releases/v3.2.0
releases/v3.1.1
releases/v3.1.0
releases/v3.0.2
releases/v3.0.1
releases/v3.0.0
releases/v2.4.1
releases/v2.4.0</p>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/tornadoweb/tornado/commit/a5ecfab15e52202a46d34638aad93cddca86d87b"><code>a5ecfab</code></a>
Bump version to 6.4.2</li>
<li><a
href="https://github.com/tornadoweb/tornado/commit/bc7df6bafdec61155e7bf385081feb205463857d"><code>bc7df6b</code></a>
Fix tests with Twisted 24.7.0</li>
<li><a
href="https://github.com/tornadoweb/tornado/commit/d5ba4a1695fbf7c6a3e54313262639b198291533"><code>d5ba4a1</code></a>
httputil: Fix quadratic performance of cookie parsing</li>
<li>See full diff in <a
href="https://github.com/tornadoweb/tornado/compare/v6.4.1...v6.4.2">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=tornado&package-manager=pip&previous-version=6.4.1&new-version=6.4.2)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/langchain-ai/langgraph/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Vadym Barda <vadym@langchain.dev>
2024-12-18 11:41:52 -08:00
Nuno CamposandWilliam Fu-Hinthorn ef88b805a7 0.2.60 2024-12-18 11:41:52 -08:00
Nuno CamposandWilliam Fu-Hinthorn 7e8a68f0f0 Fix 2024-12-18 11:41:52 -08:00
Nuno CamposandWilliam Fu-Hinthorn e20bd15580 lib: Fix incorrect default for Command.update
- this should not default to empty tuple, it should default to None
2024-12-18 11:41:52 -08:00
William Fu-Hinthorn 51c57cc819 [SDK] Add studio user object 2024-12-18 11:41:52 -08:00
William FHandGitHub 09a6450eed [SDK] Add studio user object (#2813) 2024-12-18 11:33:50 -08:00
William FHandGitHub 77fba7a571 Merge branch 'main' into wfh/sdk/studio_user 2024-12-18 11:28:21 -08:00
William Fu-Hinthorn 60232eadc4 [SDK] Add studio user object 2024-12-18 11:21:13 -08:00
83e4e6c4c2 Update docs/docs/tutorials/auth/add_auth_server.md
Co-authored-by: Eugene Yurtsev <eyurtsev@gmail.com>
2024-12-18 11:15:38 -08:00
William FHandGitHub a6f0c665af Unpin install command in doc (#2811) 2024-12-18 10:13:29 -08:00
William Fu-Hinthorn f1a2e19144 Add admonition 2024-12-18 10:04:57 -08:00
William Fu-Hinthorn b28e9d0a87 Unpin install command in doc 2024-12-18 10:03:16 -08:00
William Fu-Hinthorn b3230cf6d1 More guidance 2024-12-18 10:01:56 -08:00
1a6c3114f3 fix example in docs of state_schema in create_react_agent (#2109)
because in
```python
    def call_model(
        state: AgentState,
        config: RunnableConfig,
    )
...
        if (
            (
                "remaining_steps" not in state
                and state["is_last_step"]
                and has_tool_calls
            )
```

https://github.com/langchain-ai/langgraph/blob/c0b56bf60d84ed435609c35b0691cd0305ceae78/libs/langgraph/langgraph/prebuilt/chat_agent_executor.py#L543
the AgentState requires is_last_step to have a default value, like
`False`, and `IsLastStep` can satisfy it.

---------

Co-authored-by: Vadym Barda <vadym@langchain.dev>
2024-12-18 12:46:33 -05:00
BagaturandGitHub 4f1bf4fa7a langgraph[patch]: format messages in state (#2199)
Add `format` flag to `add_messages` which allows you to specify if the
contents of messages in state should be formatted in a particular way.
PR only adds support for OpenAI style contents. Helpful if you're using
different models at different nodes and want a unified messages format
to interact with when you manually update messages.
2024-12-18 17:19:04 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>Vadym Barda
22fa673872 build(deps-dev): bump tornado from 6.4.1 to 6.4.2 (#2519)
Bumps [tornado](https://github.com/tornadoweb/tornado) from 6.4.1 to
6.4.2.
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/tornadoweb/tornado/blob/v6.4.2/docs/releases.rst">tornado's
changelog</a>.</em></p>
<blockquote>
<h1>Release notes</h1>
<p>.. toctree::
:maxdepth: 2</p>
<p>releases/v6.4.2
releases/v6.4.1
releases/v6.4.0
releases/v6.3.3
releases/v6.3.2
releases/v6.3.1
releases/v6.3.0
releases/v6.2.0
releases/v6.1.0
releases/v6.0.4
releases/v6.0.3
releases/v6.0.2
releases/v6.0.1
releases/v6.0.0
releases/v5.1.1
releases/v5.1.0
releases/v5.0.2
releases/v5.0.1
releases/v5.0.0
releases/v4.5.3
releases/v4.5.2
releases/v4.5.1
releases/v4.5.0
releases/v4.4.3
releases/v4.4.2
releases/v4.4.1
releases/v4.4.0
releases/v4.3.0
releases/v4.2.1
releases/v4.2.0
releases/v4.1.0
releases/v4.0.2
releases/v4.0.1
releases/v4.0.0
releases/v3.2.2
releases/v3.2.1
releases/v3.2.0
releases/v3.1.1
releases/v3.1.0
releases/v3.0.2
releases/v3.0.1
releases/v3.0.0
releases/v2.4.1
releases/v2.4.0</p>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/tornadoweb/tornado/commit/a5ecfab15e52202a46d34638aad93cddca86d87b"><code>a5ecfab</code></a>
Bump version to 6.4.2</li>
<li><a
href="https://github.com/tornadoweb/tornado/commit/bc7df6bafdec61155e7bf385081feb205463857d"><code>bc7df6b</code></a>
Fix tests with Twisted 24.7.0</li>
<li><a
href="https://github.com/tornadoweb/tornado/commit/d5ba4a1695fbf7c6a3e54313262639b198291533"><code>d5ba4a1</code></a>
httputil: Fix quadratic performance of cookie parsing</li>
<li>See full diff in <a
href="https://github.com/tornadoweb/tornado/compare/v6.4.1...v6.4.2">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=tornado&package-manager=pip&previous-version=6.4.1&new-version=6.4.2)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/langchain-ai/langgraph/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Vadym Barda <vadym@langchain.dev>
2024-12-18 10:04:05 -05:00
Nuno Campos 3d85f2296c 0.2.60 2024-12-18 13:45:32 +00:00
Nuno CamposandGitHub 5ad9cfa030 lib: Fix incorrect default for Command.update (#2809)
- this should not default to empty tuple, it should default to None
2024-12-18 13:35:53 +00:00
Nuno Campos 39281c866d Fix 2024-12-18 13:28:22 +00:00
Nuno Campos 4c3a38d324 lib: Fix incorrect default for Command.update
- this should not default to empty tuple, it should default to None
2024-12-18 13:15:38 +00:00
William Fu-Hinthorn 8fc3f204ea Add pt 2 and 3 2024-12-18 01:10:33 -08:00
William FH 2df2f41dc7 Merge branch 'main' into wfh/docs/auth 2024-12-18 01:08:41 -08:00
William Fu-Hinthorn 30e647abce Add links 2024-12-18 01:06:01 -08:00
William Fu-Hinthorn b85c9961d7 Split into 3 2024-12-18 00:50:38 -08:00
William Fu-Hinthorn 1c97bddc14 Fix cross linking 2024-12-17 18:22:56 -08:00
William Fu-Hinthorn 97b3d1b9ac Simplify tutorial 2024-12-17 17:43:09 -08:00
Vadym BardaandGitHub 8b70da6a0f docs: update howtos nav (#2805) 2024-12-17 19:50:00 -05:00
William Fu-Hinthorn 7904fdc928 Update explanations 2024-12-17 16:41:00 -08:00
William Fu-Hinthorn ba56ba1b2a Add langgraph reference 2024-12-17 16:31:13 -08:00
William Fu-Hinthorn 12f2a480cd Add concepts 2024-12-17 16:25:48 -08:00
Vadym BardaandGitHub 532cb0a691 docs: reorg multi-agent howtos (#2784) 2024-12-18 00:01:28 +00:00
William Fu-Hinthorn 23e18e8c1b Add how-tos 2024-12-17 14:50:09 -08:00
William Fu-Hinthorn 6d160a2865 Merge branch 'main' into wfh/docs/auth 2024-12-17 14:04:31 -08:00
e0b4eb6454 doc: fixed typo in interrupt_concurrent.md (#2791)
Co-authored-by: Eugene Yurtsev <eyurtsev@gmail.com>
2024-12-17 15:49:44 -05:00
Eugene YurtsevandGitHub 608ff41e78 ci: fix link checker (#2803) 2024-12-17 15:41:10 -05:00
Eugene YurtsevandGitHub 9280e3411b docs: update why langgraph and other typos (#2799) 2024-12-17 15:36:41 -05:00
William FHandGitHub 36fa8e0097 Update ref docs (#2802) 2024-12-17 11:54:32 -08:00
Eugene Yurtsev f63217794d qx 2024-12-17 14:50:26 -05:00
William Fu-Hinthorn bcb11640a5 Update AuthContext 2024-12-17 11:46:30 -08:00
William Fu-Hinthorn b6c3a0d0fd Update wording 2024-12-17 11:37:25 -08:00
Luke JangandGitHub 8b14b6a9f0 docs: removed redundant import from the example (#2794)
there were two same import
"from langchain_openai import ChatOpenAI"
2024-12-17 14:33:35 -05:00
William Fu-Hinthorn 8c63cc1778 Add excption docs 2024-12-17 11:22:54 -08:00
William Fu-Hinthorn 6756e91ffc Authenticate return single type 2024-12-17 11:01:54 -08:00
William Fu-Hinthorn 3dd1e67977 Add mermaid 2024-12-17 10:54:18 -08:00
Eugene YurtsevandGitHub fc5d919aee docs: fix some typos (#2790) 2024-12-17 13:53:57 -05:00
Eugene Yurtsev 0369300160 update 2024-12-17 12:24:15 -05:00
William Fu-Hinthorn 245cf83b20 [Docs] Add auth tutorial 2024-12-17 08:59:04 -08:00
Vadym BardaandGitHub 9bd351b80f docs: small fixes (#2798) 2024-12-17 11:58:38 -05:00
Eugene YurtsevandGitHub 83c3f86159 docs: fix port
Fix port from 8123 (was the default for langgraph up) to 2024 (the default for langgraph dev)
2024-12-17 10:29:12 -05:00
Eugene Yurtsev 3a41a2addc q 2024-12-16 22:04:21 -05:00
Eugene Yurtsev 3eca363c23 x 2024-12-16 22:01:53 -05:00
Eugene Yurtsev 2f1e864570 x 2024-12-16 22:00:41 -05:00
Eugene Yurtsev 79a1ce6804 x 2024-12-16 21:47:32 -05:00
Eugene YurtsevandGitHub deb99a9acb docs: Update langgraph cloud deploy quickstart
docs: update langgraph cloud deploy quickstart
2024-12-16 20:39:58 -05:00
William FHandGitHub 781d0cf27a [CLI] Handle dependencies js up --watch 2024-12-16 16:27:56 -08:00
William Fu-Hinthorn 8a02ddd868 [CLI] Handle dependencies js up --watch 2024-12-16 16:19:23 -08:00
Eugene Yurtsev 5f7dcbb07a x 2024-12-16 17:57:01 -05:00
Eugene Yurtsev 9615580a66 x 2024-12-16 17:55:29 -05:00
Eugene Yurtsev d45eb0f9f2 x 2024-12-16 17:49:38 -05:00
William FHandGitHub 4caa483478 [SDK] relax response typehint
So you can return a dict with extra args
2024-12-16 14:02:46 -08:00
William Fu-Hinthorn 006305f6a8 [SDK] relax response typehint 2024-12-16 11:28:21 -08:00
Vadym BardaandGitHub d9b7aaa5cc langgraph: relax constraints in ToolNode Command validation (#2778) 2024-12-16 14:00:45 -05:00
Vadym BardaandGitHub d2794eda0a langgraph: relax type annotation for Command.update (#2777)
Addresses #2758 , #2747
2024-12-16 14:00:23 -05:00
William FHandGitHub 9cbef9b542 [SDK] Add HTTPException type
To make it easier to raise exceptions with custom status codes in the imported file.
2024-12-16 09:01:15 -08:00
William Fu-Hinthorn 343dc2d37a [SDK] Add HTTPException type 2024-12-16 08:45:44 -08:00
David DuongandGitHub ca0ff1d334 Merge pull request #2774 from langchain-ai/dqbd/studio-datasets-navigation
docs: update studio index to add datasets
2024-12-16 20:14:46 +04:00
Tat Dat Duong f5663ffa49 docs: update studio index to add datasets 2024-12-16 16:44:43 +01:00
David DuongandGitHub e7477a9315 Merge pull request #2680 from langchain-ai/dqbd/add-to-dataset-docs
feat(studio): add Add to Dataset docs
2024-12-16 19:31:49 +04:00
Tat Dat Duong 18c083b60a Use S3 for assets 2024-12-16 16:24:05 +01:00
Vadym BardaandGitHub 2f0e3c66d1 docs: update structured output how-to guide (#2773)
Fixes #2760
2024-12-16 09:51:12 -05:00
ZapironandGitHub d9ec185e72 docs: Update correct ID for initial ToolNode hyperlink (#2767)
Automatically leads to the correct `ToolNode` section instead of the top
2024-12-16 09:15:34 -05:00
Denis Capkovic 721945b5ce Fix config cli type from Path -> str
Config parameter in `dev` was typed as pathlib.Path, but it is actually
a string. We need to manually create a Path from the string when parsing
the config.
2024-12-16 10:52:02 +01:00
William FHandGitHub 87fba0ecd0 [CLI] Add openapi param to config
For langraph api
2024-12-14 07:33:07 -08:00
William FHandGitHub 30e6b5482f Merge branch 'main' into wfh/cli/add_auth_env_var 2024-12-14 07:26:34 -08:00
William Fu-Hinthorn 77b42e0867 [CLI] Add openapi param to config 2024-12-14 07:25:14 -08:00
Tat Dat Duong df1e48154a feat(studio): add Add to Dataset docs 2024-12-09 19:22:41 +01:00
153 changed files with 13462 additions and 2918 deletions
+23 -1
View File
@@ -109,9 +109,31 @@ jobs:
- name: Build
run: yarn build
test-js:
runs-on: ubuntu-latest
strategy:
matrix:
working-directory:
- "libs/sdk-js"
defaults:
run:
working-directory: ${{ matrix.working-directory }}
steps:
- uses: actions/checkout@v3
- name: Setup Node.js (LTS)
uses: actions/setup-node@v3
with:
node-version: "20"
cache: "yarn"
cache-dependency-path: ${{ matrix.working-directory }}/yarn.lock
- name: Install dependencies
run: yarn install
- name: Run tests
run: yarn test
ci_success:
name: "CI Success"
needs: [lint, lint-js, test, test-langgraph, test-scheduler-kafka, integration-test]
needs: [lint, lint-js, test, test-langgraph, test-scheduler-kafka, integration-test, test-js]
if: |
always()
runs-on: ubuntu-latest
+4
View File
@@ -89,6 +89,8 @@ jobs:
--check-links-ignore "https://x.com/.*" \
--check-links-ignore "https://github\.com/.*" \
--check-links-ignore "http://localhost:8123/.*" \
--check-links-ignore "http://localhost:2024.*" \
--check-links-ignore "http://127.0.0.1:.*" \
--check-links-ignore "/.*\.(ipynb|html)$" \
--check-links-ignore "https://python\.langchain\.com/.*" \
--check-links-ignore "https://openai\.com/.*" \
@@ -106,6 +108,8 @@ jobs:
poetry run pytest -v \
--check-links-ignore "https://(api|web|docs)\.smith\.langchain\.com/.*" \
--check-links-ignore "http://localhost:8123/.*" \
--check-links-ignore "http://localhost:2024.*" \
--check-links-ignore "http://127.0.0.1:.*" \
--check-links-ignore "https://x.com/.*" \
--check-links-ignore "https://github\.com/.*" \
--check-links-ignore "/.*\.(ipynb|html)$" \
+1 -1
View File
@@ -13,7 +13,7 @@ serve-clean-docs: clean-docs
poetry run python -m mkdocs serve -c -f docs/mkdocs.yml --strict -w ./libs/langgraph
serve-docs: build-typedoc
poetry run python -m mkdocs serve -f docs/mkdocs.yml -w ./libs/langgraph -w ./libs/checkpoint --dirty
poetry run python -m mkdocs serve -f docs/mkdocs.yml -w ./libs/langgraph -w ./libs/checkpoint -w ./libs/sdk-py --dirty
clean-docs:
find ./docs/docs -name "*.ipynb" -type f -delete
+1 -1
View File
@@ -8,7 +8,7 @@
⚡ Building language agents as graphs ⚡
> [!NOTE]
> Looking for the JS version? Click [here](https://github.com/langchain-ai/langgraphjs) ([JS docs](https://langchain-ai.github.io/langgraphjs/)).
> Looking for the JS version? See the [JS repo](https://github.com/langchain-ai/langgraphjs) and the [JS docs](https://langchain-ai.github.io/langgraphjs/).
## Overview
+93
View File
@@ -0,0 +1,93 @@
import functools
from urllib3 import __version__ as urllib3version # type: ignore[import-untyped]
from urllib3 import connection # type: ignore[import-untyped]
def _ensure_str(s, encoding="utf-8", errors="strict") -> str:
if isinstance(s, str):
return s
if isinstance(s, bytes):
return s.decode(encoding, errors)
return str(s)
# Copied from https://github.com/urllib3/urllib3/blob/1c994dfc8c5d5ecaee8ed3eb585d4785f5febf6e/src/urllib3/connection.py#L231
def request(self, method, url, body=None, headers=None):
"""Make the request.
This function is based on the urllib3 request method, with modifications
to handle potential issues when using vcrpy in concurrent workloads.
Args:
self: The HTTPConnection instance.
method (str): The HTTP method (e.g., 'GET', 'POST').
url (str): The URL for the request.
body (Optional[Any]): The body of the request.
headers (Optional[dict]): Headers to send with the request.
Returns:
The result of calling the parent request method.
"""
# Update the inner socket's timeout value to send the request.
# This only triggers if the connection is re-used.
if getattr(self, "sock", None) is not None:
self.sock.settimeout(self.timeout)
if headers is None:
headers = {}
else:
# Avoid modifying the headers passed into .request()
headers = headers.copy()
if "user-agent" not in (_ensure_str(k.lower()) for k in headers):
headers["User-Agent"] = connection._get_default_user_agent()
# The above is all the same ^^^
# The following is different:
return self._parent_request(method, url, body=body, headers=headers)
_PATCHED = False
def patch_urllib3():
"""Patch the request method of urllib3 to avoid type errors when using vcrpy.
In concurrent workloads (such as the tracing background queue), the
connection pool can get in a state where an HTTPConnection is created
before vcrpy patches the HTTPConnection class. In urllib3 >= 2.0 this isn't
a problem since they use the proper super().request(...) syntax, but in older
versions, super(HTTPConnection, self).request is used, resulting in a TypeError
since self is no longer a subclass of "HTTPConnection" (which at this point
is vcr.stubs.VCRConnection).
This method patches the class to fix the super() syntax to avoid mixed inheritance.
In the case of the LangSmith tracing logic, it doesn't really matter since we always
exclude cache checks for calls to LangSmith.
The patch is only applied for urllib3 versions older than 2.0.
"""
global _PATCHED
if _PATCHED:
return
from packaging import version
if version.parse(urllib3version) >= version.parse("2.0"):
_PATCHED = True
return
# Lookup the parent class and its request method
parent_class = connection.HTTPConnection.__bases__[0]
parent_request = parent_class.request
def new_request(self, *args, **kwargs):
"""Handle parent request.
This method binds the parent's request method to self and then
calls our modified request function.
"""
self._parent_request = functools.partial(parent_request, self)
return request(self, *args, **kwargs)
connection.HTTPConnection.request = new_request
_PATCHED = True
+83 -39
View File
@@ -6,6 +6,9 @@ import re
from typing import List, Literal, Optional
from typing_extensions import TypedDict
from functools import lru_cache
import nbformat
from nbconvert.preprocessors import Preprocessor
@@ -47,6 +50,8 @@ MANUAL_API_REFERENCES_LANGGRAPH = [
(["langgraph.graph"], "langgraph.constants", "END", "constants"),
(["langgraph.constants"], "langgraph.types", "Send", "types"),
(["langgraph.constants"], "langgraph.types", "Interrupt", "types"),
(["langgraph.constants"], "langgraph.types", "interrupt", "types"),
(["langgraph.constants"], "langgraph.types", "Command", "types"),
([], "langgraph.types", "RetryPolicy", "types"),
([], "langgraph.checkpoint.base", "Checkpoint", "checkpoints"),
([], "langgraph.checkpoint.base", "CheckpointMetadata", "checkpoints"),
@@ -83,8 +88,11 @@ _IMPORT_LANGCHAIN_RE = _make_regular_expression("langchain")
_IMPORT_LANGGRAPH_RE = _make_regular_expression("langgraph")
def _get_full_module_name(module_path, class_name) -> Optional[str]:
"""Get full module name using inspect"""
@lru_cache(maxsize=10_000)
def _get_full_module_name(module_path: str, class_name: str) -> Optional[str]:
"""Get full module name using inspect, with LRU cache to memoize results."""
try:
module = importlib.import_module(module_path)
class_ = getattr(module, class_name)
@@ -95,13 +103,12 @@ def _get_full_module_name(module_path, class_name) -> Optional[str]:
return module_path
return module.__name__
except AttributeError as e:
logger.warning(f"Could not find module for {class_name}, {e}")
logger.warning(f"API Reference: Could not find module for {class_name}, {e}")
return None
except ImportError as e:
logger.warning(f"Failed to load for class {class_name}, {e}")
logger.warning(f"API Reference: Failed to load for class {class_name}, {e}")
return None
def _get_doc_title(data: str, file_name: str) -> str:
try:
return re.findall(r"^#\s*(.*)", data, re.MULTILINE)[0]
@@ -115,10 +122,10 @@ def _get_doc_title(data: str, file_name: str) -> str:
class ImportInformation(TypedDict):
imported: str # imported class name
source: str # module path
docs: str # URL to the documentation
title: str # Title of the document
imported: str # The name of the class that was imported.
source: str # The full module path from which the class was imported.
docs: str # The URL pointing to the class's documentation.
title: str # The title of the document where the import is used.
def _get_imports(
@@ -211,36 +218,73 @@ def _get_imports(
return imports
class ImportPreprocessor(Preprocessor):
"""A preprocessor to replace imports in each Python code cell with links to their
documentation and append the import info in a comment."""
def get_imports(code: str, doc_title: str) -> List[ImportInformation]:
"""Retrieve all import references from the given code for specified ecosystems.
def preprocess(self, nb, resources):
self.all_imports = []
file_name = os.path.basename(resources.get("metadata", {}).get("name", ""))
_DOC_TITLE = _get_doc_title(nb.cells[0].source, file_name)
Args:
code: The source code from which to extract import references.
doc_title: The documentation title associated with the code.
cells = []
for cell in nb.cells:
if cell.cell_type == "code":
cells.append(cell)
imports = _get_imports(
cell.source, _DOC_TITLE, "langchain"
) + _get_imports(cell.source, _DOC_TITLE, "langgraph")
if not imports:
continue
Returns:
A list of import information for each import found.
"""
ecosystems = ["langchain", "langgraph"]
all_imports = []
for package_ecosystem in ecosystems:
all_imports.extend(_get_imports(code, doc_title, package_ecosystem))
return all_imports
cells.append(
nbformat.v4.new_markdown_cell(
source=f"""
<div>
<b>API Reference:</b>
{' | '.join(f'<a href="{imp["docs"]}">{imp["imported"]}</a>' for imp in imports)}
</div>
"""
)
)
else:
cells.append(cell)
nb.cells = cells
return nb, resources
def update_markdown_with_imports(markdown: str) -> str:
"""Update markdown to include API reference links for imports in Python code blocks.
This function scans the markdown content for Python code blocks, extracts any imports, and appends links to their API documentation.
Args:
markdown: The markdown content to process.
Returns:
Updated markdown with API reference links appended to Python code blocks.
Example:
Given a markdown with a Python code block:
```python
from langchain.nlp import TextGenerator
```
This function will append an API reference link to the `TextGenerator` class from the `langchain.nlp` module if it's recognized.
"""
code_block_pattern = re.compile(
r'(?P<indent>[ \t]*)```(?P<language>python|py)\n(?P<code>.*?)\n(?P=indent)```', re.DOTALL
)
def replace_code_block(match: re.Match) -> str:
"""Replace the matched code block with additional API reference links if imports are found.
Args:
match (re.Match): The regex match object containing the code block.
Returns:
str: The modified code block with API reference links appended if applicable.
"""
indent = match.group('indent')
code_block = match.group('code')
language = match.group('language') # Preserve the language from the regex match
# Retrieve import information from the code block
imports = get_imports(code_block, "__unused__")
original_code_block = match.group(0)
# If no imports are found, return the original code block
if not imports:
return original_code_block
# Generate API reference links for each import
api_links = ' | '.join(
f'<a href="{imp["docs"]}">{imp["imported"]}</a>' for imp in imports
)
# Return the code block with appended API reference links
return f'{original_code_block}\n\n{indent}API Reference: {api_links}'
# Apply the replace_code_block function to all matches in the markdown
updated_markdown = code_block_pattern.sub(replace_code_block, markdown)
return updated_markdown
-3
View File
@@ -6,8 +6,6 @@ import nbformat
from nbconvert.exporters import MarkdownExporter
from nbconvert.preprocessors import Preprocessor
from generate_api_reference_links import ImportPreprocessor
class EscapePreprocessor(Preprocessor):
def preprocess_cell(self, cell, resources, cell_index):
@@ -107,7 +105,6 @@ exporter = MarkdownExporter(
preprocessors=[
EscapePreprocessor,
ExtractAttachmentsPreprocessor,
ImportPreprocessor,
],
template_name="mdoutput",
extra_template_basedirs=[
+83 -3
View File
@@ -1,13 +1,18 @@
import logging
import os
import re
from typing import Any, Dict
from mkdocs.structure.pages import Page
from mkdocs.structure.files import Files, File
from mkdocs.structure.pages import Page
from notebook_convert import convert_notebook
from generate_api_reference_links import update_markdown_with_imports
logger = logging.getLogger(__name__)
logging.basicConfig()
logger.setLevel(logging.INFO)
DISABLED = os.getenv("DISABLE_NOTEBOOK_CONVERT") in ("1", "true", "True")
class NotebookFile(File):
@@ -16,6 +21,8 @@ class NotebookFile(File):
def on_files(files: Files, **kwargs: Dict[str, Any]):
if DISABLED:
return files
new_files = Files([])
for file in files:
if file.src_path.endswith(".ipynb"):
@@ -31,10 +38,83 @@ def on_files(files: Files, **kwargs: Dict[str, Any]):
return new_files
def _highlight_code_blocks(markdown: str) -> str:
"""Find code blocks with highlight comments and add hl_lines attribute.
Args:
markdown: The markdown content to process.
Returns:
updated Markdown code with code blocks containing highlight comments
updated to use the hl_lines attribute.
"""
# Pattern to find code blocks with highlight comments and without
# existing hl_lines for Python and JavaScript
# Pattern to find code blocks with highlight comments, handling optional indentation
code_block_pattern = re.compile(
r"(?P<indent>[ \t]*)```(?P<language>py|python|js|javascript)(?!\s+hl_lines=)\n"
r"(?P<code>((?:.*\n)*?))" # Capture the code inside the block using named group
r"(?P=indent)```" # Match closing backticks with the same indentation
)
def replace_highlight_comments(match: re.Match) -> str:
indent = match.group("indent")
language = match.group("language")
code_block = match.group("code")
lines = code_block.split("\n")
highlighted_lines = []
# Skip initial empty lines
while lines and not lines[0].strip():
lines.pop(0)
lines_to_keep = []
comment_syntax = (
"# highlight-next-line"
if language in ["py", "python"]
else "// highlight-next-line"
)
for line in lines:
if comment_syntax in line:
count = len(lines_to_keep) + 1
highlighted_lines.append(str(count))
else:
lines_to_keep.append(line)
# Reconstruct the new code block
new_code_block = "\n".join(lines_to_keep)
if highlighted_lines:
return (
f'{indent}```{language} hl_lines="{" ".join(highlighted_lines)}"\n'
# The indent and terminating \n is already included in the code block
f'{new_code_block}'
f'{indent}```'
)
else:
return (
f"{indent}```{language}\n"
# The indent and terminating \n is already included in the code block
f"{new_code_block}"
f"{indent}```"
)
# Replace all code blocks in the markdown
markdown = code_block_pattern.sub(replace_highlight_comments, markdown)
return markdown
def on_page_markdown(markdown: str, page: Page, **kwargs: Dict[str, Any]):
if DISABLED:
return markdown
if page.file.src_path.endswith(".ipynb"):
logger.info("Processing Jupyter notebook: %s", page.file.src_path)
body = convert_notebook(page.file.abs_src_path)
return body
markdown = convert_notebook(page.file.abs_src_path)
# Append API reference links to code blocks
markdown = update_markdown_with_imports(markdown)
# Apply highlight comments to code blocks
markdown = _highlight_code_blocks(markdown)
return markdown
+16 -1
View File
@@ -43,7 +43,9 @@ NOTEBOOKS_NO_EXECUTION = [
"docs/docs/tutorials/lats/lats.ipynb", # issues only when running with VCR
"docs/docs/tutorials/rag/langgraph_crag.ipynb", # flakiness from tavily
"docs/docs/tutorials/rag/langgraph_adaptive_rag.ipynb", # Cannot create a consistent method resolution error from VCR
"docs/docs/how-tos/map-reduce.ipynb" # flakiness from structured output, only when running with VCR
"docs/docs/how-tos/map-reduce.ipynb", # flakiness from structured output, only when running with VCR
"docs/docs/tutorials/tot/tot.ipynb",
"docs/docs/how-tos/visualization.ipynb"
]
@@ -86,6 +88,7 @@ def add_vcr_to_notebook(
) -> nbformat.NotebookNode:
"""Inject `with vcr.cassette` into each code cell of the notebook."""
uses_langsmith = False
# Inject VCR context manager into each code cell
for idx, cell in enumerate(notebook.cells):
if cell.cell_type != "code":
@@ -120,6 +123,9 @@ def add_vcr_to_notebook(
f" {line}" for line in lines
)
if any("hub.pull" in line or "from langsmith import" in line for line in lines):
uses_langsmith = True
# Add import statement
vcr_import_lines = [
"import nest_asyncio",
@@ -152,6 +158,15 @@ def add_vcr_to_notebook(
"custom_vcr.register_serializer('advanced_compressed', AdvancedCompressedSerializer())",
"custom_vcr.serializer = 'advanced_compressed'",
]
if uses_langsmith:
vcr_import_lines.extend(
# patch urllib3 to handle vcr errors, see more here:
# https://github.com/langchain-ai/langsmith-sdk/blob/main/python/langsmith/_internal/_patch.py
"import sys",
f"sys.path.insert(0, '{os.path.join(DOCS_PATH, '_scripts')}')",
"import _patch as patch_urllib3",
"patch_urllib3.patch_urllib3()",
)
import_cell = nbformat.v4.new_code_cell(source="\n".join(vcr_import_lines))
import_cell.pop("id", None)
notebook.cells.insert(0, import_cell)
@@ -0,0 +1 @@
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
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
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
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
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
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
eNrtVg1sE9cdD0sKhLU0asegK2g3dwRWcokdOzgOY8JxnIaPfDRxVZIA3svds33x+e64e05jg1UBabMsG+3BUEVBlILjtF5KElJgqGQhqrI2SVeh0qoLqIEw1qwr5aNfUjWx7N3ZzjeEdVSatFiy9O69/8fv/b/eb3tjFRQlhudmNDEcgiKgEP6Qdm1vFOFmL5RQTcgDkYung0WFJbbDXpHpW+xCSJCy0tKAwKQCDrlEXmCoVIr3pFXp0jxQkoATSsEKnvad+15wi8YDqu2Id0NO0mQROm26IYXQxKTwTvkWjcizEK80XgmKGnxK8RgKh5Stp1wAEUCEhMdHOEAVLzIIEgJEkiawUbHD05BV5CgWeGlI6skM0gUYt5dkAcLoFWuI59moIw54Io4EGh/bYwbtqkEsSkOJEhlBCYEiZqZpArkgwTISInjHWACpigLDCV5klygX9ACssUUj4GBAETHq1fCnYlhZjLO8bjKLBOIJCaKIYQQ9EU3kE1TMEhIZzqkJKDeKbgFRBD5NQNlSssWIkFbuGfG6cZQgX1EJKaRKjgQBRw7eQRByVLH/Lg6BqbAolqdEogZtbMQYBwEkN8Rp4r81jo2BRhcENG6D/rikoIuXkNw6obSbAUVBAZGQo3ga50F+zelnhBSChg6l0sK4ZDmo9o4cdkMokIBlqmAooiW3AEFgGQoo52mVEs81RUucVNBMPA4rnUDiBuGQfMIcw5FW5MOdyBHaVEN6qralmpQQYDgWtxKudgwpJKjnb4w+EADlxnbIaJfLoYjykdEyvCQ35AOqsGSMSSBSLrkBiJ7lhrbR+6KXQ4wHyo2Woonuoocj7vSpOl2qqXWMYcnHUXKDA7ASbB0O8rBKOF2brie1y0mt7sQY0xCJPpLisQf5Ze2RWABZyDmRSz683Lj8FRFKAh5ecEcIqyGvtD2IkwXfebsxOm4OFa4dSfX8YA5OnNyeKzIpeCwRawBHYNcZhC4jy2DI0pmIx/JtTZaoG9ukeWq1iYCTHDhX1lhdNFIuL4crMmyZtCL6NCM3FrF/lvEwiIzOWpxH5VMOGrRabV/ybSVFXOEMp3gM6k0m0xR2cWQgkl9X7ofjSuq0tugtjWWT+1EbiYyM7SiqkIIK43p0SvkRbDGd5DvQuQVCU1nfksm0eS+aALEhU/W2bGr5EYhRnSV3onNriMRk6uPCF3H009tIjg5cRJq4rfQt8YSjmScZWj6F13atzlLm8JvLypDf6qdAZZ6xWLQV+6sOVzFADutSdYST550sbLbkkhaA5ydZoraQ3JhTWmDOX21pWk8W8xU8riUbwDXH8RwMlUARd60cpljeS+M5KMIQVi82l8qvZzocNGUyUQbaAYzpRpq0PlncEmum4WYJKkNUZQrbQpH3rWtG7o/rZ8epv3j8Hxqin+8ueFOb9Ky968Xvz5/V8MkD3cZH/avyftXwt7hseXOobt6Hc28syMjxPXX54Jyiwrat+qGU+L8/kLBw6xPBthUXzlg6OvecW/nkN18fP9ccfmuRcPCPXeuf6e+ds80yuG5d3rVjS5jEzIUb6mYmLH3EUtf989LkY+/n5x01lL/zXkr83J7S7LXEptrdyQO6qsLc039+RteUe/Zo/z2hSlv+l1nHX3jpyNmaJuvnV9qdne5yXVh+6LNXM29UXPrnL+cxzjRwuWP3fr31+uabn3y8oLb6qz3vDnKLDrz/9ad/OdW059TVa8bTnT2WKys+fG7O8fsSKha8ZZx7YEM5WtreNjS47vRfH1v1+6Sa+/4kzD57f9f1w48/7z7jT6r3vWte/QT1ctbvNt+89LTVf2lRz+f7T656Wt+7dWHHDCVY8XFFbUd39eP1XSJwCYbvjsApBCCmCSQJv+4YzFh1bBvBatXQ6iUsS7ggKxA+3kvgCqXcykocxwgqfIRXwoU0TFjG0grC4eXUoazShRgXUH2oeIZ3MGm04+soUrcnKeoUi5ELho4pe3HLFZgZo57P9pZQNn+1uyybAjm+IitmrmOuPjFo5eNw4Eb3suMio8HPkIT5B++UYiRXwWu/IwQKUskORZEXsbT6EmNQ04R6mlBPE+ppQq0S6qBOe5cZdbr2/4JRm/7XGXW6dppR3xVGvcadA9xrrABw5kpYZE7PKcn2Zni+W0YNjBU0VfGfMerEEUZNP/f42njzvTVDPbsGV5488MPeX/Rss7b+wG7d2bLvuLO5+2T3rxu+7Oidd09XY+IX+TM/Tghsj5v1UdVXV2542k3vHQqUHetdfOT84AeBZfdf7Nz5SP7PirvIbbUpb9IHK5ufZS8OEHUzZyfNSqkLf1q56bOWCy+EX923r/Qjw8WfzHm41Vxtuol+FCi4MD9RqF3TnbjivNwbhNprO4uPnr9Ye7n/7d/S/9Durs9ctH/Drtc2dX7zwY4Xf7Mx2QZmr9qftWzvwMNL2+dfykv6w707asWHHiw488X6Jn3fg8tqfO3Xk3RXX6nfe8Y/cJU/9K+BvdqbzuIrJ/RvrHypIy5CihfXN+9xYlL8b/H+B4Y=
@@ -1 +1 @@
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
@@ -1 +0,0 @@
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
@@ -1 +0,0 @@
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
+12 -12
View File
@@ -5,17 +5,17 @@ LangGraph Cloud is available within <a href="https://www.langchain.com/langsmith
## Prerequisites
1. LangGraph Cloud applications are deployed from GitHub repositories. Configure and upload a LangGraph Cloud application to a GitHub repository in order to deploy it to LangGraph Cloud.
1. [Verify that the LangGraph API runs locally](test_locally.md). If the API does not build and run successfully (i.e. `langgraph up`), deploying to LangGraph Cloud will fail as well.
1. [Verify that the LangGraph API runs locally](test_locally.md). If the API does not run successfully (i.e. `langgraph dev`), deploying to LangGraph Cloud will fail as well.
## Create New Deployment
Starting from the <a href="https://smith.langchain.com/" target="_blank">LangSmith UI</a>...
1. In the left-hand navigation panel, select `LangGraph Cloud`. The `LangGraph Cloud` view contains a list of existing LangGraph Cloud deployments.
1. In the left-hand navigation panel, select `LangGraph Platform`. The `LangGraph Platform` view contains a list of existing LangGraph Cloud deployments.
1. In the top-right corner, select `+ New Deployment` to create a new deployment.
1. In the `Create New Deployment` panel, fill out the required fields.
1. `Deployment details`
1. Select `Import from GitHub` and follow the GitHub OAuth workflow to install and authorize LangChain's `hosted-langserve` GitHub app to access the selected repositories. After installation is complete, return to the `Create New Deployment` panel and select the GitHub repository to deploy from the dropdown menu.
1. Select `Import from GitHub` and follow the GitHub OAuth workflow to install and authorize LangChain's `hosted-langserve` GitHub app to access the selected repositories. After installation is complete, return to the `Create New Deployment` panel and select the GitHub repository to deploy from the dropdown menu. **Note**: The GitHub user installing LangChain's `hosted-langserve` GitHub app must be an [owner](https://docs.github.com/en/organizations/managing-peoples-access-to-your-organization-with-roles/roles-in-an-organization#organization-owners) of the organization or account.
1. Specify a name for the deployment.
1. Specify the desired `Git Branch`. A deployment is linked to a branch. When a new revision is created, code for the linked branch will be deployed. The branch can be updated later in the [Deployment Settings](#deployment-settings).
1. Specify the full path to the [LangGraph API config file](../reference/cli.md#configuration-file) including the file name. For example, if the file `langgraph.json` is in the root of the repository, simply specify `langgraph.json`.
@@ -38,7 +38,7 @@ When [creating a new deployment](#create-new-deployment), a new revision is crea
Starting from the <a href="https://smith.langchain.com/" target="_blank">LangSmith UI</a>...
1. In the left-hand navigation panel, select `LangGraph Cloud`. The `LangGraph Cloud` view contains a list of existing LangGraph Cloud deployments.
1. In the left-hand navigation panel, select `LangGraph Platform`. The `LangGraph Platform` view contains a list of existing LangGraph Cloud deployments.
1. Select an existing deployment to create a new revision for.
1. In the `Deployment` view, in the top-right corner, select `+ New Revision`.
1. In the `New Revision` modal, fill out the required fields.
@@ -52,15 +52,15 @@ Starting from the <a href="https://smith.langchain.com/" target="_blank">LangSmi
1. Update the value of existing secrets or environment variables.
1. Select `Submit`. After a few seconds, the `New Revision` modal will close and the new revision will be queued for deployment.
## View Build and Deployment Logs
## View Build and Server Logs
Build and deployment logs are available for each revision.
Build and server logs are available for each revision.
Starting from the `LangGraph Cloud` view...
Starting from the `LangGraph Platform` view...
1. Select the desired revision from the `Revisions` table. A panel slides open from the right-hand side and the `Build` tab is selected by default, which displays build logs for the revision.
1. In the panel, select the `Deploy` tab to view deployment logs for the revision.
1. Within the `Deploy` tab, adjust the date/time range picker as needed. By default, the date/time range picker is set to the `Last 15 minutes`.
1. In the panel, select the `Server` tab to view server logs for the revision. Server logs are only available after a revision has been deployed.
1. Within the `Server` tab, adjust the date/time range picker as needed. By default, the date/time range picker is set to the `Last 7 days`.
## Interrupt Revision
@@ -69,7 +69,7 @@ Interrupting a revision will stop deployment of the revision.
!!! warning "Undefined Behavior"
Interrupted revisions have undefined behavior. This is only useful if you need to deploy a new revision and you already have a revision "stuck" in progress. In the future, this feature may be removed.
Starting from the `LangGraph Cloud` view...
Starting from the `LangGraph Platform` view...
1. Select the menu icon (three dots) on the right-hand side of the row for the desired revision from the `Revisions` table.
1. Select `Interrupt` from the menu.
@@ -79,13 +79,13 @@ Starting from the `LangGraph Cloud` view...
Starting from the <a href="https://smith.langchain.com/" target="_blank">LangSmith UI</a>...
1. In the left-hand navigation panel, select `LangGraph Cloud`. The `LangGraph Cloud` view contains a list of existing LangGraph Cloud deployments.
1. In the left-hand navigation panel, select `LangGraph Platform`. The `LangGraph Platform` view contains a list of existing LangGraph Cloud deployments.
1. Select the menu icon (three dots) on the right-hand side of the row for the desired deployment and select `Delete`.
1. A `Confirmation` modal will appear. Select `Delete`.
## Deployment Settings
Starting from the `LangGraph Cloud` view...
Starting from the `LangGraph Platform` view...
1. In the top-right corner, select the gear icon (`Deployment Settings`).
1. Update the `Git Branch` to the desired branch.
Binary file not shown.

After

Width:  |  Height:  |  Size: 736 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 304 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 266 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 376 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 400 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 461 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 642 KiB

+24 -20
View File
@@ -6,17 +6,11 @@ Testing locally ensures that there are no errors or conflicts with Python depend
## Setup
Install the proper packages:
Install the LangGraph CLI package:
=== "pip"
```bash
pip install -U langgraph-cli
```
=== "Homebrew (macOS only)"
```bash
brew install langgraph-cli
```
```bash
pip install -U "langgraph-cli[inmem]"
```
Ensure you have an API key, which you can create from the [LangSmith UI](https://smith.langchain.com) (Settings > API Keys). This is required to authenticate that you have LangGraph Cloud access. After you have saved the key to a safe place, place the following line in your `.env` file:
@@ -29,16 +23,26 @@ LANGSMITH_API_KEY = *********
Once you have installed the CLI, you can run the following command to start the API server for local testing:
```shell
langgraph up
langgraph dev
```
This will start up the LangGraph API server locally. If this runs successfully, you should see something like:
```shell
Ready!
- API: http://localhost:8123
2024-06-26 19:20:41,056:INFO:uvicorn.access 127.0.0.1:44138 - "GET /ok HTTP/1.1" 200
```
> Ready!
>
> - API: [http://localhost:2024](http://localhost:2024/)
>
> - Docs: http://localhost:2024/docs
>
> - LangGraph Studio Web UI: https://smith.langchain.com/studio/?baseUrl=http://127.0.0.1:2024
!!! note "In-Memory Mode"
The `langgraph dev` command starts LangGraph Server in an in-memory mode. This mode is suitable for development and testing purposes. For production use, you should deploy LangGraph Server with access to a persistent storage backend.
If you want to test your application with a persistent storage backend, you can use the `langgraph up` command instead of `langgraph dev`. You will
need to have `docker` installed on your machine to use this command.
### Interact with the server
@@ -53,7 +57,7 @@ You can either initialize by passing authentication or by setting an environment
```python
from langgraph_sdk import get_client
# only pass the url argument to get_client() if you changed the default port when calling langgraph up
# only pass the url argument to get_client() if you changed the default port when calling langgraph dev
client = get_client(url=<DEPLOYMENT_URL>,api_key=<LANGSMITH_API_KEY>)
# Using the graph deployed with the name "agent"
assistant_id = "agent"
@@ -65,7 +69,7 @@ You can either initialize by passing authentication or by setting an environment
```js
import { Client } from "@langchain/langgraph-sdk";
// only set the apiUrl if you changed the default port when calling langgraph up
// only set the apiUrl if you changed the default port when calling langgraph dev
const client = new Client({ apiUrl: <DEPLOYMENT_URL>, apiKey: <LANGSMITH_API_KEY> });
// Using the graph deployed with the name "agent"
const assistantId = "agent";
@@ -91,7 +95,7 @@ If you have a `LANGSMITH_API_KEY` set in your environment, you do not need to ex
```python
from langgraph_sdk import get_client
# only pass the url argument to get_client() if you changed the default port when calling langgraph up
# only pass the url argument to get_client() if you changed the default port when calling langgraph dev
client = get_client()
# Using the graph deployed with the name "agent"
assistant_id = "agent"
@@ -103,7 +107,7 @@ If you have a `LANGSMITH_API_KEY` set in your environment, you do not need to ex
```js
import { Client } from "@langchain/langgraph-sdk";
// only set the apiUrl if you changed the default port when calling langgraph up
// only set the apiUrl if you changed the default port when calling langgraph dev
const client = new Client();
// Using the graph deployed with the name "agent"
const assistantId = "agent";
@@ -0,0 +1,17 @@
# Adding nodes as dataset examples in Studio
In LangGraph Studio you can create dataset examples from the thread history in the right-hand pane. This can be especially useful when you want to evaluate intermediate steps of the agent.
1. Click on the `Add to Dataset` button to enter the dataset mode.
1. Select nodes which you want to add to dataset.
1. Select the target dataset to create the example in.
You can edit the example payload before sending it to the dataset, which is useful if you need to make changes to conform the example to the dataset schema.
Finally, you can customise the target dataset by clicking on the `Settings` button.
See [Evaluating intermediate steps](https://docs.smith.langchain.com/evaluation/how_to_guides/langgraph#evaluating-intermediate-steps) for more details on how to evaluate intermediate steps.
<video controls allowfullscreen="true" poster="../img/studio_datasets.jpg">
<source src="https://langgraph-docs-assets.pages.dev/studio_datasets.mp4" type="video/mp4">
</video>
Binary file not shown.

After

Width:  |  Height:  |  Size: 170 KiB

@@ -83,7 +83,7 @@ Now, let's import our required packages and instantiate our client, assistant, a
## Create runs
Now we can start our two runs and join the second on euntil it has completed:
Now we can start our two runs and join the second one until it has completed:
=== "Python"
@@ -7,17 +7,21 @@
Make sure you have setup your app correctly, by creating a compiled graph, a `.env` file with any environment variables, and a `langgraph.json` config file that points to your environment file and compiled graph. See [here](https://langchain-ai.github.io/langgraph/cloud/deployment/setup/) for more detailed instructions.
After you have your app setup, head into the directory with your `langgraph.json` file and call `langgraph up -c langgraph.json --watch` to start the API server in watch mode which means it will restart on code changes, which is ideal for local testing. If the API server start correctly you should see logs that look something like this:
After you have your app setup, head into the directory with your `langgraph.json` file and call `langgraph dev` to start the API server in watch mode which means it will restart on code changes, which is ideal for local testing. If the API server start correctly you should see logs that look something like this:
Ready!
- API: http://localhost:8123
2024-06-26 19:20:41,056:INFO:uvicorn.access 127.0.0.1:44138 - "GET /ok HTTP/1.1" 200
> Ready!
>
> - API: [http://localhost:2024](http://localhost:2024/)
>
> - Docs: http://localhost:2024/docs
>
> - LangGraph Studio Web UI: https://smith.langchain.com/studio/?baseUrl=http://127.0.0.1:2024
Read this [reference](https://langchain-ai.github.io/langgraph/cloud/reference/cli/#up) to learn about all the options for starting the API server.
## Access Studio
Once you have successfully started the API server, you can access the studio by going to the following URL: `https://smith.langchain.com/studio/?baseUrl=http://127.0.0.1:8123` (see warning above if using Safari).
Once you have successfully started the API server, you can access the studio by going to the following URL: `https://smith.langchain.com/studio/?baseUrl=http://127.0.0.1:2024` (see warning above if using Safari).
If everything is working correctly you should see the studio show up looking something like this (with your graph diagram on the left hand side):
+200 -390
View File
@@ -1,462 +1,272 @@
# LangGraph Cloud Quick Start
# Quickstart: Deploy on LangGraph Cloud
In this tutorial you will build and deploy a simple chatbot agent that can look things up on the internet. You will be using [LangGraph Cloud](../concepts/langgraph_cloud.md), [LangGraph Studio](../concepts/langgraph_studio.md) to visualize and test it out, and [LangGraph SDK](./reference/sdk/python_sdk_ref.md) to interact with the deployed agent.
!!! note "Prerequisites"
If you want to learn how to build an agent like this from scratch, take a look at the [LangGraph Quick Start tutorial](../tutorials/introduction.ipynb).
Before you begin, ensure you have the following:
## Set up requirements
- [GitHub account](https://github.com/)
- [LangSmith account](https://smith.langchain.com/)
This tutorial will use:
## Create a repository on GitHub
- Anthropic for the LLM - sign up and get an API key [here](https://console.anthropic.com/).
- Tavily for the search engine - sign up and get an API key [here](https://app.tavily.com/).
- LangSmith for hosting - sign up and get an API key [here](https://smith.langchain.com/).
To deploy a LangGraph application to **LangGraph Cloud**, your application code must reside in a GitHub repository. Both public and private repositories are supported.
## Create and configure your app
You can deploy any [LangGraph Application](../concepts/application_structure.md) to LangGraph Cloud.
First, let's set create all of the necessary files for our LangGraph application.
For this guide, we'll use the pre-built Python [**ReAct Agent**](https://github.com/langchain-ai/react-agent) template.
1. __Create application directory and files__
??? note "Get Required API Keys for the ReAct Agent template"
Create a new application `my-app` with the following file structure:
This **ReAct Agent** application requires an API key from [Anthropic](https://console.anthropic.com/) and [Tavily](https://app.tavily.com/). You can get these API keys by signing up on their respective websites.
```shell
mkdir my-app
```
**Alternative**: If you'd prefer a scaffold application that doesn't require API keys, use the [**New LangGraph Project**](https://github.com/langchain-ai/new-langgraph-project) template instead of the **ReAct Agent** template.
=== "Python"
my-app/
|-- agent.py # code for your LangGraph agent
|-- requirements.txt # Python packages required for your graph
|-- langgraph.json # configuration file for LangGraph
|-- .env # environment files with API keys
=== "Javascript"
my-app/
|-- agent.ts # code for your LangGraph agent
|-- package.json # Javascript packages required for your graph
|-- langgraph.json # configuration file for LangGraph
|-- .env # environment files with API keys
1. __Define your graph__
=== "Python"
The `agent.py` file should contain code with your graph.
=== "Javascript"
The `agent.ts` file should contain code with your graph.
The following code example is a simple chatbot agent (similar to the one in the [previous tutorial](../tutorials/introduction.ipynb)). Specifically, it uses [create_react_agent][langgraph.prebuilt.chat_agent_executor.create_react_agent], a prebuilt [ReAct](../concepts/agentic_concepts.md#react-implementation)-style agent.
The `agent` file needs to have a variable with a [CompiledGraph][langgraph.graph.graph.CompiledGraph] (in this case the `graph` variable).
=== "Python"
```python
# agent.py
from langchain_anthropic import ChatAnthropic
from langchain_community.tools.tavily_search import TavilySearchResults
from langgraph.prebuilt import create_react_agent
model = ChatAnthropic(model="claude-3-5-sonnet-20240620")
tools = [TavilySearchResults(max_results=2)]
# compiled graph
graph = create_react_agent(model, tools)
```
=== "Javascript"
```ts
// agent.ts
import { ChatAnthropic } from "@langchain/anthropic";
import { TavilySearchResults } from "@langchain/community/tools/tavily_search";
import { createReactAgent } from "@langchain/langgraph/prebuilt";
const model = new ChatAnthropic({
model: "claude-3-5-sonnet-20240620",
});
const tools = [
new TavilySearchResults({ maxResults: 3, }),
];
// compiled graph
export const graph = createReactAgent({ llm: model, tools });
```
1. __Specify dependencies__
=== "Python"
You should add dependencies for your graph(s) to `requirements.txt`.
=== "Javascript"
You should add dependencies for your graph(s) to `package.json`.
In this case we only require four packages for our graph to run:
=== "Python"
```python
langgraph
langchain_anthropic
tavily-python
langchain_community
```
=== "Javascript"
```js
{
"name": "my-app",
"packageManager": "yarn@1.22.22",
"dependencies": {
"@langchain/community": "^0.3.11",
"@langchain/core": "^0.3.16",
"@langchain/langgraph": "0.2.18",
"@langchain/anthropic": "^0.3.7"
}
}
```
1. __Create LangGraph configuration file__
The [`langgraph.json`][langgraph.json] file is a configuration file that describes what graph(s) you are going to deploy. In this case we only have one graph: the compiled `graph` object from `agent.py` / `agent.ts`.
=== "Python"
```json
{
"dependencies": ["."],
"graphs": {
"agent": "./agent.py:graph"
},
"env": ".env"
}
```
=== "Javascript"
```json
{
"node_version": "20",
"dockerfile_lines": [],
"dependencies": ["."],
"graphs": {
"agent": "./src/agent.ts:graph"
},
"env": ".env"
}
```
Learn more about the LangGraph CLI configuration file [here](./reference/cli.md#configuration-file).
1. __Specify environment variables__
The `.env` file should have any environment variables needed to run your graph. This will only be used for local testing, so if you are not testing locally you can skip this step.
!!! warning
The `.env` file should NOT be included with the rest of source code in your Github repository. When creating a deployment using LangGraph Cloud, you will be able to specify the environment variables manually.
For this graph, we need two environment variables:
```shell
ANTHROPIC_API_KEY=...
TAVILY_API_KEY=...
```
!!! tip
Learn more about different application structure options [here](../how-tos/index.md#application-structure).
Now that we have set everything up on our local file system, we are ready to test our graph locally.
## Test the app locally
To test the LangGraph app before deploying it using LangGraph Cloud, you can start the [LangGraph server](../concepts/langgraph_server.md) locally or use [LangGraph Studio](../concepts/langgraph_studio.md).
## Using local server
You can test your app by running [LangGraph server](../concepts/langgraph_server.md) locally. This is useful to make sure you have configured our [CLI configuration file][langgraph.json] correctly and can interact with your graph.
To run the server locally, you need to first install the LangGraph CLI:
```shell
pip install langgraph-cli
```
You can then test our API server locally. In order to run the server locally, you will need to add your `LANGSMITH_API_KEY` to the `.env` file.
```shell
langgraph up
```
This will start up the LangGraph API server locally. If this runs successfully, you should see something like:
```shell
Ready!
- API: http://localhost:8123
```
First, let's verify that the server is running correctly by calling `/ok` endpoint:
```shell
curl --request GET --url http://localhost:8123/ok
```
Output:
```
{"ok": "true"}
```
Now we're ready to test the app with the real inputs!
```shell
curl --request POST \
--url http://localhost:8123/runs/stream \
--header 'Content-Type: application/json' \
--data '{
"assistant_id": "agent",
"input": {
"messages": [
{
"role": "user",
"content": "What is the weather in NYC?"
}
]
},
"stream_mode": "updates"
}'
```
Output:
```
...
data: {
"agent": {
"messages": [
{
"content": "The search results from Tavily provide the current weather conditions in New York City, including temperature, wind speed, precipitation, humidity, and cloud cover. According to the results, as of 3:00pm on October 30th, 2024, it is overcast in NYC with a temperature of around 66°F (19°C), light winds from the southwest around 8 mph (13 km/h), and 66% humidity.\n\nSo in summary, the current weather in NYC is overcast with mild temperatures in the mid 60sF and light winds, based on the search results. Let me know if you need any other details!",
"type": "ai",
...
}
]
}
}
```
You can see that our agent responds with the up-to-date search results!
### Using LangGraph Studio Desktop
You can also test your app locally with [LangGraph Studio](../concepts/langgraph_studio.md). LangGraph Studio offers a new way to develop LLM applications by providing a specialized agent IDE that enables visualization, interaction, and debugging of complex agentic applications.
With visual graphs and the ability to edit state, you can better understand agent workflows and iterate faster. LangGraph Studio integrates with LangSmith allowing you to collaborate with teammates to debug failure modes.
LangGraph Studio is available as a [desktop app](https://studio.langchain.com/) for MacOS users. Once you have installed the app, you can select `my-app` directory, which will automatically start the server locally and load the graph in the UI.
To interact with your chatbot agent in LangGraph Studio, you can add a new message in the `Input` section and press `Submit`.
![LangGraph Studio Desktop](./deployment/img/quick_start_studio.png)
1. Go to the [ReAct Agent](https://github.com/langchain-ai/react-agent) repository.
2. Fork the repository to your GitHub account by clicking the `Fork` button in the top right corner.
## Deploy to LangGraph Cloud
Once you've tested your graph locally and verified that it works as expected, you can deploy it to the LangGraph Cloud.
??? note "1. Log in to [LangSmith](https://smith.langchain.com/)"
First, you'll need to turn the `my-app` directory into a GitHub repo and [push it to GitHub](https://docs.github.com/en/migrations/importing-source-code/using-the-command-line-to-import-source-code/adding-locally-hosted-code-to-github).
<figure markdown="1">
[![Login to LangSmith](deployment/img/01_login.png){: style="max-height:300px"}](deployment/img/01_login.png)
<figcaption>
Go to [LangSmith](https://smith.langchain.com/) and log in. If you don't have an account, you can sign up for free.
</figcaption>
</figure>
Once you have created your GitHub repository with a Python file containing your compiled graph as well as a `langgraph.json` with the configuration, you can head over to [LangSmith](https://smith.langchain.com/) and click on the graph icon (`LangGraph Cloud`) on the bottom of the left navbar. This will open the LangGraph deployments page. On this page, click the `+ New Deployment` button in the top right corner.
![Langsmith Workflow](./deployment/img/cloud_deployment.png)
??? note "2. Click on <em>LangGraph Platform</em> (the left sidebar)"
**_If you have not deployed to LangGraph Cloud before:_** there will be a button that shows up saying `Import from GitHub`. Youll need to follow that flow to connect LangGraph Cloud to GitHub.
<figure markdown="1">
[![Login to LangSmith](deployment/img/02_langgraph_platform.png){: style="max-height:300px"}](deployment/img/02_langgraph_platform.png)
<figcaption>
Select **LangGraph Platform** from the left sidebar.
</figcaption>
</figure>
**_Once you have set up your GitHub connection:_** the new deployment page will look as follows:
??? note "3. Click on + New Deployment (top right corner)"
![Deployment before being filled out](./deployment/img/deployment_page.png)
<figure markdown="1">
[![Login to LangSmith](deployment/img/03_deployments_page.png){: style="max-height:300px"}](deployment/img/03_deployments_page.png)
<figcaption>
Click on **+ New Deployment** to create a new deployment. This button is located in the top right corner.
It'll open a new modal where you can fill out the required fields.
</figcaption>
</figure>
To deploy your application, you should do the following:
??? note "4. Click on Import from GitHub (first time users)"
1. Select your GitHub username or organization from the selector
1. Search for your repo to deploy in the search bar and select it
1. Choose a name for your deployment
1. In the `Git Branch` field, you can specify either the branch for the code you want to deploy, or the exact commit SHA.
1. In the `LangGraph API config file` field, enter the path to your `langgraph.json` file (which in this case is just `langgraph.json`)
1. If your application needs environment variables, add those in the `Environment Variables` section. They will be propagated to the underlying server so your code can access them. In this case, we will need `ANTHROPIC_API_KEY` and `TAVILY_API_KEY`.
<figure markdown="1">
[![image](deployment/img/04_create_new_deployment.png)](deployment/img/04_create_new_deployment.png)
<figcaption>
Click on **Import from GitHub** and follow the instructions to connect your GitHub account. This step is needed for **first-time users** or to add private repositories that haven't been connected before.</figcaption>
</figure>
Hit `Submit` and your application will start deploying!
??? note "5. Select the repository, configure ENV vars etc"
After your deployment is complete, your deployments page should look as follows:
<figure markdown="1">
[![image](deployment/img/05_configure_deployment.png){: style="max-height:300px"}](deployment/img/05_configure_deployment.png)
<figcaption>
Select the <strong>repository</strong>, add env variables and secrets, and set other configuration options.
</figcaption>
</figure>
![Deployed page](./deployment/img/deployed_page.png)
- **Repository**: Select the repository you forked earlier (or any other repository you want to deploy).
- Set the secrets and environment variables required by your application. For the **ReAct Agent** template, you need to set the following secrets:
- **ANTHROPIC_API_KEY**: Get an API key from [Anthropic](https://console.anthropic.com/).
- **TAVILY_API_KEY**: Get an API key on the [Tavily website](https://app.tavily.com/).
## Interact with your deployment
??? note "6. Click Submit to Deploy!"
### Using LangGraph Studio (Cloud)
On the deployment page for your application,, you should see a button in the top right corner that says `LangGraph Studio`. Clicking on this button will take you to the web version of LangGraph Studio. This is the same UI that you interacted with when [testing the app locally](#using-langgraph-studio-recommended), but instead of using a local LangGraph server, it uses the one from your LangGraph Cloud deployment.
<figure markdown="1">
[![image](deployment/img/05_configure_deployment.png){: style="max-height:300px"}](deployment/img/05_configure_deployment.png)
<figcaption>
Please note that this step may ~15 minutes to complete. You can check the status of your deployment in the **Deployments** view.
Click the <strong>Submit</strong> button at the top right corner to deploy your application.
</figcaption>
</figure>
![Studio UI once being run](./deployment/img/graph_run.png)
### Using LangGraph SDK
## Lagraph Studio Web UI
You can also interact with your deployed LangGraph application programmatically, using [LangGraph SDK](./reference/sdk/python_sdk_ref.md).
Once your application is deployed, you can test it in **LangGraph Studio**.
First, make sure you have the SDK installed:
??? note "1. Click on an existing deployment"
=== "Python"
<figure markdown="1">
[![image](deployment/img/07_deployments_page.png){: style="max-height:300px"}](deployment/img/07_deployments_page.png)
<figcaption>
Click on the deployment you just created to view more details.
</figcaption>
</figure>
```shell
pip install langgraph_sdk
```
??? note "2. Click on LangGraph Studio"
=== "Javascript"
<figure markdown="1">
[![image](deployment/img/08_deployment_view.png){: style="max-height:300px"}](deployment/img/08_deployment_view.png)
<figcaption>
Click on the <strong>LangGraph Studio</strong> button to open LangGraph Studio.
</figcaption>
</figure>
```shell
yarn add @langchain/langgraph-sdk
```
<figure markdown="1">
[![image](deployment/img/09_langgraph_studio.png){: style="max-height:400px"}](deployment/img/09_langgraph_studio.png)
<figcaption>
Sample graph run in LangGraph Studio.
</figcaption>
</figure>
Before using, you need to get the URL of your LangGraph deployment. You can find this in the `Deployment` view. Click the URL to copy it to the clipboard.
## Test the API
You also need to make sure you have set up your API key properly so you can authenticate with LangGraph Cloud.
!!! note
The API calls below are for the **ReAct Agent** template. If you're deploying a different application, you may need to adjust the API calls accordingly.
Before using, you need to get the `URL` of your LangGraph deployment. You can find this in the `Deployment` view. Click the `URL` to copy it to the clipboard.
You also need to make sure you have set up your API key properly, so you can authenticate with LangGraph Cloud.
```shell
export LANGSMITH_API_KEY=...
```
The first thing to do when using the SDK is to setup our client, access our assistant, and create a thread to execute a run on:
=== "Python SDK (Async)"
=== "Python"
**Install the LangGraph Python SDK**
```python
from langgraph_sdk import get_client
```shell
pip install langgraph-sdk
```
client = get_client(url=<DEPLOYMENT_URL>)
# get default assistant
assistants = await client.assistants.search(metadata={"created_by": "system"})
assistant = assistants[0]
# create thread
thread = await client.threads.create()
print(thread)
```
=== "Javascript"
```js
import { Client } from "@langchain/langgraph-sdk";
const client = new Client({ apiUrl: <DEPLOYMENT_URL> });
// get default assistant
const assistants = await client.assistants.search({ metadata: {"created_by": "system"} })
const assistant = assistants[0];
// create thread
const thread = await client.threads.create();
console.log(thread)
```
=== "CURL"
```bash
curl --request POST \
--url <DEPLOYMENT_URL>/assistants/search \
--header 'Content-Type: application/json' \
--data '{
"limit": 10,
"offset": 0,
"metadata": {"created_by": "system"}
}' &&
curl --request POST \
--url <DEPLOYMENT_URL>/threads \
--header 'Content-Type: application/json' \
--data '{}'
```
We can then execute a run on the thread:
=== "Python"
**Send a message to the assistant (threadless run)**
```python
input = {
"messages": [{"role": "user", "content": "What is the weather in NYC?"}]
}
from langgraph_sdk import get_client
client = get_client(url="your-deployment-url", api_key="your-langsmith-api-key")
async for chunk in client.runs.stream(
thread["thread_id"],
assistant["assistant_id"],
input=input,
None, # Threadless run
"agent", # Name of assistant. Defined in langgraph.json.
input={
"messages": [{
"role": "human",
"content": "What is LangGraph?",
}],
},
stream_mode="updates",
):
if chunk.data:
print(chunk.data)
print(f"Receiving new event of type: {chunk.event}...")
print(chunk.data)
print("\n\n")
```
=== "Javascript"
=== "Python SDK (Sync)"
**Install the LangGraph Python SDK**
```shell
pip install langgraph-sdk
```
**Send a message to the assistant (threadless run)**
```python
from langgraph_sdk import get_sync_client
client = get_sync_client(url="your-deployment-url", api_key="your-langsmith-api-key")
for chunk in client.runs.stream(
None, # Threadless run
"agent", # Name of assistant. Defined in langgraph.json.
input={
"messages": [{
"role": "human",
"content": "What is LangGraph?",
}],
},
stream_mode="updates",
):
print(f"Receiving new event of type: {chunk.event}...")
print(chunk.data)
print("\n\n")
```
=== "Javascript SDK"
**Install the LangGraph JS SDK**
```shell
npm install @langchain/langgraph-sdk
```
**Send a message to the assistant (threadless run)**
```js
const input = { "messages": [{ "role": "user", "content": "What is the weather in NYC?" }] };
const { Client } = await import("@langchain/langgraph-sdk");
const client = new Client({ apiUrl: "your-deployment-url", apiKey: "your-langsmith-api-key" });
const streamResponse = client.runs.stream(
thread["thread_id"],
assistant["assistant_id"],
{
input,
streamMode: "updates"
}
null, // Threadless run
"agent", // Assistant ID
{
input: {
"messages": [
{ "role": "user", "content": "What is LangGraph?"}
]
},
streamMode: "messages",
}
);
for await (const chunk of streamResponse) {
if (chunk.data) {
console.log(chunk.data);
}
console.log(`Receiving new event of type: ${chunk.event}...`);
console.log(JSON.stringify(chunk.data));
console.log("\n\n");
}
```
=== "CURL"
=== "Rest API"
```bash
curl --request POST \
--url <DEPLOYMENT_URL>/threads/<THREAD_ID>/runs/stream \
--header 'Content-Type: application/json' \
--data '{
"assistant_id": <ASSISTANT_ID>,
"input": {
"messages": [
{
"role": "user",
"content": "What is the weather in NYC?"
}
]
},
"stream_mode": "updates"
}'
curl -s --request POST \
--url <DEPLOYMENT_URL> \
--header 'Content-Type: application/json' \
--data "{
\"assistant_id\": \"agent\",
\"input\": {
\"messages\": [
{
\"role\": \"human\",
\"content\": \"What is LangGraph?\"
}
]
},
\"stream_mode\": \"updates\"
}"
```
Output:
```
...
data: {
"agent": {
"messages": [
{
"content": "The search results from Tavily provide the current weather conditions in New York City, including temperature, wind speed, precipitation, humidity, and cloud cover. According to the results, as of 3:00pm on October 30th, 2024, it is overcast in NYC with a temperature of around 66°F (19°C), light winds from the southwest around 8 mph (13 km/h), and 66% humidity.\n\nSo in summary, the current weather in NYC is overcast with mild temperatures in the mid 60sF and light winds, based on the search results. Let me know if you need any other details!",
"type": "ai",
...
}
]
}
}
```
## Next steps
## Next Steps
Congratulations! If you've worked your way through this tutorial you are well on your way to becoming a LangGraph Cloud expert. Here are some other resources to check out to help you out on the path to expertise:
* [LangGraph How-to guides](../how-tos/index.md)
* [LangGraph Tutorials](../tutorials/index.md)
### LangGraph Framework
- **[LangGraph Tutorial](../tutorials/introduction.ipynb)**: Get started with LangGraph framework.
- **[LangGraph Concepts](../concepts/index.md)**: Learn the foundational concepts of LangGraph.
- **[LangGraph How-to Guides](../how-tos/index.md)**: Guides for common tasks with LangGraph.
### 📚 Learn More about LangGraph Platform
Expand your knowledge with these resources:
- **[LangGraph Platform Concepts](../concepts/index.md#langgraph-platform)**: Understand the foundational concepts of the LangGraph Platform.
- **[LangGraph Platform How-to Guides](../how-tos/index.md#langgraph-platform)**: Discover step-by-step guides to build and deploy applications.
- **[Launch Local LangGraph Server](../tutorials/langgraph-platform/local-server.md)**: This quick start guide shows how to start a LangGraph Server locally for the **ReAct Agent** template. The steps are similar for other templates.
@@ -0,0 +1,19 @@
<!doctype html>
<html>
<head>
<title>LangGraph Cloud API Reference</title>
<meta charset="utf-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1" />
</head>
<body>
<script id="api-reference" data-url="./openapi_control_plane.json"></script>
<script>
var configuration = {}
document.getElementById('api-reference').dataset.configuration =
JSON.stringify(configuration)
</script>
<script src="https://cdn.jsdelivr.net/npm/@scalar/api-reference"></script>
</body>
</html>
@@ -0,0 +1,758 @@
{
"openapi": "3.1.0",
"info": {
"title": "LangGraph Control Plane API (Beta)",
"version": "0.0.1",
"description": "The LangGraph Control Plane API is used to programmatically create and manage LangGraph Server deployments. For example, the APIs can be orchestrated to create custom CI/CD workflows.\n\n### Beta\nThis API is currently in beta and may change or break without notice. This API documentation may not be up-to-date with actual API functionality.\n### Host\nhttps://api.host.langchain.com/\n\n### Authentication\nTo authenticate with the LangGraph Control Plane API, set the `X-Api-Key` header to a valid LangSmith API key for each request.\n\n### Versioning\nEach endpoint path is prefixed with a version (e.g. `v1`).\n\n### Quick Start\n\n1. Call `GET /{version}/projects` to retrieve the `Project` `id`. The `Project` `id` is needed in subsequent API calls.\n2. Call `POST /{version}/projects/{project_id}/revisions` to create a new `Revision` for the `Project`.\n3. Call `GET /{version}/projects/{project_id}/revisions` to get the latest `Revision` (first element in returned list). Get the `Revision` `id`.\n4. Poll for `Revision` `status` until `status` is `DEPLOYED` by calling `GET /{version}/projects/{project_id}/revisions/{revision_id}`."
},
"servers": [
{
"url": "https://api.host.langchain.com"
}
],
"tags": [
{
"name": "Projects (v1)",
"description": "A project corresponds to a LangGraph Server deployment and the associated LangSmith tracing project.\n\nCreating a project via API is not currently supported/documented."
},
{
"name": "Revisions (v1)",
"description": "A revision is a version of a LangGraph Server deployment. Different revisions may contain different code and/or environment variables. A project can have many revisions."
}
],
"paths": {
"/v1/projects": {
"get": {
"tags": ["Projects (v1)"],
"summary": "List Projects",
"description": "List all projects.",
"operationId": "list_projects_projects_get",
"parameters": [
{
"required": false,
"schema": {
"type": "integer",
"title": "Limit",
"description": "Maximum number of results to return. Minimum: 1. Maximum: 100.",
"default": 20
},
"name": "limit",
"in": "query"
},
{
"required": false,
"schema": {
"type": "integer",
"title": "Offset",
"description": "Pagination offset value. Pass this value in subsequent requests to retrieve the next page of results. Minimum: 0.",
"default": 0
},
"name": "offset",
"in": "query"
},
{
"required": false,
"schema": {
"type": "string",
"title": "Name Contains",
"description": "Filter string to filter projects by `name`."
},
"name": "name_contains",
"in": "query"
}
],
"responses": {
"200": {
"description": "Success",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Project"
}
}
}
}
}
}
}
},
"/v1/projects/{project_id}": {
"get": {
"tags": ["Projects (v1)"],
"summary": "Get Project",
"description": "Get project by ID.",
"operationId": "get_project_projects__project_id__get",
"parameters": [
{
"required": true,
"schema": {
"type": "string",
"format": "uuid",
"title": "Project ID"
},
"name": "project_id",
"in": "path"
}
],
"responses": {
"200": {
"description": "Success",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Project"
}
}
}
}
}
},
"delete": {
"tags": ["Projects (v1)"],
"summary": "Delete Project",
"description": "Delete project by ID.",
"operationId": "delete_project_projects__project_id__delete",
"parameters": [
{
"required": true,
"schema": {
"type": "string",
"format": "uuid",
"title": "Project ID"
},
"name": "project_id",
"in": "path"
}
],
"responses": {
"200": {
"description": "Success",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Project"
}
}
}
}
}
}
},
"/v1/projects/{project_id}/revisions": {
"get": {
"tags": ["Revisions (v1)"],
"summary": "List Revisions",
"description": "List revisions of a project.",
"operationId": "list_revisions_projects__project_id__revisions_get",
"parameters": [
{
"required": true,
"schema": {
"type": "string",
"format": "uuid",
"title": "Project ID"
},
"name": "project_id",
"in": "path"
},
{
"required": false,
"schema": {
"type": "integer",
"title": "Limit",
"description": "Maximum number of results to return. Minimum: 1. Maximum: 100.",
"default": 20
},
"name": "limit",
"in": "query"
},
{
"required": false,
"schema": {
"type": "integer",
"title": "Offset",
"description": "Pagination offset value. Pass this value in subsequent requests to retrieve the next page of results. Minimum: 0.",
"default": 0
},
"name": "offset",
"in": "query"
}
],
"responses": {
"200": {
"description": "Success",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Revision"
}
}
}
}
}
}
},
"post": {
"tags": ["Revisions (v1)"],
"summary": "Create Revision",
"description": "Create a new revision for a project.",
"operationId": "create_revision_projects__project_id__revisions_post",
"parameters": [
{
"required": true,
"schema": {
"type": "string",
"format": "uuid",
"title": "Project ID"
},
"name": "project_id",
"in": "path"
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateRevisionRequest"
}
}
},
"required": true
},
"responses": {
"200": {
"description": "Success",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Project"
}
}
}
}
}
}
},
"/v1/projects/{project_id}/revisions/{revision_id}": {
"get": {
"tags": ["Revisions (v1)"],
"summary": "Get Revision",
"description": "Get revision by ID.",
"operationId": "get_revision_projects__project_id__revisions__revision_id__get",
"parameters": [
{
"required": true,
"schema": {
"type": "string",
"format": "uuid",
"title": "Project ID"
},
"name": "project_id",
"in": "path"
},
{
"required": true,
"schema": {
"type": "string",
"format": "uuid",
"title": "Revision ID"
},
"name": "revision_id",
"in": "path"
}
],
"responses": {
"200": {
"description": "Success",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Revision"
}
}
}
}
}
}
},
"/v1/projects/{project_id}/revisions/{revision_id}/deploy": {
"post": {
"tags": ["Revisions (v1)"],
"summary": "Deploy Revision",
"description": "Deploy revision by ID.\n\nThis endpoint redeploys the deployment of a revision without rebuilding the image for the deployment. Redeploying the deployment of a revision may mitigate intermittent issues with a deployment.\n\nThe revision must be in the `DEPLOYED` status and must be the latest revision of the project.",
"operationId": "deploy_revision_projects__project_id__revisions__revision_id__deploy_post",
"parameters": [
{
"required": true,
"schema": {
"type": "string",
"format": "uuid",
"title": "Project ID"
},
"name": "project_id",
"in": "path"
},
{
"required": true,
"schema": {
"type": "string",
"format": "uuid",
"title": "Revision ID"
},
"name": "revision_id",
"in": "path"
}
],
"responses": {
"400": {
"description": "Revision is not in DEPLOYED status or revision is not the latest revision for the project.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "Revision not found.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/v1/projects/{project_id}/revisions/{revision_id}/interrupt": {
"post": {
"tags": ["Revisions (v1)"],
"summary": "Interrupt Revision",
"description": "Interrupt revision by ID.\n\nIf the deployment of a revision appears \"stuck\", the revision may need to be interrupted. A new revision cannot be created if the latest revision is in a non-terminal `status`. In this scenario, the revision may need to be interrupted.",
"operationId": "interrupt_revision_projects__project_id__revisions__revision_id__interrupt_post",
"parameters": [
{
"required": true,
"schema": {
"type": "string",
"format": "uuid",
"title": "Project ID"
},
"name": "project_id",
"in": "path"
},
{
"required": true,
"schema": {
"type": "string",
"format": "uuid",
"title": "Revision ID"
},
"name": "revision_id",
"in": "path"
}
]
}
}
},
"components": {
"securitySchemes": {
"apiKeyAuth": {
"type": "apiKey",
"in": "header",
"name": "X-Api-Key"
}
},
"schemas": {
"ContainerSpec": {
"type": "object",
"description": "Container specification for a revision's deployment.\n\nIf any field is omitted or set to `null`, the internal default value is used depending on the deployment type (`dev` or `prod`).",
"properties": {
"min_scale": {
"type": ["integer", "null"],
"description": "Minimum number of replicas in deployment.",
"default": "null"
},
"max_scale": {
"type": ["integer", "null"],
"description": "Maximum number of replicas in deployment.",
"default": "null"
},
"cpu": {
"type": ["integer", "null"],
"description": "Number of vCPU cores per replica.",
"default": "null"
},
"memory_mb": {
"type": ["integer", "null"],
"description": "Amount of memory in MB per replica.",
"default": "null"
}
}
},
"CreateRevisionRequest": {
"type": "object",
"description": "Object for creating a new revision.",
"properties": {
"image_path": {
"type": ["string", "null"],
"description": "URI of the Docker image to deploy.\n\nIf this field is omitted or set to `null`, the previous revision's `image_path` value is used. Set this field for BYOC deployments. Omit this field if creating a new revision from a GitHub repository.",
"default": "null"
},
"repo_path": {
"type": ["string", "null"],
"description": "Path to `langgraph.json` configuration file. For example, `langgraph.json` or `src/langgraph.json`.\n\nIf this field is omitted or set to `null`, the previous revision's `repo_path` value is used. Set this field for deployments from a GitHub repository. Omit this field if creating a new revision from a Docker image.",
"default": "null"
},
"env_vars": {
"type": "array",
"description": "List of environment variables or secrets.\n\nIf this field is omitted or set to `null`, the previous revision's `env_vars` value is used.",
"items": {
"$ref": "#/components/schemas/EnvVar"
},
"default": "null"
},
"shareable": {
"type": ["boolean", "null"],
"description": "Boolean flag to configure if a deployment is shareable through LangGraph Studio.\n\nIf this field is omitted or set to `null`, the previous revision's `shareable` value is used. This field does not apply to BYOC deployments.",
"default": "null"
},
"container_spec": {
"description": "If this field is omitted or set to `null`, the previous revision's `container_spec` value is used.",
"$ref": "#/components/schemas/ContainerSpec",
"default": "null"
}
}
},
"EnvVar": {
"type": "object",
"description": "An environment variable or secret.",
"properties": {
"name": {
"type": "string",
"description": "Environment variable or secret name.",
"required": true
},
"value": {
"type": "string",
"description": "Environment variable or secret value.",
"required": true
},
"type": {
"type": "string",
"enum": [
"default",
"secret"
],
"description": "Field to designate type of the environment variable (default) or secret.",
"required": true
}
}
},
"ErrorResponse": {
"type": "object",
"description": "Error response.",
"properties": {
"detail": {
"type": "string",
"description": "Error details.",
"required": true
}
}
},
"Project": {
"type": "object",
"description": "A project corresponds to a LangGraph Server deployment and the associated LangSmith tracing project.",
"properties": {
"id": {
"type": "string",
"format": "uuid",
"description": "ID of the project.",
"required": true
},
"tool_name": {
"type": ["string", "null"],
"description": "Do not use."
},
"display_name": {
"type": ["string", "null"],
"description": "Do not use."
},
"description": {
"type": ["string", "null"],
"description": "Do not use."
},
"example_input": {
"type": ["object", "null"],
"description": "Do not use."
},
"tenant_id": {
"type": "string",
"format": "uuid",
"description": "ID of the tenant/workspace of the project.",
"required": true
},
"created_at": {
"type": "string",
"format": "date-time",
"description": "Timestamp of when the project was created.",
"required": true
},
"updated_at": {
"type": "string",
"format": "date-time",
"description": "Timestamp of when the project was updated.",
"required": true
},
"name": {
"type": "string",
"description": "Name of the project.\n\nThis is also the name of the LangSmith tracing project for the LangGraph deployment.",
"required": true
},
"lc_hosted": {
"type": "boolean",
"description": "Boolean flag to indicate if the deployment is hosted in LangChain's cloud or an external cloud (e.g. BYOC).",
"required": true
},
"repo_url": {
"type": ["string", "null"],
"description": "URL of the GitHub repository.\n\nThis field is not used for deployments from a Docker image."
},
"repo_branch": {
"type": ["string", "null"],
"description": "Branch of the GitHub repository.\n\nThis field is not used for deployments from a Docker image."
},
"tracer_session_id": {
"type": ["string", "null"],
"format": "uuid",
"description": "Do not use."
},
"api_key_id": {
"type": ["string", "null"],
"format": "uuid",
"description": "Do not use."
},
"build_on_push": {
"type": "boolean",
"description": "Boolean flag to indicate if a new revision is automatically created on push to GitHub branch (`repo_branch`).\n\nThis field does not apply for BYOC deployments."
},
"input_json_schemas": {
"type": ["object", "null"],
"description": "Do not use."
},
"output_json_schemas": {
"type": ["object", "null"],
"description": "Do not use."
},
"host_integration_id": {
"type": ["string", "null"],
"format": "uuid",
"description": "Do not use."
},
"metadata": {
"$ref": "#/components/schemas/ProjectMetadata"
},
"resource": {
"$ref": "#/components/schemas/ResourceService"
}
}
},
"ProjectMetadata": {
"type": "object",
"description": "Metadata associated with a `Project`.",
"properties": {
"deployment_type": {
"type": "string",
"description": "Development (`dev`) or Production (`prod`) type deployment.",
"enum": [
"dev",
"prod"
]
},
"image_source": {
"type": "string",
"description": "Do not use.",
"enum": [
"github",
"internal_docker",
"external_docker"
]
},
"shareable": {
"type": "boolean",
"description": "Boolean flag to configure if a deployment is shareable through LangGraph Studio.\n\nThis field does not apply to BYOC deployments."
},
"region": {
"type": "string",
"description": "Region of deployment.\n\nRegion value is cloud provider specific."
},
"aws_account_id": {
"type": "string",
"description": "AWS account ID of BYOC deployment.\n\nThis field does not apply to non-BYOC deployments."
},
"aws_external_id": {
"type": "string",
"description": "Do not use."
}
}
},
"ResourceId": {
"type": "object",
"description": "Internal identifier for a `ResourceRevision` or `ResourceService`.",
"properties": {
"type": {
"type": "string",
"enum": [
"revisions",
"services"
]
},
"name": {
"type": "string"
}
}
},
"ResourceRevision": {
"type": "object",
"description": "Internal revision resource for a `ResourceService`.",
"properties": {
"id": {
"$ref": "#/components/schemas/ResourceId"
},
"env_vars": {
"type": "array",
"items": {
"$ref": "#/components/schemas/EnvVar"
}
},
"hosted_langserve_revision_id": {
"type": "string",
"format": "uuid",
"description": "References `id` of a `Revision`."
}
}
},
"ResourceService": {
"type": "object",
"description": "Internal service resource for a `Project`.",
"properties": {
"id": {
"$ref": "#/components/schemas/ResourceId"
},
"url": {
"type": ["string", "null"],
"description": "URL of LangGraph Server deployment."
},
"latest_revision": {
"description": "References latest `ResourceRevision`.\n\nThe latest `ResourceRevision` may not be active if it's currently being deployed.",
"$ref": "#/components/schemas/ResourceRevision"
},
"latest_active_revision": {
"description": "References latest active `ResourceRevision`.\n\nThe latest active `ResourceRevision` is not always the latest `ResourceRevision`.",
"$ref": "#/components/schemas/ResourceRevision"
}
}
},
"Revision": {
"type": "object",
"description": "A revision is a version of a LangGraph Server deployment.\n\nDifferent revisions may contain different code and/or environment variables. A project can have many revisions.",
"properties": {
"id": {
"type": "string",
"format": "uuid",
"description": "ID of the revision.",
"required": true
},
"project_id": {
"type": "string",
"format": "uuid",
"description": "References `id` of `Project`.",
"required": true
},
"created_at": {
"type": "string",
"format": "date-time",
"description": "Timestamp of when the revision was created.",
"required": true
},
"updated_at": {
"type": "string",
"format": "date-time",
"description": "Timestamp of when the revision was updated.",
"required": true
},
"repo_path": {
"type": ["string", "null"],
"description": "Path to `langgraph.json` configuration file. For example, `langgraph.json` or `src/langgraph.json`.\n\nThis field only applies to deployments from a GitHub repository.",
"default": "null"
},
"repo_commit": {
"type": ["string", "null"],
"description": "Git branch name of deployment.\n\nThis field only applies to deployments from a GitHub repository.",
"default": "null"
},
"status": {
"type": "string",
"enum": [
"CREATING",
"AWAITING_BUILD",
"BUILDING",
"AWAITING_DEPLOY",
"DEPLOYING",
"CREATE_FAILED",
"BUILD_FAILED",
"DEPLOY_FAILED",
"DEPLOYED",
"INTERRUPTED",
"UNKNOWN"
],
"description": "Deployment status of the revision.\n\nNon-terminal statuses: `CREATING`, `AWAITING_BUILD`, `BUILDING`, `AWAITING_DEPLOY`, `DEPLOYING`. All other statuses are terminal."
},
"status_message": {
"type": "string",
"description": "Message associated with the `status`."
},
"gcp_build_name": {
"type": ["string", "null"],
"description": "Do not use."
},
"metadata": {
"$ref": "#/components/schemas/RevisionMetadata"
},
"image_path": {
"type": ["string", "null"],
"description": "URI of the Docker image to deploy.\n\nThis field does not apply to deployments from a GitHub repository.",
"default": "null"
},
"container_spec": {
"$ref": "#/components/schemas/ContainerSpec"
},
"resource": {
"$ref": "#/components/schemas/ResourceRevision"
}
}
},
"RevisionMetadata": {
"type": "object",
"description": "Metadata associated with a `Revision`.",
"properties": {
"created_by": {
"type": "object",
"description": "Do not use."
},
"repo_commit_sha": {
"type": "string",
"description": "Git commit SHA of the deployment.\n\nThis field only applies to deployments from a GitHub repository."
}
}
}
}
}
}
+37 -3
View File
@@ -21,14 +21,15 @@ The LangGraph command line interface includes commands to build and run a LangGr
[](){#langgraph.json}
## Configuration File
## Configuration File {#configuration-file}
The LangGraph CLI requires a JSON configuration file with the following keys:
| Key | Description |
| Key | Description |
| ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `dependencies` | **Required**. Array of dependencies for LangGraph Cloud API server. Dependencies can be one of the following: (1) `"."`, which will look for local Python packages, (2) `pyproject.toml`, `setup.py` or `requirements.txt` in the app directory `"./local_package"`, or (3) a package name. |
| `graphs` | **Required**. Mapping from graph ID to path where the compiled graph or a function that makes a graph is defined. Example: <ul><li>`./your_package/your_file.py:variable`, where `variable` is an instance of `langgraph.graph.state.CompiledStateGraph`</li><li>`./your_package/your_file.py:make_graph`, where `make_graph` is a function that takes a config dictionary (`langchain_core.runnables.RunnableConfig`) and creates an instance of `langgraph.graph.state.StateGraph` / `langgraph.graph.state.CompiledStateGraph`.</li></ul> |
| `auth` | _(Added in v0.0.11)_ Auth configuration containing the path to your authentication handler. Example: `./your_package/auth.py:auth`, where `auth` is an instance of `langgraph_sdk.Auth`. See [authentication guide](../../concepts/auth.md) for details. |
| `env` | Path to `.env` file or a mapping from environment variable to its value. |
| `store` | Configuration for adding semantic search to the BaseStore. Contains the following fields: <ul><li>`index`: Configuration for semantic search indexing with fields:<ul><li>`embed`: Embedding provider (e.g., "openai:text-embedding-3-small") or path to custom embedding function</li><li>`dims`: Dimension size of the embedding model. Used to initialize the vector table.</li><li>`fields` (optional): List of fields to index. Defaults to `["$"]`, meaningto index entire documents. Can be specific fields like `["text", "summary", "some.value"]`</li></ul></li></ul> |
| `python_version` | `3.11` or `3.12`. Defaults to `3.11`. |
@@ -60,6 +61,7 @@ The LangGraph CLI requires a JSON configuration file with the following keys:
All deployments come with a DB-backed BaseStore. Adding an "index" configuration to your `langgraph.json` will enable [semantic search](../deployment/semantic_search.md) within the BaseStore of your deployment.
The `fields` configuration determines which parts of your documents to embed:
- If omitted or set to `["$"]`, the entire document will be embedded
- To embed specific fields, use JSON path notation: `["metadata.title", "content.text"]`
- Documents missing specified fields will still be stored but won't have embeddings for those fields
@@ -120,6 +122,35 @@ def embed_texts(texts: list[str]) -> list[list[float]]:
return [[0.1, 0.2, ...] for _ in texts] # dims-dimensional vectors
```
#### Adding custom authentication
```json
{
"dependencies": ["."],
"graphs": {
"chat": "./chat/graph.py:graph"
},
"auth": {
"path": "./auth.py:auth",
"openapi": {
"securitySchemes": {
"apiKeyAuth": {
"type": "apiKey",
"in": "header",
"name": "X-API-Key"
}
},
"security": [
{"apiKeyAuth": []}
]
},
"disable_studio_auth": false
}
}
```
See the [authentication conceptual guide](../../concepts/auth.md) for details, and the [setting up custom authentication](../../tutorials/auth/getting_started.md) guide for a practical walk through of the process.
## Commands
The base command for the LangGraph CLI is `langgraph`.
@@ -258,4 +289,7 @@ RUN set -ex && \
RUN PIP_CONFIG_FILE=/pipconfig.txt PYTHONDONTWRITEBYTECODE=1 pip install --no-cache-dir -c /api/constraints.txt -e /deps/*
ENV LANGSERVE_GRAPHS='{"agent": "/deps/__outer_graphs/src/agent.py:graph", "storm": "/deps/__outer_graphs/src/storm.py:graph"}'
```
```
???+ note "Updating your langgraph.json file"
The `langgraph dockerfile` command translates all the configuration in your `langgraph.json` file into Dockerfile commands. When using this command, you will have to re-run it whenever you update your `langgraph.json` file. Otherwise, your changes will not be reflected when you build or run the dockerfile.
+34 -2
View File
@@ -1,6 +1,6 @@
# Environment Variables
The LangGraph Cloud API supports specific environment variables for configuring a deployment.
The LangGraph Cloud Server supports specific environment variables for configuring a deployment.
## `LANGCHAIN_TRACING_SAMPLING_RATE`
@@ -10,10 +10,42 @@ See <a href="https://docs.smith.langchain.com/how_to_guides/tracing/sample_trace
## `LANGGRAPH_AUTH_TYPE`
Type of authentication for the LangGraph Cloud API deployment. Valid values: `langsmith`, `noop`.
Type of authentication for the LangGraph Cloud Server deployment. Valid values: `langsmith`, `noop`.
For deployments to LangGraph Cloud, this environment variable is set automatically. For local development or deployments where authentication is handled externally (e.g. self-hosted), set this environment variable to `noop`.
## `LANGSMITH_RUNS_ENDPOINTS`
For [Bring Your Own Cloud (BYOC)](../../concepts/bring_your_own_cloud.md) deployments with [self-hosted LangSmith](https://docs.smith.langchain.com/self_hosting) only.
Set this environment variable to have a BYOC deployment send traces to a self-hosted LangSmith instance. The value of `LANGSMITH_RUNS_ENDPOINTS` is a JSON string: `{"<SELF_HOSTED_LANGSMITH_HOSTNAME>":"<LANGSMITH_API_KEY>"}`.
`SELF_HOSTED_LANGSMITH_HOSTNAME` is the hostname of the self-hosted LangSmith instance. It must be accessible to the BYOC deployment. `LANGSMITH_API_KEY` is a LangSmith API generated from the self-hosted LangSmith instance.
## `N_JOBS_PER_WORKER`
Number of jobs per worker for the LangGraph Cloud task queue. Defaults to `10`.
## `POSTGRES_URI_CUSTOM`
For [Bring Your Own Cloud (BYOC)](../../concepts/bring_your_own_cloud.md) deployments only.
Specify `POSTGRES_URI_CUSTOM` to use an externally managed Postgres instance. The value of `POSTGRES_URI_CUSTOM` must be a valid [Postgres connection URI](https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING-URIS).
Postgres:
- Version 15.8 or higher.
- An initial database must be present and the connection URI must reference the database.
Control Plane Functionality:
- If `POSTGRES_URI_CUSTOM` is specified, the LangGraph Control Plane will not provision a database for the server.
- If `POSTGRES_URI_CUSTOM` is removed, the LangGraph Control Plane will not provision a database for the server and will not delete the externally managed Postgres instance.
- If `POSTGRES_URI_CUSTOM` is removed, deployment of the revision will not succeed. Once `POSTGRES_URI_CUSTOM` is specified, it must always be set for the lifecycle of the deployment.
- If the deployment is deleted, the LangGraph Control Plane will not delete the externally managed Postgres instance.
- The value of `POSTGRES_URI_CUSTOM` can be updated. For example, a password in the URI can be updated.
Database Connectivity:
- The externally managed Postgres instance must be accessible by the LangGraph Server service in the ECS cluster. The BYOC user is responsible for ensuring connectivity.
- For example, if an AWS RDS Postgres instance is provisioned, it can be provisioned in the same VPC (`langgraph-cloud-vpc`) as the ECS cluster with the `langgraph-cloud-service-sg` security group to ensure connectivity.
@@ -6,3 +6,12 @@
::: langgraph_sdk.schema
handler: python
::: langgraph_sdk.auth
handler: python
::: langgraph_sdk.auth.types
handler: python
::: langgraph_sdk.auth.exceptions
handler: python
+11 -11
View File
@@ -1,26 +1,26 @@
# Agent architectures
Many LLM applications implement a particular control flow of steps before and / or after LLM calls. As an example, [RAG](https://github.com/langchain-ai/rag-from-scratch) performs retrieval of relevant documents to a question, and passes those documents to an LLM in order to ground the model's response.
Many LLM applications implement a particular control flow of steps before and / or after LLM calls. As an example, [RAG](https://github.com/langchain-ai/rag-from-scratch) performs retrieval of documents relevant to a user question, and passes those documents to an LLM in order to ground the model's response in the provided document context.
Instead of hard-coding a fixed control flow, we sometimes want LLM systems that can pick its own control flow to solve more complex problems! This is one definition of an [agent](https://blog.langchain.dev/what-is-an-agent/): *an agent is a system that uses an LLM to decide the control flow of an application.* There are many ways that an LLM can control application:
Instead of hard-coding a fixed control flow, we sometimes want LLM systems that can pick their own control flow to solve more complex problems! This is one definition of an [agent](https://blog.langchain.dev/what-is-an-agent/): *an agent is a system that uses an LLM to decide the control flow of an application.* There are many ways that an LLM can control application:
- An LLM can route between two potential paths
- An LLM can decide which of many tools to call
- An LLM can decide whether the generated answer is sufficient or more work is needed
As a result, there are many different types of [agent architectures](https://blog.langchain.dev/what-is-a-cognitive-architecture/), which given an LLM varying levels of control.
As a result, there are many different types of [agent architectures](https://blog.langchain.dev/what-is-a-cognitive-architecture/), which give an LLM varying levels of control.
![Agent Types](img/agent_types.png)
## Router
A router allows an LLM to select a single step from a specified set of options. This is an agent architecture that exhibits a relatively limited level of control because the LLM usually governs a single decision and can return a narrow set of outputs. Routers typically employ a few different concepts to achieve this.
A router allows an LLM to select a single step from a specified set of options. This is an agent architecture that exhibits a relatively limited level of control because the LLM usually focuses on making a single decision and produces a specific output from limited set of pre-defined options. Routers typically employ a few different concepts to achieve this.
### Structured Output
Structured outputs with LLMs work by providing a specific format or schema that the LLM should follow in its response. This is similar to tool calling, but more general. While tool calling typically involves selecting and using predefined functions, structured outputs can be used for any type of formatted response. Common methods to achieve structured outputs include:
1. Prompt engineering: Instructing the LLM to respond in a specific format.
1. Prompt engineering: Instructing the LLM to respond in a specific format via the system prompt.
2. Output parsers: Using post-processing to extract structured data from LLM responses.
3. Tool calling: Leveraging built-in tool calling capabilities of some LLMs to generate structured outputs.
@@ -30,7 +30,7 @@ Structured outputs are crucial for routing as they ensure the LLM's decision can
While a router allows an LLM to make a single decision, more complex agent architectures expand the LLM's control in two key ways:
1. Multi-step decision making: The LLM can control a sequence of decisions rather than just one.
1. Multi-step decision making: The LLM can make a series of decisions, one after another, instead of just one.
2. Tool access: The LLM can choose from and use a variety of tools to accomplish tasks.
[ReAct](https://arxiv.org/abs/2210.03629) is a popular general purpose agent architecture that combines these expansions, integrating three core concepts.
@@ -39,13 +39,13 @@ While a router allows an LLM to make a single decision, more complex agent archi
2. `Memory`: Enabling the agent to retain and use information from previous steps.
3. `Planning`: Empowering the LLM to create and follow multi-step plans to achieve goals.
This architecture allows for more complex and flexible agent behaviors, going beyond simple routing to enable dynamic problem-solving across multiple steps. You can use it with [`create_react_agent`][langgraph.prebuilt.chat_agent_executor.create_react_agent].
This architecture allows for more complex and flexible agent behaviors, going beyond simple routing to enable dynamic problem-solving with multiple steps. You can use it with [`create_react_agent`][langgraph.prebuilt.chat_agent_executor.create_react_agent].
### Tool calling
Tools are useful whenever you want an agent to interact with external systems. External systems (e.g., APIs) often require a particular input schema or payload, rather than natural language. When we bind an API, for example, as a tool we given the model awareness of the required input schema. The model will choose to call a tool based upon the natural language input from the user and it will return an output that adheres to the tool's schema.
Tools are useful whenever you want an agent to interact with external systems. External systems (e.g., APIs) often require a particular input schema or payload, rather than natural language. When we bind an API, for example, as a tool, we give the model awareness of the required input schema. The model will choose to call a tool based upon the natural language input from the user and it will return an output that adheres to the tool's required schema.
[Many LLM providers support tool calling](https://python.langchain.com/v0.1/docs/integrations/chat/) and [tool calling interface](https://blog.langchain.dev/improving-core-tool-interfaces-and-docs-in-langchain/) in LangChain is simple: you can simply pass any Python `function` into `ChatModel.bind_tools(function)`.
[Many LLM providers support tool calling](https://python.langchain.com/docs/integrations/chat/) and [tool calling interface](https://blog.langchain.dev/improving-core-tool-interfaces-and-docs-in-langchain/) in LangChain is simple: you can simply pass any Python `function` into `ChatModel.bind_tools(function)`.
![Tools](img/tool_call.png)
@@ -67,11 +67,11 @@ Effective memory management enhances an agent's ability to maintain context, lea
### Planning
In the ReAct architecture, an LLM is called repeatedly in a while-loop. At each step the agent decides which tools to call, and what the inputs to those tools should be. Those tools are then executed, and the outputs are fed back into the LLM as observations. The while-loop terminates when the agent decides it is not worth calling any more tools.
In the ReAct architecture, an LLM is called repeatedly in a while-loop. At each step the agent decides which tools to call, and what the inputs to those tools should be. Those tools are then executed, and the outputs are fed back into the LLM as observations. The while-loop terminates when the agent decides it has enough information to solve the user request and it is not worth calling any more tools.
### ReAct implementation
There are several differences between this paper and the pre-built [`create_react_agent`][langgraph.prebuilt.chat_agent_executor.create_react_agent] implementation:
There are several differences between [this](https://arxiv.org/abs/2210.03629) paper and the pre-built [`create_react_agent`][langgraph.prebuilt.chat_agent_executor.create_react_agent] implementation:
- First, we use [tool-calling](#tool-calling) to have LLMs call tools, whereas the paper used prompting + parsing of raw output. This is because tool calling did not exist when the paper was written, but is generally better and more reliable.
- Second, we use messages to prompt the LLM, whereas the paper used string formatting. This is because at the time of writing, LLMs didn't even expose a message-based interface, whereas now that's the only interface they expose.
+428
View File
@@ -0,0 +1,428 @@
# Authentication & Access Control
LangGraph Platform provides a flexible authentication and authorization system that can integrate with most authentication schemes.
!!! note "Python only"
We currently only support custom authentication and authorization in Python deployments with `langgraph-api>=0.0.11`. Support for LangGraph.JS will be added soon.
## Core Concepts
### Authentication vs Authorization
While often used interchangeably, these terms represent distinct security concepts:
- [**Authentication**](#authentication) ("AuthN") verifies _who_ you are. This runs as middleware for every request.
- [**Authorization**](#authorization) ("AuthZ") determines _what you can do_. This validates the user's privileges and roles on a per-resource basis.
In LangGraph Platform, authentication is handled by your [`@auth.authenticate`](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.Auth.authenticate) handler, and authorization is handled by your [`@auth.on`](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.Auth.on) handlers.
## Default Security Models
LangGraph Platform provides different security defaults:
### LangGraph Cloud
- Uses LangSmith API keys by default
- Requires valid API key in `x-api-key` header
- Can be customized with your auth handler
### Self-Hosted
- No default authentication
- Complete flexibility to implement your security model
- You control all aspects of authentication and authorization
## System Architecture
A typical authentication setup involves three main components:
1. **Authentication Provider** (Identity Provider/IdP)
* A dedicated service that manages user identities and credentials
* Handles user registration, login, password resets, etc.
* Issues tokens (JWT, session tokens, etc.) after successful authentication
* Examples: Auth0, Supabase Auth, Okta, or your own auth server
2. **LangGraph Backend** (Resource Server)
* Your LangGraph application that contains business logic and protected resources
* Validates tokens with the auth provider
* Enforces access control based on user identity and permissions
* Doesn't store user credentials directly
3. **Client Application** (Frontend)
* Web app, mobile app, or API client
* Collects time-sensitive user credentials and sends to auth provider
* Receives tokens from auth provider
* Includes these tokens in requests to LangGraph backend
Here's how these components typically interact:
```mermaid
sequenceDiagram
participant Client as Client App
participant Auth as Auth Provider
participant LG as LangGraph Backend
Client->>Auth: 1. Login (username/password)
Auth-->>Client: 2. Return token
Client->>LG: 3. Request with token
Note over LG: 4. Validate token (@auth.authenticate)
LG-->>Auth: 5. Fetch user info
Auth-->>LG: 6. Confirm validity
Note over LG: 7. Apply access control (@auth.on.*)
LG-->>Client: 8. Return resources
```
Your [`@auth.authenticate`](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.Auth.authenticate) handler in LangGraph handles steps 4-6, while your [`@auth.on`](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.Auth.on) handlers implement step 7.
## Authentication
Authentication in LangGraph runs as middleware on every request. Your [`@auth.authenticate`](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.Auth.authenticate) handler receives request information and should:
1. Validate the credentials
2. Return [user info](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.types.MinimalUserDict) containing the user's identity and user information if valid
3. Raise an [HTTP exception](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.exceptions.HTTPException) or AssertionError if invalid
```python
from langgraph_sdk import Auth
auth = Auth()
@auth.authenticate
async def authenticate(headers: dict) -> Auth.types.MinimalUserDict:
# Validate credentials (e.g., API key, JWT token)
api_key = headers.get("x-api-key")
if not api_key or not is_valid_key(api_key):
raise Auth.exceptions.HTTPException(
status_code=401,
detail="Invalid API key"
)
# Return user info - only identity and is_authenticated are required
# Add any additional fields you need for authorization
return {
"identity": "user-123", # Required: unique user identifier
"is_authenticated": True, # Optional: assumed True by default
"permissions": ["read", "write"] # Optional: for permission-based auth
# You can add more custom fields if you want to implement other auth patterns
"role": "admin",
"org_id": "org-456"
}
```
The returned user information is available:
- To your authorization handlers via [`ctx.user`](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.types.AuthContext)
- In your application via `config["configuration"]["langgraph_auth_user"]`
??? tip "Supported Parameters"
The [`@auth.authenticate`](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.Auth.authenticate) handler can accept any of the following parameters by name:
* request (Request): The raw ASGI request object
* body (dict): The parsed request body
* path (str): The request path, e.g., "/threads/abcd-1234-abcd-1234/runs/abcd-1234-abcd-1234/stream"
* method (str): The HTTP method, e.g., "GET"
* path_params (dict[str, str]): URL path parameters, e.g., {"thread_id": "abcd-1234-abcd-1234", "run_id": "abcd-1234-abcd-1234"}
* query_params (dict[str, str]): URL query parameters, e.g., {"stream": "true"}
* headers (dict[bytes, bytes]): Request headers
* authorization (str | None): The Authorization header value (e.g., "Bearer <token>")
In many of our tutorials, we will just show the "authorization" parameter to be concise, but you can opt to accept more information as needed
to implement your custom authentication scheme.
## Authorization
After authentication, LangGraph calls your [`@auth.on`](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.Auth.on) handlers to control access to specific resources (e.g., threads, assistants, crons). These handlers can:
1. Add metadata to be saved during resource creation by mutating the `value["metadata"]` dictionary directly. See the [supported actions table](##supported-actions) for the list of types the value can take for each action.
2. Filter resources by metadata during search/list or read operations by returning a [filter dictionary](#filter-operations).
3. Raise an HTTP exception if access is denied.
If you want to just implement simple user-scoped access control, you can use a single [`@auth.on`](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.Auth.on) handler for all resources and actions. If you want to have different control depending on the resource and action, you can use [resource-specific handlers](#resource-specific-handlers). See the [Supported Resources](#supported-resources) section for a full list of the resources that support access control.
```python
@auth.on
async def add_owner(
ctx: Auth.types.AuthContext,
value: dict # The payload being sent to this access method
) -> dict: # Returns a filter dict that restricts access to resources
"""Authorize all access to threads, runs, crons, and assistants.
This handler does two things:
- Adds a value to resource metadata (to persist with the resource so it can be filtered later)
- Returns a filter (to restrict access to existing resources)
Args:
ctx: Authentication context containing user info, permissions, the path, and
value: The request payload sent to the endpoint. For creation
operations, this contains the resource parameters. For read
operations, this contains the resource being accessed.
Returns:
A filter dictionary that LangGraph uses to restrict access to resources.
See [Filter Operations](#filter-operations) for supported operators.
"""
# Create filter to restrict access to just this user's resources
filters = {"owner": ctx.user.identity}
# Get or create the metadata dictionary in the payload
# This is where we store persistent info about the resource
metadata = value.setdefault("metadata", {})
# Add owner to metadata - if this is a create or update operation,
# this information will be saved with the resource
# So we can filter by it later in read operations
metadata.update(filters)
# Return filters to restrict access
# These filters are applied to ALL operations (create, read, update, search, etc.)
# to ensure users can only access their own resources
return filters
```
### Resource-Specific Handlers {#resource-specific-handlers}
You can register handlers for specific resources and actions by chaining the resource and action names together with the [`@auth.on`](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.Auth.on) decorator.
When a request is made, the most specific handler that matches that resource and action is called. Below is an example of how to register handlers for specific resources and actions. For the following setup:
1. Authenticated users are able to create threads, read thread, create runs on threads
2. Only users with the "assistants:create" permission are allowed to create new assistants
3. All other endpoints (e.g., e.g., delete assistant, crons, store) are disabled for all users.
!!! tip "Supported Handlers"
For a full list of supported resources and actions, see the [Supported Resources](#supported-resources) section below.
```python
# Generic / global handler catches calls that aren't handled by more specific handlers
@auth.on
async def reject_unhandled_requests(ctx: Auth.types.AuthContext, value: Any) -> False:
print(f"Request to {ctx.path} by {ctx.user.identity}")
raise Auth.exceptions.HTTPException(
status_code=403,
detail="Forbidden"
)
# Matches the "thread" resource and all actions - create, read, update, delete, search
# Since this is **more specific** than the generic @auth.on handler, it will take precedence
# over the generic handler for all actions on the "threads" resource
@auth.on.threads
async def on_thread_create(
ctx: Auth.types.AuthContext,
value: Auth.types.threads.create.value
):
if "write" not in ctx.permissions:
raise Auth.exceptions.HTTPException(
status_code=403,
detail="User lacks the required permissions."
)
# Setting metadata on the thread being created
# will ensure that the resource contains an "owner" field
# Then any time a user tries to access this thread or runs within the thread,
# we can filter by owner
metadata = value.setdefault("metadata", {})
metadata["owner"] = ctx.user.identity
return {"owner": ctx.user.identity}
# Thread creation. This will match only on thread create actions
# Since this is **more specific** than both the generic @auth.on handler and the @auth.on.threads handler,
# it will take precedence for any "create" actions on the "threads" resources
@auth.on.threads.create
async def on_thread_create(
ctx: Auth.types.AuthContext,
value: Auth.types.threads.create.value
):
# Setting metadata on the thread being created
# will ensure that the resource contains an "owner" field
# Then any time a user tries to access this thread or runs within the thread,
# we can filter by owner
metadata = value.setdefault("metadata", {})
metadata["owner"] = ctx.user.identity
return {"owner": ctx.user.identity}
# Reading a thread. Since this is also more specific than the generic @auth.on handler, and the @auth.on.threads handler,
# it will take precedence for any "read" actions on the "threads" resource
@auth.on.threads.read
async def on_thread_read(
ctx: Auth.types.AuthContext,
value: Auth.types.threads.read.value
):
# Since we are reading (and not creating) a thread,
# we don't need to set metadata. We just need to
# return a filter to ensure users can only see their own threads
return {"owner": ctx.user.identity}
# Run creation, streaming, updates, etc.
# This takes precedenceover the generic @auth.on handler and the @auth.on.threads handler
@auth.on.threads.create_run
async def on_run_create(
ctx: Auth.types.AuthContext,
value: Auth.types.threads.create_run.value
):
metadata = value.setdefault("metadata", {})
metadata["owner"] = ctx.user.identity
# Inherit thread's access control
return {"owner": ctx.user.identity}
# Assistant creation
@auth.on.assistants.create
async def on_assistant_create(
ctx: Auth.types.AuthContext,
value: Auth.types.assistants.create.value
):
if "assistants:create" not in ctx.permissions:
raise Auth.exceptions.HTTPException(
status_code=403,
detail="User lacks the required permissions."
)
```
Notice that we are mixing global and resource-specific handlers in the above example. Since each request is handled by the most specific handler, a request to create a `thread` would match the `on_thread_create` handler but NOT the `reject_unhandled_requests` handler. A request to `update` a thread, however would be handled by the global handler, since we don't have a more specific handler for that resource and action. Requests to create, update,
### Filter Operations {#filter-operations}
Authorization handlers can return `None`, a boolean, or a filter dictionary.
- `None` and `True` mean "authorize access to all underling resources"
- `False` means "deny access to all underling resources (raises a 403 exception)"
- A metadata filter dictionary will restrict access to resources
A filter dictionary is a dictionary with keys that match the resource metadata. It supports three operators:
- The default value is a shorthand for exact match, or "$eq", below. For example, `{"owner": user_id}` will include only resources with metadata containing `{"owner": user_id}`
- `$eq`: Exact match (e.g., `{"owner": {"$eq": user_id}}`) - this is equivalent to the shorthand above, `{"owner": user_id}`
- `$contains`: List membership (e.g., `{"allowed_users": {"$contains": user_id}}`) The value here must be an element of the list. The metadata in the stored resource must be a list/container type.
A dictionary with multiple keys is treated using a logical `AND` filter. For example, `{"owner": org_id, "allowed_users": {"$contains": user_id}}` will only match resources with metadata whose "owner" is `org_id` and whose "allowed_users" list contains `user_id`.
See the reference [here](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.types.FilterType) for more information.
## Common Access Patterns
Here are some typical authorization patterns:
### Single-Owner Resources
This common pattern lets you scope all threads, assistants, crons, and runs to a single user. It's useful for common single-user use cases like regular chatbot-style apps.
```python
@auth.on
async def owner_only(ctx: Auth.types.AuthContext, value: dict):
metadata = value.setdefault("metadata", {})
metadata["owner"] = ctx.user.identity
return {"owner": ctx.user.identity}
```
### Permission-based Access
This pattern lets you control access based on **permissions**. It's useful if you want certain roles to have broader or more restricted access to resources.
```python
# In your auth handler:
@auth.authenticate
async def authenticate(headers: dict) -> Auth.types.MinimalUserDict:
...
return {
"identity": "user-123",
"is_authenticated": True,
"permissions": ["threads:write", "threads:read"] # Define permissions in auth
}
def _default(ctx: Auth.types.AuthContext, value: dict):
metadata = value.setdefault("metadata", {})
metadata["owner"] = ctx.user.identity
return {"owner": ctx.user.identity}
@auth.on.threads.create
async def create_thread(ctx: Auth.types.AuthContext, value: dict):
if "threads:write" not in ctx.permissions:
raise Auth.exceptions.HTTPException(
status_code=403,
detail="Unauthorized"
)
return _default(ctx, value)
@auth.on.threads.read
async def rbac_create(ctx: Auth.types.AuthContext, value: dict):
if "threads:read" not in ctx.permissions and "threads:write" not in ctx.permissions:
raise Auth.exceptions.HTTPException(
status_code=403,
detail="Unauthorized"
)
return _default(ctx, value)
```
## Supported Resources
LangGraph provides three levels of authorization handlers, from most general to most specific:
1. **Global Handler** (`@auth.on`): Matches all resources and actions
2. **Resource Handler** (e.g., `@auth.on.threads`, `@auth.on.assistants`, `@auth.on.crons`): Matches all actions for a specific resource
3. **Action Handler** (e.g., `@auth.on.threads.create`, `@auth.on.threads.read`): Matches a specific action on a specific resource
The most specific matching handler will be used. For example, `@auth.on.threads.create` takes precedence over `@auth.on.threads` for thread creation.
If a more specific handler is registered, the more general handler will not be called for that resource and action.
???+ tip "Type Safety"
Each handler has type hints available for its `value` parameter at `Auth.types.on.<resource>.<action>.value`. For example:
```python
@auth.on.threads.create
async def on_thread_create(
ctx: Auth.types.AuthContext,
value: Auth.types.on.threads.create.value # Specific type for thread creation
):
...
@auth.on.threads
async def on_threads(
ctx: Auth.types.AuthContext,
value: Auth.types.on.threads.value # Union type of all thread actions
):
...
@auth.on
async def on_all(
ctx: Auth.types.AuthContext,
value: dict # Union type of all possible actions
):
...
```
More specific handlers provide better type hints since they handle fewer action types.
#### Supported actions and types {#supported-actions}
Here are all the supported action handlers:
| Resource | Handler | Description | Value Type |
|----------|---------|-------------|------------|
| **Threads** | `@auth.on.threads.create` | Thread creation | [`ThreadsCreate`](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.types.ThreadsCreate) |
| | `@auth.on.threads.read` | Thread retrieval | [`ThreadsRead`](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.types.ThreadsRead) |
| | `@auth.on.threads.update` | Thread updates | [`ThreadsUpdate`](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.types.ThreadsUpdate) |
| | `@auth.on.threads.delete` | Thread deletion | [`ThreadsDelete`](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.types.ThreadsDelete) |
| | `@auth.on.threads.search` | Listing threads | [`ThreadsSearch`](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.types.ThreadsSearch) |
| | `@auth.on.threads.create_run` | Creating or updating a run | [`RunsCreate`](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.types.RunsCreate) |
| **Assistants** | `@auth.on.assistants.create` | Assistant creation | [`AssistantsCreate`](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.types.AssistantsCreate) |
| | `@auth.on.assistants.read` | Assistant retrieval | [`AssistantsRead`](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.types.AssistantsRead) |
| | `@auth.on.assistants.update` | Assistant updates | [`AssistantsUpdate`](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.types.AssistantsUpdate) |
| | `@auth.on.assistants.delete` | Assistant deletion | [`AssistantsDelete`](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.types.AssistantsDelete) |
| | `@auth.on.assistants.search` | Listing assistants | [`AssistantsSearch`](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.types.AssistantsSearch) |
| **Crons** | `@auth.on.crons.create` | Cron job creation | [`CronsCreate`](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.types.CronsCreate) |
| | `@auth.on.crons.read` | Cron job retrieval | [`CronsRead`](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.types.CronsRead) |
| | `@auth.on.crons.update` | Cron job updates | [`CronsUpdate`](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.types.CronsUpdate) |
| | `@auth.on.crons.delete` | Cron job deletion | [`CronsDelete`](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.types.CronsDelete) |
| | `@auth.on.crons.search` | Listing cron jobs | [`CronsSearch`](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.types.CronsSearch) |
???+ note "About Runs"
Runs are scoped to their parent thread for access control. This means permissions are typically inherited from the thread, reflecting the conversational nature of the data model. All run operations (reading, listing) except creation are controlled by the thread's handlers.
There is a specific `create_run` handler for creating new runs because it had more arguments that you can view in the handler.
## Next Steps
For implementation details:
- Check out the introductory tutorial on [setting up authentication](../tutorials/auth/getting_started.md)
- See the how-to guide on implementing a [custom auth handlers](../how-tos/auth/custom_auth.md)
+3 -2
View File
@@ -39,6 +39,7 @@ LangChain has no direct access to the resources created in your cloud account, a
- Read CloudWatch metrics/logs to monitor your instances/push deployment logs
- https://docs.aws.amazon.com/aws-managed-policy/latest/reference/AmazonRDSFullAccess.html
- Provision `RDS` instances for your LangGraph Cloud instances
- Alternatively, an externally managed Postgres instance can be used instead of the default `RDS` instance. LangChain does not monitor or manage the externally managed Postgres instance. See details for [`POSTGRES_URI_CUSTOM` environment variable](../cloud/reference/env_var.md#postgres_uri_custom).
2. Either
- Tags an existing vpc / subnets as `langgraph-cloud-enabled`
- Creates a new vpc and subnets and tags them as `langgraph-cloud-enabled`
@@ -50,5 +51,5 @@ LangChain has no direct access to the resources created in your cloud account, a
Notes for customers using [self-hosted LangSmith](https://docs.smith.langchain.com/self_hosting):
- Creation of new LangGraph Cloud projects and revisions currently needs to be done on smith.langchain.com.
- You can however set up the project to trace to your self-hosted LangSmith instance if desired
- Creation of new LangGraph Cloud projects and revisions currently needs to be done on `smith.langchain.com`.
- However, you can set up the project to trace to your self-hosted LangSmith instance if desired. See details for [`LANGSMITH_RUNS_ENDPOINTS` environment variable](../cloud/reference/env_var.md#langsmith_runs_endpoints).
+10 -3
View File
@@ -28,6 +28,10 @@ The guide below will explain the differences between the deployment options.
The Self-Hosted Enterprise version is only available for the **Enterprise** plan.
!!! warning "Note"
The LangGraph Platform Deployments view (within LangSmith SaaS and self-hosted LangSmith) is not available for Self-Hosted Enterprise LangGraph deployments. Self-hosted LangGraph deployments are managed externally from LangSmith (e.g. there is no UI to manage these deployments).
With a Self-Hosted Enterprise deployment, you are responsible for managing the infrastructure, including setting up and maintaining required databases and Redis instances.
Youll build a Docker image using the [LangGraph CLI](./langgraph_cli.md), which can then be deployed on your own infrastructure.
@@ -43,6 +47,10 @@ For more information, please see:
The Self-Hosted Lite version is available for all plans.
!!! warning "Note"
The LangGraph Platform Deployments view (within LangSmith SaaS and self-hosted LangSmith) is not available for Self-Hosted Lite LangGraph deployments. Self-hosted LangGraph deployments are managed externally from LangSmith (e.g. there is no UI to manage these deployments).
The Self-Hosted Lite deployment option is a free (up to 1 million nodes executed), limited version of LangGraph Platform that you can run locally or in a self-hosted manner.
With a Self-Hosted Lite deployment, you are responsible for managing the infrastructure, including setting up and maintaining required databases and Redis instances.
@@ -61,12 +69,11 @@ For more information, please see:
The Cloud SaaS version of LangGraph Platform is only available for **Plus** and **Enterprise** plans.
The [Cloud SaaS](./langgraph_cloud.md) version of LangGraph Platform is hosted as part of [LangSmith](https://smith.langchain.com/).
The Cloud SaaS version of LangGraph Platform provides a simple way to deploy and manage your LangGraph applications.
This deployment option provides an integration with GitHub, allowing you to deploy code from any of your repositories on GitHub.
This deployment option provides access to the LangGraph Platform UI (within LangSmith) and an integration with GitHub, allowing you to deploy code from any of your repositories on GitHub.
For more information, please see:
@@ -81,7 +88,7 @@ For more information, please see:
The Bring Your Own Cloud version of LangGraph Platform is only available for **Enterprise** plans.
This combines the best of both worlds for Cloud and Self-Hosted. We manage the infrastructure, so you don't have to, but the infrastructure all runs within your cloud. This is currently only available on AWS.
This combines the best of both worlds for Cloud and Self-Hosted. Create your deployments through the LangGraph Platform UI (within LangSmith) and we manage the infrastructure so you don't have to. The infrastructure all runs within your cloud. This is currently only available on AWS.
For more information please see:
+1 -1
View File
@@ -16,7 +16,7 @@ If you do not want to use LangGraph Platform, we describe the options we have im
## Reject
This is the simplest option, this just rejects any follow up runs and does not allow double texting.
This is the simplest option, this just rejects any follow-up runs and does not allow double texting.
See the [how-to guide](../cloud/how-tos/reject_concurrent.md) for configuring the reject double text option.
## Enqueue
+15 -15
View File
@@ -22,21 +22,21 @@ Yes. LangGraph is an MIT-licensed open-source library and is free to use.
LangGraph is a stateful, orchestration framework that brings added control to agent workflows. LangGraph Platform is a service for deploying and scaling LangGraph applications, with an opinionated API for building agent UXs, plus an integrated developer studio.
| Features | LangGraph (open source) | LangGraph Platform |
|----------|------------------------|-------------------|
| Description | Stateful orchestration framework for agentic applications | Scalable infrastructure for deploying LangGraph applications |
| SDKs | Python and JavaScript | Python and JavaScript |
| HTTP APIs | None | Yes - useful for retrieving & updating state or long-term memory, or creating a configurable assistant |
| Streaming | Basic | Dedicated mode for token-by-token messages |
| Checkpointer | Community contributed | Supported out-of-the-box |
| Persistence Layer | Self-managed | Managed Postgres with efficient storage |
| Deployment | Self-managed | • Cloud SaaS <br> • Free self-hosted <br> • Enterprise (BYOC or paid self-hosted) |
| Scalability | Self-managed | Auto-scaling of task queues and servers |
| Fault-tolerance | Self-managed | Automated retries |
| Concurrency Control | Simple threading | Supports double-texting |
| Scheduling | None | Cron scheduling |
| Monitoring | None | Integrated with LangSmith for observability |
| IDE integration | LangGraph Studio for Desktop | LangGraph Studio for Desktop & Cloud |
| Features | LangGraph (open source) | LangGraph Platform |
|---------------------|-----------------------------------------------------------|--------------------------------------------------------------------------------------------------------|
| Description | Stateful orchestration framework for agentic applications | Scalable infrastructure for deploying LangGraph applications |
| SDKs | Python and JavaScript | Python and JavaScript |
| HTTP APIs | None | Yes - useful for retrieving & updating state or long-term memory, or creating a configurable assistant |
| Streaming | Basic | Dedicated mode for token-by-token messages |
| Checkpointer | Community contributed | Supported out-of-the-box |
| Persistence Layer | Self-managed | Managed Postgres with efficient storage |
| Deployment | Self-managed | • Cloud SaaS <br> • Free self-hosted <br> • Enterprise (BYOC or paid self-hosted) |
| Scalability | Self-managed | Auto-scaling of task queues and servers |
| Fault-tolerance | Self-managed | Automated retries |
| Concurrency Control | Simple threading | Supports double-texting |
| Scheduling | None | Cron scheduling |
| Monitoring | None | Integrated with LangSmith for observability |
| IDE integration | LangGraph Studio for Desktop | LangGraph Studio for Desktop & Cloud |
## What are my deployment options for LangGraph Platform?
Binary file not shown.

Before

Width:  |  Height:  |  Size: 170 KiB

After

Width:  |  Height:  |  Size: 214 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 397 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 214 KiB

+1
View File
@@ -68,6 +68,7 @@ The LangGraph Platform comprises several components that work together to suppor
- [Web-hooks](./langgraph_server.md#webhooks): Webhooks allow your running LangGraph application to send data to external services on specific events.
- [Cron Jobs](./langgraph_server.md#cron-jobs): Cron jobs are a way to schedule tasks to run at specific times in your LangGraph application.
- [Double Texting](./double_texting.md): Double texting is a common issue in LLM applications where users may send multiple messages before the graph has finished running. This guide explains how to handle double texting with LangGraph Deploy.
- [Authentication & Access Control](./auth.md): Learn about options for authentication and access control when deploying the LangGraph Platform.
### Deployment Options
+3
View File
@@ -62,6 +62,9 @@ The server includes all API endpoints for your graph's runs, threads, assistants
The `langgraph dockerfile` command generates a [Dockerfile](https://docs.docker.com/reference/dockerfile/) that can be used to build images for and deploy instances of the [LangGraph API server](./langgraph_server.md). This is useful if you want to further customize the dockerfile or deploy in a more custom way.
??? note "Updating your langgraph.json file"
The `langgraph dockerfile` command translates all the configuration in your `langgraph.json` file into Dockerfile commands. When using this command, you will have to re-run it whenever you update your `langgraph.json` file. Otherwise, your changes will not be reflected when you build or run the dockerfile.
## Related
- [LangGraph CLI API Reference](../cloud/reference/cli.md)
+1 -1
View File
@@ -19,7 +19,7 @@ See the [how-to guide](../cloud/deployment/cloud.md#create-new-deployment) for c
| **Deployment Type** | **CPU** | **Memory** | **Scaling** |
|---------------------|---------|------------|---------------------|
| Development | 1 CPU | 1 GB | Up to 1 container |
| Production | 1 CPU | 2 GB | Up to 10 containers |
| Production | 2 CPU | 2 GB | Up to 10 containers |
## Autoscaling
`Production` type deployments automatically scale up to 10 containers. Scaling is based on the current request load for a single container. Specifically, the autoscaling implementation scales the deployment so that each container is processing about 10 concurrent requests. For example...
+1 -26
View File
@@ -18,32 +18,7 @@ The LangGraph Platform offers a few different deployment options described in th
## Why Use LangGraph Platform?
LangGraph Platform is designed to make deploying agentic applications seamless and production-ready.
For simpler applications, deploying a LangGraph agent can be as straightforward as using your own server logic—for example, setting up a FastAPI endpoint and invoking LangGraph directly.
### Option 1: Deploying with Custom Server Logic
For basic LangGraph applications, you may choose to handle deployment using your custom server infrastructure. Setting up endpoints with frameworks like [FastAPI](https://fastapi.tiangolo.com/) allows you to quickly deploy and run LangGraph as you would any other Python application:
```python
from fastapi import FastAPI
from your_agent_package import graph
app = FastAPI()
@app.get("/foo")
async def foo(...):
return await graph.ainvoke({...})
```
This approach works well for simple applications with straightforward needs and provides you with full control over the deployment setup. For example, you might use this for a single-assistant application that doesnt require long-running sessions or persistent memory.
### Option 2: Leveraging LangGraph Platform for Complex Deployments
As your applications scale or add complex features, the deployment requirements often evolve. Running an application with more nodes, longer processing times, or a need for persistent memory can introduce challenges that quickly become time-consuming and difficult to manage manually. [LangGraph Platform](./langgraph_platform.md) is built to handle these challenges seamlessly, allowing you to focus on agent logic rather than server infrastructure.
Here are some common issues that arise in complex deployments, which LangGraph Platform addresses:
**LangGraph Platform** handles common issues that arise when deploying LLM applications to production, allowing you to focus on agent logic instead of managing server infrastructure.
- **[Streaming Support](streaming.md)**: As agents grow more sophisticated, they often benefit from streaming both token outputs and intermediate states back to the user. Without this, users are left waiting for potentially long operations with no feedback. LangGraph Server provides [multiple streaming modes](streaming.md) optimized for various application needs.
+13 -10
View File
@@ -25,25 +25,28 @@ The key features of LangGraph Studio are:
## Types
### Desktop app
### Development server with web UI
LangGraph Studio is available as a [desktop app](https://studio.langchain.com/) for MacOS users.
You can [run a local in-memory development server](../tutorials/langgraph-platform/local-server.md) that can be used to connect a local LangGraph app with a web version of the studio.
For example, if you start the local server with `langgraph dev` (running at `http://127.0.0.1:2024` by default), you can connect to the studio by navigating to:
While in Beta, LangGraph Studio is available for free to all [LangSmith](https://smith.langchain.com/) users on any plan tier.
```
https://smith.langchain.com/studio/?baseUrl=http://127.0.0.1:2024
```
See [instructions here](../cloud/reference/cli.md#dev) for more information.
The web UI version of the studio will connect to your locally running server — your agent is still running locally and never leaves your device.
### Cloud studio
If you have deployed your LangGraph application on LangGraph Platform (Cloud), you can access the studio as part of that
### Development server
### Desktop app
LangGraph CLI also contains a command for running an in-memory development server that can be used to connect a local LangGraph app with the studio.
See [instructions here](../cloud/reference/cli.md#dev) for more information.
LangGraph Studio is available as a [desktop app](https://studio.langchain.com/) for MacOS users.
The way this works is that it runs inside your local environment.
It will spin up an in-memory, development server to deploy the graph.
You can then connect to the studio via the Cloud hosted version of LangGraph Platform.
To be clear, the web studio will connect to your locally running server - your agent is still running locally and never leaves your device.
While in Beta, LangGraph Studio is available for free to all [LangSmith](https://smith.langchain.com/) users on any plan tier.
## Studio FAQs
+1 -1
View File
@@ -191,7 +191,7 @@ class State(MessagesState):
## Nodes
In LangGraph, nodes are typically python functions (sync or `async`) where the **first** positional argument is the [state](#state), and (optionally), the **second** positional argument is a "config", containing optional [configurable parameters](#configuration) (such as a `thread_id`).
In LangGraph, nodes are typically python functions (sync or async) where the **first** positional argument is the [state](#state), and (optionally), the **second** positional argument is a "config", containing optional [configurable parameters](#configuration) (such as a `thread_id`).
Similar to `NetworkX`, you add these nodes to a graph using the [add_node][langgraph.graph.StateGraph.add_node] method:
+8 -8
View File
@@ -94,13 +94,13 @@ This is a special case of updating the graph state from tools where in addition
!!! important
If you want to use tools that return `Command`, you can either use prebuilt [`create_react_agent`][langgraph.prebuilt.chat_agent_executor.create_react_agent] / [`ToolNode`][langgraph.prebuilt.tool_node.ToolNode] components, or implement your own tool-executing node that collects `Command` objects returned by the tools and returns a list of them, e.g.:
```python
def call_tools(state):
...
commands = [tools_by_name[tool_call["name"]].invoke(tool_call) for tool_call in tool_calls]
return commands
```
```python
def call_tools(state):
...
commands = [tools_by_name[tool_call["name"]].invoke(tool_call) for tool_call in tool_calls]
return commands
```
Let's now take a closer look at the different multi-agent architectures.
@@ -112,7 +112,7 @@ In this architecture, agents are defined as graph nodes. Each agent can communic
```python
from typing import Literal
from langchain_openai import ChatOpenAI
from langgraph.graph import StateGraph, MessagesState, START
from langgraph.graph import StateGraph, MessagesState, START, END
model = ChatOpenAI()
+6 -9
View File
@@ -147,24 +147,21 @@ In our example, the output of `get_state_history` will look like this:
### Replay
It's also possible to play-back a prior graph execution. If we `invoking` a graph with a `thread_id` and a `checkpoint_id`, then we will *re-play* the graph from a checkpoint that corresponds to the `checkpoint_id`.
It's also possible to play-back a prior graph execution. If we `invoke` a graph with a `thread_id` and a `checkpoint_id`, then we will *re-play* the previously executed steps _before_ a checkpoint that corresponds to the `checkpoint_id`, and only execute the steps _after_ the checkpoint.
* `thread_id` is simply the ID of a thread. This is always required.
* `checkpoint_id` This identifier refers to a specific checkpoint within a thread.
* `thread_id` is the ID of a thread.
* `checkpoint_id` is an identifier that refers to a specific checkpoint within a thread.
You must pass these when invoking the graph as part of the `configurable` portion of the config:
```python
# {"configurable": {"thread_id": "1"}} # valid config
# {"configurable": {"thread_id": "1", "checkpoint_id": "0c62ca34-ac19-445d-bbb0-5b4984975b2a"}} # also valid config
config = {"configurable": {"thread_id": "1"}}
config = {"configurable": {"thread_id": "1", "checkpoint_id": "0c62ca34-ac19-445d-bbb0-5b4984975b2a"}}
graph.invoke(None, config=config)
```
Importantly, LangGraph knows whether a particular checkpoint has been executed previously. If it has, LangGraph simply *re-plays* that particular step in the graph and does not re-execute the step. See this [how to guide on time-travel to learn more about replaying](../how-tos/human_in_the_loop/time-travel.ipynb).
Importantly, LangGraph knows whether a particular step has been executed previously. If it has, LangGraph simply *re-plays* that particular step in the graph and does not re-execute the step, but only for the steps _before_ the provided `checkpoint_id`. All of the steps _after_ `checkpoint_id` will be executed (i.e., a new fork), even if they have been executed previously. See this [how to guide on time-travel to learn more about replaying](../how-tos/human_in_the_loop/time-travel.ipynb).
![Replay](img/persistence/re_play.jpg)
![Replay](img/persistence/re_play.png)
### Update state
+4
View File
@@ -32,6 +32,10 @@ To use the Self-Hosted Enterprise version, you must acquire a license key that y
- Build the docker image for [LangGraph Server](./langgraph_server.md) using the [LangGraph CLI](./langgraph_cli.md).
- Deploy a web server that will run the docker image and pass in the necessary environment variables.
!!! warning "Note"
The LangGraph Platform Deployments view (within LangSmith SaaS and self-hosted LangSmith) is not available for Self-Hosted Lite or Self-Hosted Enterprise LangGraph deployments. Self-hosted LangGraph deployments are managed externally from LangSmith (e.g. there is no UI to manage these deployments).
For step-by-step instructions, see [How to set up a self-hosted deployment of LangGraph](../how-tos/deploy-self-hosted.md).
## Helm Chart
+1 -1
View File
@@ -12,7 +12,7 @@ You can create an application from a template using the LangGraph CLI.
## Install the LangGraph CLI
```bash
pip install "langgraph-cli[inmem]==0.1.58" python-dotenv
pip install "langgraph-cli[inmem]" --upgrade
```
## Available Templates
+3 -11
View File
@@ -17,17 +17,9 @@ We call these debugging techniques **Time Travel**, composed of two key actions:
![](./img/human_in_the_loop/replay.png)
Replaying allows us to revisit and reproduce an agent's past actions. This can be done either from the current state (or checkpoint) of the graph or from a specific checkpoint.
Replaying allows us to revisit and reproduce an agent's past actions, up to and including a specific step (checkpoint).
To replay from the current state, simply pass `None` as the input along with a `thread`:
```python
thread = {"configurable": {"thread_id": "1"}}
for event in graph.stream(None, thread, stream_mode="values"):
print(event)
```
To replay actions from a specific checkpoint, start by retrieving all checkpoints for the thread:
To replay actions before a specific checkpoint, start by retrieving all checkpoints for the thread:
```python
all_checkpoints = []
@@ -43,7 +35,7 @@ for event in graph.stream(None, config, stream_mode="values"):
print(event)
```
The graph efficiently replays previously executed nodes instead of re-executing them, leveraging its awareness of prior checkpoint executions.
The graph replays previously executed steps _before_ the provided `checkpoint_id` and executes the steps _after_ `checkpoint_id` (i.e., a new fork), even if they have been executed previously.
## Forking
File diff suppressed because it is too large Load Diff
+140
View File
@@ -0,0 +1,140 @@
# How to add custom authentication
!!! tip "Prerequisites"
This guide assumes familiarity with the following concepts:
* [**Authentication & Access Control**](../../concepts/auth.md)
* [**LangGraph Platform**](../../concepts/index.md#langgraph-platform)
For a more guided walkthrough, see [**setting up custom authentication**](../../tutorials/auth/getting_started.md) tutorial.
???+ note "Python only"
We currently only support custom authentication and authorization in Python deployments with `langgraph-api>=0.0.11`. Support for LangGraph.JS will be added soon.
This guide shows how to add custom authentication to your LangGraph Platform application. This guide applies to both LangGraph Cloud, BYOC, and self-hosted deployments. It does not apply to isolated usage of the LangGraph open source library in your own custom server.
## 1. Implement authentication
Create `auth.py` file, with a basic JWT authentication handler:
```python
from langgraph_sdk import Auth
my_auth = Auth()
@my_auth.authenticate
async def authenticate(authorization: str) -> str:
token = authorization.split(" ", 1)[-1] # "Bearer <token>"
try:
# Verify token with your auth provider
user_id = await verify_token(token)
return user_id
except Exception:
raise Auth.exceptions.HTTPException(
status_code=401,
detail="Invalid token"
)
# Add authorization rules to actually control access to resources
@my_auth.on
async def add_owner(
ctx: Auth.types.AuthContext,
value: dict,
):
"""Add owner to resource metadata and filter by owner."""
filters = {"owner": ctx.user.identity}
metadata = value.setdefault("metadata", {})
metadata.update(filters)
return filters
# Assumes you organize information in store like (user_id, resource_type, resource_id)
@my_auth.on.store()
async def authorize_store(ctx: Auth.types.AuthContext, value: dict):
namespace: tuple = value["namespace"]
assert namespace[0] == ctx.user.identity, "Not authorized"
```
## 2. Update configuration
In your `langgraph.json`, add the path to your auth file:
```json hl_lines="7-9"
{
"dependencies": ["."],
"graphs": {
"agent": "./agent.py:graph"
},
"env": ".env",
"auth": {
"path": "./auth.py:my_auth"
}
}
```
## 3. Connect from the client
Once you've set up authentication in your server, requests must include the the required authorization information based on your chosen scheme.
Assuming you are using JWT token authentication, you could access your deployments using any of the following methods:
=== "Python Client"
```python
from langgraph_sdk import get_client
my_token = "your-token" # In practice, you would generate a signed token with your auth provider
client = get_client(
url="http://localhost:2024",
headers={"Authorization": f"Bearer {my_token}"}
)
threads = await client.threads.search()
```
=== "Python RemoteGraph"
```python
from langgraph.pregel.remote import RemoteGraph
my_token = "your-token" # In practice, you would generate a signed token with your auth provider
remote_graph = RemoteGraph(
"agent",
url="http://localhost:2024",
headers={"Authorization": f"Bearer {my_token}"}
)
threads = await remote_graph.ainvoke(...)
```
=== "JavaScript Client"
```javascript
import { Client } from "@langchain/langgraph-sdk";
const my_token = "your-token"; // In practice, you would generate a signed token with your auth provider
const client = new Client({
apiUrl: "http://localhost:2024",
headers: { Authorization: `Bearer ${my_token}` },
});
const threads = await client.threads.search();
```
=== "JavaScript RemoteGraph"
```javascript
import { RemoteGraph } from "@langchain/langgraph/remote";
const my_token = "your-token"; // In practice, you would generate a signed token with your auth provider
const remoteGraph = new RemoteGraph({
graphId: "agent",
url: "http://localhost:2024",
headers: { Authorization: `Bearer ${my_token}` },
});
const threads = await remoteGraph.invoke(...);
```
=== "CURL"
```bash
curl -H "Authorization: Bearer ${your-token}" http://localhost:2024/threads
```
@@ -0,0 +1,98 @@
# How to document API authentication in OpenAPI
This guide shows how to customize the OpenAPI security schema for your LangGraph Platform API documentation. A well-documented security schema helps API consumers understand how to authenticate with your API and even enables automatic client generation. See the [Authentication & Access Control conceptual guide](../../concepts/auth.md) for more details about LangGraph's authentication system.
!!! note "Implementation vs Documentation"
This guide only covers how to document your security requirements in OpenAPI. To implement the actual authentication logic, see [How to add custom authentication](./custom_auth.md).
This guide applies to all LangGraph Platform deployments (Cloud, BYOC, and self-hosted). It does not apply to usage of the LangGraph open source library if you are not using LangGraph Platform.
## Default Schema
The default security scheme varies by deployment type:
=== "LangGraph Cloud"
By default, LangGraph Cloud requires a LangSmith API key in the `x-api-key` header:
```yaml
components:
securitySchemes:
apiKeyAuth:
type: apiKey
in: header
name: x-api-key
security:
- apiKeyAuth: []
```
When using one of the LangGraph SDK's, this can be inferred from environment variables.
=== "Self-hosted"
By default, self-hosted deployments have no security scheme. This means they are to be deployed only on a secured network or with authentication. To add custom authentication, see [How to add custom authentication](./custom_auth.md).
## Custom Security Schema
To customize the security schema in your OpenAPI documentation, add an `openapi` field to your `auth` configuration in `langgraph.json`. Remember that this only updates the API documentation - you must also implement the corresponding authentication logic as shown in [How to add custom authentication](./custom_auth.md).
Note that LangGraph Platform does not provide authentication endpoints - you'll need to handle user authentication in your client application and pass the resulting credentials to the LangGraph API.
=== "OAuth2 with Bearer Token"
```json
{
"auth": {
"path": "./auth.py:my_auth", // Implement auth logic here
"openapi": {
"securitySchemes": {
"OAuth2": {
"type": "oauth2",
"flows": {
"implicit": {
"authorizationUrl": "https://your-auth-server.com/oauth/authorize",
"scopes": {
"me": "Read information about the current user",
"threads": "Access to create and manage threads"
}
}
}
}
},
"security": [
{"OAuth2": ["me", "threads"]}
]
}
}
}
```
=== "API Key"
```json
{
"auth": {
"path": "./auth.py:my_auth", // Implement auth logic here
"openapi": {
"securitySchemes": {
"apiKeyAuth": {
"type": "apiKey",
"in": "header",
"name": "X-API-Key"
}
},
"security": [
{"apiKeyAuth": []}
]
}
}
}
```
## Testing
After updating your configuration:
1. Deploy your application
2. Visit `/docs` to see the updated OpenAPI documentation
3. Try out the endpoints using credentials from your authentication server (make sure you've implemented the authentication logic first)
+1
View File
@@ -29,6 +29,7 @@ You will eventually need to pass in the following environment variables to the L
- `DATABASE_URI`: Postgres connection details. Postgres will be used to store assistants, threads, runs, persist thread state and long term memory, and to manage the state of the background task queue with 'exactly once' semantics.
- `LANGSMITH_API_KEY`: (If using [Self-Hosted Lite](../concepts/deployment_options.md#self-hosted-lite)) LangSmith API key. This will be used to authenticate ONCE at server start up.
- `LANGGRAPH_CLOUD_LICENSE_KEY`: (If using [Self-Hosted Enterprise](../concepts/deployment_options.md#self-hosted-enterprise)) LangGraph Platform license key. This will be used to authenticate ONCE at server start up.
- `LANGCHAIN_ENDPOINT`: To send traces to a [self-hosted LangSmith](https://docs.smith.langchain.com/self_hosting) instance, set `LANGCHAIN_ENDPOINT` to the hostname of the self-hosted LangSmith instance.
## Build the Docker Image
+7
View File
@@ -108,6 +108,7 @@ These how-to guides show common patterns for tool calling with LangGraph:
[Multi-agent systems](../concepts/multi_agent.md) are useful to break down complex LLM applications into multiple agents, each responsible for a different part of the application. These how-to guides show how to implement multi-agent systems in LangGraph:
- [How to implement handoffs between agents](agent-handoffs.ipynb)
- [How to build a multi-agent network](multi-agent-network.ipynb)
- [How to add multi-turn conversation in a multi-agent application](multi-agent-multi-turn-convo.ipynb)
@@ -179,6 +180,11 @@ LangGraph applications can be deployed using LangGraph Cloud, which provides a r
- [How to deploy to a self-hosted environment](./deploy-self-hosted.md)
- [How to interact with the deployment using RemoteGraph](./use-remote-graph.md)
### Authentication & Access Control
- [How to add custom authentication](./auth/custom_auth.md)
- [How to update the security schema of your OpenAPI spec](./auth/openapi_security.md)
### Assistants
[Assistants](../concepts/assistants.md) is a configured instance of a template.
@@ -247,6 +253,7 @@ LangGraph Studio is a built-in UI for visualizing, testing, and debugging your a
- [How to connect to a local deployment (Docker)](../cloud/how-tos/test_local_deployment.md)
- [How to test your graph in LangGraph Studio (MacOS only)](../cloud/how-tos/invoke_studio.md)
- [How to interact with threads in LangGraph Studio](../cloud/how-tos/threads_studio.md)
- [How to add nodes as dataset examples in LangGraph Studio](../cloud/how-tos/datasets_studio.md)
## Troubleshooting
+8 -2
View File
@@ -6,8 +6,8 @@ This guide shows you how to connect your local agent to [LangGraph Studio](../co
There are two ways to connect your local agent to LangGraph Studio:
- [Development Server](../concepts/langgraph_studio.md#development-server-with-web-ui): Python package, all platforms, no Docker
- [LangGraph Desktop](../concepts/langgraph_studio.md#desktop-app): Application, Mac only, requires Docker
- [Development Server](../concepts/langgraph_studio.md#dev-server): Python package, all platforms, no Docker
In this guide we will cover how to use the development server as that is generally an easier and better experience.
@@ -22,8 +22,14 @@ See [this guide](../concepts/application_structure.md) for information on how to
You will need to install [`langgraph-cli`](../cloud/reference/cli.md#langgraph-cli) (version `0.1.55` or higher).
You will need to make sure to install the `inmem` extras.
???+ note "Minimum version"
The minimum version to use the `inmem` extra with `langgraph-cli` is `0.1.55`.
Python 3.11 or higher is required.
```shell
pip install "langgraph-cli[inmem]==0.1.55"
pip install -U "langgraph-cli[inmem]"
```
## Run the development server
@@ -208,7 +208,7 @@
"from typing import Optional\n",
"\n",
"from langchain.chat_models import init_chat_model\n",
"from langchain_core.tools import InjectedToolArg\n",
"from langgraph.prebuilt import InjectedStore\n",
"from langgraph.store.base import BaseStore\n",
"from typing_extensions import Annotated\n",
"\n",
@@ -232,7 +232,7 @@
" content: str,\n",
" *,\n",
" memory_id: Optional[uuid.UUID] = None,\n",
" store: Annotated[BaseStore, InjectedToolArg],\n",
" store: Annotated[BaseStore, InjectedStore],\n",
"):\n",
" \"\"\"Upsert a memory in the database.\"\"\"\n",
" # The LLM can use this tool to store a new memory\n",
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+4 -1
View File
@@ -42,7 +42,10 @@
"checkpointer = # postgres checkpointer (see examples below)\n",
"graph = builder.compile(checkpointer=checkpointer)\n",
"...\n",
"```"
"```\n",
"\n",
"!!! info \"Setup\n",
" You need to run `.setup()` once on your checkpointer to initialize the database before you can use it."
]
},
{
@@ -225,9 +225,19 @@
"# Define the function that responds to the user\n",
"def respond(state: AgentState):\n",
" # Construct the final answer from the arguments of the last tool call\n",
" response = WeatherResponse(**state[\"messages\"][-1].tool_calls[0][\"args\"])\n",
" weather_tool_call = state[\"messages\"][-1].tool_calls[0]\n",
" response = WeatherResponse(**weather_tool_call[\"args\"])\n",
" # Since we're using tool calling to return structured output,\n",
" # we need to add a tool message corresponding to the WeatherResponse tool call,\n",
" # This is due to LLM providers' requirement that AI messages with tool calls\n",
" # need to be followed by a tool message for each tool call\n",
" tool_message = {\n",
" \"type\": \"tool\",\n",
" \"content\": \"Here is your structured response\",\n",
" \"tool_call_id\": weather_tool_call[\"id\"],\n",
" }\n",
" # We return the final answer\n",
" return {\"final_response\": response}\n",
" return {\"final_response\": response, \"messages\": [tool_message]}\n",
"\n",
"\n",
"# Define the function that determines whether to continue or not\n",
@@ -466,7 +476,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.4"
"version": "3.12.3"
}
},
"nbformat": 4,
+1 -1
View File
@@ -6,7 +6,7 @@
"source": [
"# How to call tools using ToolNode\n",
"\n",
"This guide covers how to use LangGraph's prebuilt [`ToolNode`](https://langchain-ai.github.io/langgraph/reference/prebuilt/#toolnode) for tool calling.\n",
"This guide covers how to use LangGraph's prebuilt [`ToolNode`](https://langchain-ai.github.io/langgraph/reference/prebuilt/#langgraph.prebuilt.tool_node.ToolNode) for tool calling.\n",
"\n",
"`ToolNode` is a LangChain Runnable that takes graph state (with a list of messages) as input and outputs state update with the result of tool calls. It is designed to work well out-of-box with LangGraph's prebuilt [ReAct agent](https://langchain-ai.github.io/langgraph/how-tos/create-react-agent/), but can also work with any `StateGraph` as long as its state has a `messages` key with an appropriate reducer (see [`MessagesState`](https://github.com/langchain-ai/langgraph/blob/e3ef9adac7395e5c0943c22bbc8a4a856b103aa3/libs/langgraph/langgraph/graph/message.py#L150))."
]
@@ -166,8 +166,6 @@
"\n",
"\n",
"class State(AgentState):\n",
" # user provided\n",
" last_name: str\n",
" # updated by the tool\n",
" user_info: dict[str, Any]\n",
"\n",
@@ -1,6 +1,6 @@
# MULTIPLE_SUBGRAPHS
You are calling the same subgraph multiple times within a single LangGraph node with checkpointing enabled for each subgraph.
You are calling subgraphs multiple times within a single LangGraph node with checkpointing enabled for each subgraph.
This is currently not allowed due to internal restrictions on how checkpoint namespacing for subgraphs works.
@@ -9,4 +9,4 @@ This is currently not allowed due to internal restrictions on how checkpoint nam
The following may help resolve this error:
- If you don't need to interrupt/resume from a subgraph, pass `checkpointer=False` when compiling it like this: `.compile(checkpointer=False)`
- Don't imperatively call graphs multiple times in the same node, and instead use the [`Send`](https://langchain-ai.github.io/langgraph/concepts/low_level/#send) API.
- Don't imperatively call graphs multiple times in the same node, and instead use the [`Send`](https://langchain-ai.github.io/langgraph/concepts/low_level/#send) API.
+291
View File
@@ -0,0 +1,291 @@
# Connecting an Authentication Provider (Part 3/3)
!!! note "This is part 3 of our authentication series:"
1. [Basic Authentication](getting_started.md) - Control who can access your bot
2. [Resource Authorization](resource_auth.md) - Let users have private conversations
3. Production Auth (you are here) - Add real user accounts and validate using OAuth2
In the [Making Conversations Private](resource_auth.md) tutorial, we added [resource authorization](../../concepts/auth.md#resource-authorization) to give users private conversations. However, we were still using hard-coded tokens for authentication, which is not secure. Now we'll replace those tokens with real user accounts using [OAuth2](../../concepts/auth.md#oauth2-authentication).
We'll keep the same [`Auth`](../../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.Auth) object and [resource-level access control](../../concepts/auth.md#resource-level-access-control), but upgrade our authentication to use Supabase as our identity provider. While we use Supabase in this tutorial, the concepts apply to any OAuth2 provider. You'll learn how to:
1. Replace test tokens with real [JWT tokens](../../concepts/auth.md#jwt-tokens)
2. Integrate with OAuth2 providers for secure user authentication
3. Handle user sessions and metadata while maintaining our existing authorization logic
## Requirements
You will need to set up a Supabase project to use its authentication server for this tutorial. You can do so [here](https://supabase.com/dashboard).
## Background
OAuth2 involves three main roles:
1. **Authorization server**: The identity provider (e.g., Supabase, Auth0, Google) that handles user authentication and issues tokens
2. **Application backend**: Your LangGraph application. This validates tokens and serves protected resources (conversation data)
3. **Client application**: The web or mobile app where users interact with your service
A standard OAuth2 flow works something like this:
```mermaid
sequenceDiagram
participant User
participant Client
participant AuthServer
participant LangGraph Backend
User->>Client: Initiate login
User->>AuthServer: Enter credentials
AuthServer->>Client: Send tokens
Client->>LangGraph Backend: Request with token
LangGraph Backend->>AuthServer: Validate token
AuthServer->>LangGraph Backend: Token valid
LangGraph Backend->>Client: Serve request (e.g., run agent or graph)
```
In the following example, we'll use Supabase as our auth server. The LangGraph application will provide the backend for your app, and we will write test code for the client app.
Let's get started!
## Setting Up Authentication Provider {#setup-auth-provider}
First, let's install the required dependencies. Start in your `custom-auth` directory and ensure you have the `langgraph-cli` installed:
```bash
cd custom-auth
pip install -U "langgraph-cli[inmem]"
```
Next, we'll need to fech the URL of our auth server and the private key for authentication.
Since we're using Supabase for this, we can do this in the Supabase dashboard:
1. In the left sidebar, click on t️⚙ Project Settings" and then click "API"
2. Copy your project URL and add it to your `.env` file
```shell
echo "SUPABASE_URL=your-project-url" >> .env
```
3. Next, copy your service role secret key and add it to your `.env` file
```shell
echo "SUPABASE_SERVICE_KEY=your-service-role-key" >> .env
```
4. Finally, copy your "anon public" key and note it down. This will be used later when we set up our client code.
```bash
SUPABASE_URL=your-project-url
SUPABASE_SERVICE_KEY=your-service-role-key
```
## Implementing Token Validation
In the previous tutorials, we used the [`Auth`](../../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.Auth) object to:
1. Validate hard-coded tokens in the [authentication tutorial](getting_started.md)
2. Add resource ownership in the [authorization tutorial](resource_auth.md)
Now we'll upgrade our authentication to validate real JWT tokens from Supabase. The key changes will all be in the [`@auth.authenticate`](../../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.Auth.authenticate) decorated function:
1. Instead of checking against a hard-coded list of tokens, we'll make an HTTP request to Supabase to validate the token
2. We'll extract real user information (ID, email) from the validated token
And we'll keep our existing resource authorization logic unchanged
Let's update `src/security/auth.py` to implement this:
```python hl_lines="8-9 20-30" title="src/security/auth.py"
import os
import httpx
from langgraph_sdk import Auth
auth = Auth()
# This is loaded from the `.env` file you created above
SUPABASE_URL = os.environ["SUPABASE_URL"]
SUPABASE_SERVICE_KEY = os.environ["SUPABASE_SERVICE_KEY"]
@auth.authenticate
async def get_current_user(authorization: str | None):
"""Validate JWT tokens and extract user information."""
assert authorization
scheme, token = authorization.split()
assert scheme.lower() == "bearer"
try:
# Verify token with auth provider
async with httpx.AsyncClient() as client:
response = await client.get(
f"{SUPABASE_URL}/auth/v1/user",
headers={
"Authorization": authorization,
"apiKey": SUPABASE_SERVICE_KEY,
},
)
assert response.status_code == 200
user = response.json()
return {
"identity": user["id"], # Unique user identifier
"email": user["email"],
"is_authenticated": True,
}
except Exception as e:
raise Auth.exceptions.HTTPException(status_code=401, detail=str(e))
# ... the rest is the same as before
# Keep our resource authorization from the previous tutorial
@auth.on
async def add_owner(ctx, value):
"""Make resources private to their creator using resource metadata."""
filters = {"owner": ctx.user.identity}
metadata = value.setdefault("metadata", {})
metadata.update(filters)
return filters
```
The most important change is that we're now validating tokens with a real authentication server. Our authentication handler has the private key for our Supabase project, which we can use to validate the user's token and extract their information.
Let's test this with a real user account!
## Testing Authentication Flow
Let's test out our new authentication flow. You can run the following code in a file or notebook. You will need to provide:
- A valid email address
- A Supabase project URL (from [above](#setup-auth-provider))
- A Supabase anon **public key** (also from [above](#setup-auth-provider))
```python
import os
import httpx
from getpass import getpass
from langgraph_sdk import get_client
# Get email from command line
email = getpass("Enter your email: ")
base_email = email.split("@")
password = "secure-password" # CHANGEME
email1 = f"{base_email[0]}+1@{base_email[1]}"
email2 = f"{base_email[0]}+2@{base_email[1]}"
SUPABASE_URL = os.environ.get("SUPABASE_URL")
if not SUPABASE_URL:
SUPABASE_URL = getpass("Enter your Supabase project URL: ")
# This is your PUBLIC anon key (which is safe to use client-side)
# Do NOT mistake this for the secret service role key
SUPABASE_ANON_KEY = os.environ.get("SUPABASE_ANON_KEY")
if not SUPABASE_ANON_KEY:
SUPABASE_ANON_KEY = getpass("Enter your public Supabase anon key: ")
async def sign_up(email: str, password: str):
"""Create a new user account."""
async with httpx.AsyncClient() as client:
response = await client.post(
f"{SUPABASE_URL}/auth/v1/signup",
json={"email": email, "password": password},
headers={"apiKey": SUPABASE_ANON_KEY},
)
assert response.status_code == 200
return response.json()
# Create two test users
print(f"Creating test users: {email1} and {email2}")
await sign_up(email1, password)
await sign_up(email2, password)
```
Then run the code.
!!! tip "About test emails"
We'll create two test accounts by adding "+1" and "+2" to your email. For example, if you use "myemail@gmail.com", we'll create "myemail+1@gmail.com" and "myemail+2@gmail.com". All emails will be delivered to your original address.
⚠️ Before continuing: Check your email and click both confirmation links. Supabase will will reject `/login` requests until after you have confirmed your users' email.
Now let's test that users can only see their own data. Make sure the server is running (run `langgraph dev`) before proceeding. The following snippet requires the "anon public" key that you copied from the Supabase dashboard while [setting up the auth provider](#setup-auth-provider) previously.
```python
async def login(email: str, password: str):
"""Get an access token for an existing user."""
async with httpx.AsyncClient() as client:
response = await client.post(
f"{SUPABASE_URL}/auth/v1/token?grant_type=password",
json={
"email": email,
"password": password
},
headers={
"apikey": SUPABASE_ANON_KEY,
"Content-Type": "application/json"
},
)
assert response.status_code == 200
return response.json()["access_token"]
# Log in as user 1
user1_token = await login(email1, password)
user1_client = get_client(
url="http://localhost:2024", headers={"Authorization": f"Bearer {user1_token}"}
)
# Create a thread as user 1
thread = await user1_client.threads.create()
print(f"✅ User 1 created thread: {thread['thread_id']}")
# Try to access without a token
unauthenticated_client = get_client(url="http://localhost:2024")
try:
await unauthenticated_client.threads.create()
print("❌ Unauthenticated access should fail!")
except Exception as e:
print("✅ Unauthenticated access blocked:", e)
# Try to access user 1's thread as user 2
user2_token = await login(email2, password)
user2_client = get_client(
url="http://localhost:2024", headers={"Authorization": f"Bearer {user2_token}"}
)
try:
await user2_client.threads.get(thread["thread_id"])
print("❌ User 2 shouldn't see User 1's thread!")
except Exception as e:
print("✅ User 2 blocked from User 1's thread:", e)
```
The output should look like this:
```shell
✅ User 1 created thread: d6af3754-95df-4176-aa10-dbd8dca40f1a
✅ Unauthenticated access blocked: Client error '403 Forbidden' for url 'http://localhost:2024/threads'
✅ User 2 blocked from User 1's thread: Client error '404 Not Found' for url 'http://localhost:2024/threads/d6af3754-95df-4176-aa10-dbd8dca40f1a'
```
Perfect! Our authentication and authorization are working together:
1. Users must log in to access the bot
2. Each user can only see their own threads
All our users are managed by the Supabase auth provider, so we don't need to implement any additional user management logic.
## Congratulations! 🎉
You've successfully built a production-ready authentication system for your LangGraph application! Let's review what you've accomplished:
1. Set up an authentication provider (Supabase in this case)
2. Added real user accounts with email/password authentication
3. Integrated JWT token validation into your LangGraph server
4. Implemented proper authorization to ensure users can only access their own data
5. Created a foundation that's ready to handle your next authentication challenge 🚀
This completes our authentication tutorial series. You now have the building blocks for a secure, production-ready LangGraph application.
## What's Next?
Now that you have production authentication, consider:
1. Building a web UI with your preferred framework (see the [Custom Auth](https://github.com/langchain-ai/custom-auth) template for an example)
2. Learn more about the other aspects of authentication and authorization in the [conceptual guide on authentication](../../concepts/auth.md).
3. Customize your handlers and setup further after reading the [reference docs](../../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.Auth).
+185
View File
@@ -0,0 +1,185 @@
# Setting up Custom Authentication (Part 1/3)
!!! note "This is part 1 of our authentication series:"
1. Basic Authentication (you are here) - Control who can access your bot
2. [Resource Authorization](resource_auth.md) - Let users have private conversations
3. [Production Auth](add_auth_server.md) - Add real user accounts and validate using OAuth2
!!! tip "Prerequisites"
This guide assumes basic familiarity with the following concepts:
* [**Authentication & Access Control**](../../concepts/auth.md)
* [**LangGraph Platform**](../../concepts/index.md#langgraph-platform)
!!! note "Python only"
We currently only support custom authentication and authorization in Python deployments with `langgraph-api>=0.0.11`. Support for LangGraph.JS will be added soon.
In this tutorial, we will build a chatbot that only lets specific users access it. We'll start with the LangGraph template and add token-based security step by step. By the end, you'll have a working chatbot that checks for valid tokens before allowing access.
## Setting up our project
First, let's create a new chatbot using the LangGraph starter template:
```bash
pip install -U "langgraph-cli[inmem]"
langgraph new --template=new-langgraph-project-python custom-auth
cd custom-auth
```
The template gives us a placeholder LangGraph app. Let's try it out by installing the local dependencies and running the development server.
```shell
pip install -e .
langgraph dev
```
If everything works, the server should start and open the studio in your browser.
> - 🚀 API: http://127.0.0.1:2024
> - 🎨 Studio UI: https://smith.langchain.com/studio/?baseUrl=http://127.0.0.1:2024
> - 📚 API Docs: http://127.0.0.1:2024/docs
>
> This in-memory server is designed for development and testing.
> For production use, please use LangGraph Cloud.
The graph should run, and if you were to self-host this on the public internet, anyone could access it!
![No auth](./img/no_auth.png)
Now that we've seen the base LangGraph app, let's add authentication to it!
???+ tip "Placeholder token"
In part 1, we will start with a hard-coded token for illustration purposes.
We will get to a "production-ready" authentication scheme in part 3, after mastering the basics.
## Adding Authentication
The [`Auth`](../../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.Auth) object lets you register an authentication function that the LangGraph platform will run on every request. This function receives each request and decides whether to accept or reject.
Create a new file `src/security/auth.py`. This is where our code will live to check if users are allowed to access our bot:
```python hl_lines="10 15-16" title="src/security/auth.py"
from langgraph_sdk import Auth
# This is our toy user database. Do not do this in production
VALID_TOKENS = {
"user1-token": {"id": "user1", "name": "Alice"},
"user2-token": {"id": "user2", "name": "Bob"},
}
# The "Auth" object is a container that LangGraph will use to mark our authentication function
auth = Auth()
# The `authenticate` decorator tells LangGraph to call this function as middleware
# for every request. This will determine whether the request is allowed or not
@auth.authenticate
async def get_current_user(authorization: str | None) -> Auth.types.MinimalUserDict:
"""Check if the user's token is valid."""
assert authorization
scheme, token = authorization.split()
assert scheme.lower() == "bearer"
# Check if token is valid
if token not in VALID_TOKENS:
raise Auth.exceptions.HTTPException(status_code=401, detail="Invalid token")
# Return user info if valid
user_data = VALID_TOKENS[token]
return {
"identity": user_data["id"],
}
```
Notice that our [authentication](../../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.Auth.authenticate) handler does two important things:
1. Checks if a valid token is provided in the request's [Authorization header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Authorization)
2. Returns the user's [identity](../../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.types.MinimalUserDict)
Now tell LangGraph to use our authentication by adding the following to the [`langgraph.json`](../../cloud/reference/cli.md#configuration-file) configuration:
```json hl_lines="7-9" title="langgraph.json"
{
"dependencies": ["."],
"graphs": {
"agent": "./src/agent/graph.py:graph"
},
"env": ".env",
"auth": {
"path": "src/security/auth.py:auth"
}
}
```
## Testing Our "Secure" Bot
Let's start the server again to test everything out!
```bash
langgraph dev --no-browser
```
??? note "Custom auth in the studio"
If you didn't add the `--no-browser`, the studio UI will open in the browser. You may wonder, how is the studio able to still connect to our server? By default, we also permit access from the LangGraph studio, even when using custom auth. This makes it easier to develop and test your bot in the studio. You can remove this alternative authentication option by
setting `disable_studio_auth: "true"` in your auth configuration:
```json
{
"auth": {
"path": "src/security/auth.py:auth",
"disable_studio_auth": "true"
}
}
```
Now let's try to chat with our bot. If we've implemented authentication correctly, we should only be able to access the bot if we provide a valid token in the request header. Users will still, however, be able to access each other's resources until we add [resource authorization handlers](../../concepts/auth.md#resource-authorization) in the next section of our tutorial.
![Authentication, no authorization handlers](./img/authentication.png)
Run the following code in a file or notebook:
```python
from langgraph_sdk import get_client
# Try without a token (should fail)
client = get_client(url="http://localhost:2024")
try:
thread = await client.threads.create()
print("❌ Should have failed without token!")
except Exception as e:
print("✅ Correctly blocked access:", e)
# Try with a valid token
client = get_client(
url="http://localhost:2024", headers={"Authorization": "Bearer user1-token"}
)
# Create a thread and chat
thread = await client.threads.create()
print(f"✅ Created thread as Alice: {thread['thread_id']}")
response = await client.runs.create(
thread_id=thread["thread_id"],
assistant_id="agent",
input={"messages": [{"role": "user", "content": "Hello!"}]},
)
print("✅ Bot responded:")
print(response)
```
You should see that:
1. Without a valid token, we can't access the bot
2. With a valid token, we can create threads and chat
Congratulations! You've built a chatbot that only lets "authenticated" users access it. While this system doesn't (yet) implement a production-ready security scheme, we've learned the basic mechanics of how to control access to our bot. In the next tutorial, we'll learn how to give each user their own private conversations.
## What's Next?
Now that you can control who accesses your bot, you might want to:
1. Continue the tutorial by going to [Making Conversations Private (Part 2/3)](resource_auth.md) to learn about resource authorization.
2. Read more about [authentication concepts](../../concepts/auth.md).
3. Check out the [API reference](../../cloud/reference/sdk/python_sdk_ref.md) for more authentication details.
Binary file not shown.

After

Width:  |  Height:  |  Size: 614 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 545 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 293 KiB

+340
View File
@@ -0,0 +1,340 @@
# Making Conversations Private (Part 2/3)
!!! note "This is part 2 of our authentication series:"
1. [Basic Authentication](getting_started.md) - Control who can access your bot
2. Resource Authorization (you are here) - Let users have private conversations
3. [Production Auth](add_auth_server.md) - Add real user accounts and validate using OAuth2
In this tutorial, we will extend our chatbot to give each user their own private conversations. We'll add [resource-level access control](../../concepts/auth.md#resource-level-access-control) so users can only see their own threads.
![Authorization handlers](./img/authorization.png)
???+ tip "Placeholder token"
As we did in [part 1](getting_started.md), for this section, we will use a hard-coded token for illustration purposes.
We will get to a "production-ready" authentication scheme in part 3, after mastering the basics.
## Understanding Resource Authorization
In the last tutorial, we controlled who could access our bot. But right now, any authenticated user can see everyone else's conversations! Let's fix that by adding [resource authorization](../../concepts/auth.md#resource-authorization).
First, make sure you have completed the [Basic Authentication](getting_started.md) tutorial and that your secure bot can be run without errors:
```bash
cd custom-auth
pip install -e .
langgraph dev --no-browser
```
> - 🚀 API: http://127.0.0.1:2024
> - 🎨 Studio UI: https://smith.langchain.com/studio/?baseUrl=http://127.0.0.1:2024
> - 📚 API Docs: http://127.0.0.1:2024/docs
## Adding Resource Authorization
Recall that in the last tutorial, the [`Auth`](../../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.Auth) object let us register an [authentication function](../../concepts/auth.md#authentication), which the LangGraph platform uses to validate the bearer tokens in incoming requests. Now we'll use it to register an **authorization** handler.
Authorization handlers are functions that run **after** authentication succeeds. These handlers can add [metadata](../../concepts/auth.md#resource-metadata) to resources (like who owns them) and filter what each user can see.
Let's update our `src/security/auth.py` and add one authorization handler that is run on every request:
```python hl_lines="29-39" title="src/security/auth.py"
from langgraph_sdk import Auth
# Keep our test users from the previous tutorial
VALID_TOKENS = {
"user1-token": {"id": "user1", "name": "Alice"},
"user2-token": {"id": "user2", "name": "Bob"},
}
auth = Auth()
@auth.authenticate
async def get_current_user(authorization: str | None) -> Auth.types.MinimalUserDict:
"""Our authentication handler from the previous tutorial."""
assert authorization
scheme, token = authorization.split()
assert scheme.lower() == "bearer"
if token not in VALID_TOKENS:
raise Auth.exceptions.HTTPException(status_code=401, detail="Invalid token")
user_data = VALID_TOKENS[token]
return {
"identity": user_data["id"],
}
@auth.on
async def add_owner(
ctx: Auth.types.AuthContext, # Contains info about the current user
value: dict, # The resource being created/accessed
):
"""Make resources private to their creator."""
# Examples:
# ctx: AuthContext(
# permissions=[],
# user=ProxyUser(
# identity='user1',
# is_authenticated=True,
# display_name='user1'
# ),
# resource='threads',
# action='create_run'
# )
# value:
# {
# 'thread_id': UUID('1e1b2733-303f-4dcd-9620-02d370287d72'),
# 'assistant_id': UUID('fe096781-5601-53d2-b2f6-0d3403f7e9ca'),
# 'run_id': UUID('1efbe268-1627-66d4-aa8d-b956b0f02a41'),
# 'status': 'pending',
# 'metadata': {},
# 'prevent_insert_if_inflight': True,
# 'multitask_strategy': 'reject',
# 'if_not_exists': 'reject',
# 'after_seconds': 0,
# 'kwargs': {
# 'input': {'messages': [{'role': 'user', 'content': 'Hello!'}]},
# 'command': None,
# 'config': {
# 'configurable': {
# 'langgraph_auth_user': ... Your user object...
# 'langgraph_auth_user_id': 'user1'
# }
# },
# 'stream_mode': ['values'],
# 'interrupt_before': None,
# 'interrupt_after': None,
# 'webhook': None,
# 'feedback_keys': None,
# 'temporary': False,
# 'subgraphs': False
# }
# }
# Do 2 things:
# 1. Add the user's ID to the resource's metadata. Each LangGraph resource has a `metadata` dict that persists with the resource.
# this metadata is useful for filtering in read and update operations
# 2. Return a filter that lets users only see their own resources
filters = {"owner": ctx.user.identity}
metadata = value.setdefault("metadata", {})
metadata.update(filters)
# Only let users see their own resources
return filters
```
The handler receives two parameters:
1. `ctx` ([AuthContext](../../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.types.AuthContext)): contains info about the current `user`, the user's `permissions`, the `resource` ("threads", "crons", "assistants"), and the `action` being taken ("create", "read", "update", "delete", "search", "create_run")
2. `value` (`dict`): data that is being created or accessed. The contents of this dict depend on the resource and action being accessed. See [adding scoped authorization handlers](#scoped-authorization) below for information on how to get more tightly scoped access control.
Notice that our simple handler does two things:
1. Adds the user's ID to the resource's metadata.
2. Returns a metadata filter so users only see resources they own.
## Testing Private Conversations
Let's test our authorization. If we have set things up correctly, we should expect to see all ✅ messages. Be sure to have your development server running (run `langgraph dev`):
```python
from langgraph_sdk import get_client
# Create clients for both users
alice = get_client(
url="http://localhost:2024",
headers={"Authorization": "Bearer user1-token"}
)
bob = get_client(
url="http://localhost:2024",
headers={"Authorization": "Bearer user2-token"}
)
# Alice creates an assistant
alice_assistant = await alice.assistants.create()
print(f"✅ Alice created assistant: {alice_assistant['assistant_id']}")
# Alice creates a thread and chats
alice_thread = await alice.threads.create()
print(f"✅ Alice created thread: {alice_thread['thread_id']}")
await alice.runs.create(
thread_id=alice_thread["thread_id"],
assistant_id="agent",
input={"messages": [{"role": "user", "content": "Hi, this is Alice's private chat"}]}
)
# Bob tries to access Alice's thread
try:
await bob.threads.get(alice_thread["thread_id"])
print("❌ Bob shouldn't see Alice's thread!")
except Exception as e:
print("✅ Bob correctly denied access:", e)
# Bob creates his own thread
bob_thread = await bob.threads.create()
await bob.runs.create(
thread_id=bob_thread["thread_id"],
assistant_id="agent",
input={"messages": [{"role": "user", "content": "Hi, this is Bob's private chat"}]}
)
print(f"✅ Bob created his own thread: {bob_thread['thread_id']}")
# List threads - each user only sees their own
alice_threads = await alice.threads.search()
bob_threads = await bob.threads.search()
print(f"✅ Alice sees {len(alice_threads)} thread")
print(f"✅ Bob sees {len(bob_threads)} thread")
```
Run the test code and you should see output like this:
```bash
✅ Alice created assistant: fc50fb08-78da-45a9-93cc-1d3928a3fc37
✅ Alice created thread: 533179b7-05bc-4d48-b47a-a83cbdb5781d
✅ Bob correctly denied access: Client error '404 Not Found' for url 'http://localhost:2024/threads/533179b7-05bc-4d48-b47a-a83cbdb5781d'
For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404
✅ Bob created his own thread: 437c36ed-dd45-4a1e-b484-28ba6eca8819
✅ Alice sees 1 thread
✅ Bob sees 1 thread
```
This means:
1. Each user can create and chat in their own threads
2. Users can't see each other's threads
3. Listing threads only shows your own
## Adding scoped authorization handlers {#scoped-authorization}
The broad `@auth.on` handler matches on all [authorization events](../../concepts/auth.md#authorization-events). This is concise, but it means the contents of the `value` dict are not well-scoped, and we apply the same user-level access control to every resource. If we want to be more fine-grained, we can also control specific actions on resources.
Update `src/security/auth.py` to add handlers for specific resource types:
```python
# Keep our previous handlers...
from langgraph_sdk import Auth
@auth.on.threads.create
async def on_thread_create(
ctx: Auth.types.AuthContext,
value: Auth.types.on.threads.create.value,
):
"""Add owner when creating threads.
This handler runs when creating new threads and does two things:
1. Sets metadata on the thread being created to track ownership
2. Returns a filter that ensures only the creator can access it
"""
# Example value:
# {'thread_id': UUID('99b045bc-b90b-41a8-b882-dabc541cf740'), 'metadata': {}, 'if_exists': 'raise'}
# Add owner metadata to the thread being created
# This metadata is stored with the thread and persists
metadata = value.setdefault("metadata", {})
metadata["owner"] = ctx.user.identity
# Return filter to restrict access to just the creator
return {"owner": ctx.user.identity}
@auth.on.threads.read
async def on_thread_read(
ctx: Auth.types.AuthContext,
value: Auth.types.on.threads.read.value,
):
"""Only let users read their own threads.
This handler runs on read operations. We don't need to set
metadata since the thread already exists - we just need to
return a filter to ensure users can only see their own threads.
"""
return {"owner": ctx.user.identity}
@auth.on.assistants
async def on_assistants(
ctx: Auth.types.AuthContext,
value: Auth.types.on.assistants.value,
):
# For illustration purposes, we will deny all requests
# that touch the assistants resource
# Example value:
# {
# 'assistant_id': UUID('63ba56c3-b074-4212-96e2-cc333bbc4eb4'),
# 'graph_id': 'agent',
# 'config': {},
# 'metadata': {},
# 'name': 'Untitled'
# }
raise Auth.exceptions.HTTPException(
status_code=403,
detail="User lacks the required permissions.",
)
# Assumes you organize information in store like (user_id, resource_type, resource_id)
@auth.on.store()
async def authorize_store(ctx: Auth.types.AuthContext, value: dict):
# The "namespace" field for each store item is a tuple you can think of as the directory of an item.
namespace: tuple = value["namespace"]
assert namespace[0] == ctx.user.identity, "Not authorized"
```
Notice that instead of one global handler, we now have specific handlers for:
1. Creating threads
2. Reading threads
3. Accessing assistants
The first three of these match specific **actions** on each resource (see [resource actions](../../concepts/auth.md#resource-actions)), while the last one (`@auth.on.assistants`) matches _any_ action on the `assistants` resource. For each request, LangGraph will run the most specific handler that matches the resource and action being accessed. This means that the four handlers above will run rather than the broadly scoped "`@auth.on`" handler.
Try adding the following test code to your test file:
```python
# ... Same as before
# Try creating an assistant. This should fail
try:
await alice.assistants.create("agent")
print("❌ Alice shouldn't be able to create assistants!")
except Exception as e:
print("✅ Alice correctly denied access:", e)
# Try searching for assistants. This also should fail
try:
await alice.assistants.search()
print("❌ Alice shouldn't be able to search assistants!")
except Exception as e:
print("✅ Alice correctly denied access to searching assistants:", e)
# Alice can still create threads
alice_thread = await alice.threads.create()
print(f"✅ Alice created thread: {alice_thread['thread_id']}")
```
And then run the test code again:
```bash
✅ Alice created thread: dcea5cd8-eb70-4a01-a4b6-643b14e8f754
✅ Bob correctly denied access: Client error '404 Not Found' for url 'http://localhost:2024/threads/dcea5cd8-eb70-4a01-a4b6-643b14e8f754'
For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404
✅ Bob created his own thread: 400f8d41-e946-429f-8f93-4fe395bc3eed
✅ Alice sees 1 thread
✅ Bob sees 1 thread
✅ Alice correctly denied access:
For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500
✅ Alice correctly denied access to searching assistants:
```
Congratulations! You've built a chatbot where each user has their own private conversations. While this system uses simple token-based authentication, the authorization patterns we've learned will work with implementing any real authentication system. In the next tutorial, we'll replace our test users with real user accounts using OAuth2.
## What's Next?
Now that you can control access to resources, you might want to:
1. Move on to [Production Auth](add_auth_server.md) to add real user accounts
2. Read more about [authorization patterns](../../concepts/auth.md#authorization)
3. Check out the [API reference](../../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.Auth) for details about the interfaces and methods used in this tutorial
@@ -246,7 +246,7 @@
"\n",
"Define the (`fetch_user_flight_information`) tool to let the agent see the current user's flight information. Then define tools to search for flights and manage the passenger's bookings stored in the SQL database.\n",
"\n",
"We the can [access the RunnableConfig](https://python.langchain.com/docs/how_to/tool_configure/#inferring-by-parameter-type) for a given run to check the `passenger_id` of the user accessing this application. The LLM never has to provide these explicitly, they are provided for a given invocation of the graph so that each user cannot access other passengers' booking information.\n",
"We then can [access the RunnableConfig](https://python.langchain.com/docs/how_to/tool_configure/#inferring-by-parameter-type) for a given run to check the `passenger_id` of the user accessing this application. The LLM never has to provide these explicitly, they are provided for a given invocation of the graph so that each user cannot access other passengers' booking information.\n",
"\n",
"<div class=\"admonition warning\">\n",
" <p class=\"admonition-title\">Compatibility</p>\n",
@@ -444,7 +444,7 @@
"\n",
" # Check the signed-in user actually has this ticket\n",
" cursor.execute(\n",
" \"SELECT flight_id FROM tickets WHERE ticket_no = ? AND passenger_id = ?\",\n",
" \"SELECT ticket_no FROM tickets WHERE ticket_no = ? AND passenger_id = ?\",\n",
" (ticket_no, passenger_id),\n",
" )\n",
" current_ticket = cursor.fetchone()\n",
@@ -3423,7 +3423,7 @@
"\n",
"#### Utility\n",
"\n",
"Create a function to make an \"entry\" node for each workflow, stating \"the current assistant ix `assistant_name`\"."
"Create a function to make an \"entry\" node for each workflow, stating \"the current assistant is `assistant_name`\"."
]
},
{
@@ -4444,7 +4444,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.9"
"version": "3.12.3"
}
},
"nbformat": 4,
+15 -4
View File
@@ -11,11 +11,12 @@ New to LangGraph or LLM app development? Read this material to get up and runnin
## Get Started 🚀 {#quick-start}
- [LangGraph Quickstart](introduction.ipynb): Build a chatbot that can use tools and keep track of conversation history. Add human-in-the-loop capabilities and explore how time-travel works.
- [LangGraph Server Quickstart](langgraph-platform/local-server.md): Launch a LangGraph server locally and interact with it using the REST API and LangGraph Studio Web UI.
- [LangGraph Cloud QuickStart](../cloud/quick_start.md): Deploy a LangGraph app using LangGraph Cloud.
- [LangGraph Template Quickstart](../concepts/template_applications.md): Quickly start building with LangGraph Platform using a template application.
- [LangGraph Server Quickstart](langgraph-platform/local-server.md): Launch a LangGraph server locally and interact with it using REST API and LangGraph Studio Web UI.
- [LangGraph Template Quickstart](../concepts/template_applications.md): Start building with LangGraph Platform using a template application.
- [Deploy with LangGraph Cloud Quickstart](../cloud/quick_start.md): Deploy a LangGraph app using LangGraph Cloud.
## Use cases 🛠️ {#use-cases}
## Use cases 🛠️
Explore practical implementations tailored for specific scenarios:
@@ -71,3 +72,13 @@ Explore practical implementations tailored for specific scenarios:
- [Web Navigation](web-navigation/web_voyager.ipynb): Build an agent that can navigate and interact with websites
- [Competitive Programming](usaco/usaco.ipynb): Build an agent with few-shot "episodic memory" and human-in-the-loop collaboration to solve problems from the USA Computing Olympiad; adapted from the ["Can Language Models Solve Olympiad Programming?"](https://arxiv.org/abs/2404.10952v1) paper by Shi, Tang, Narasimhan, and Yao.
- [Complex data extraction](extraction/retries.ipynb): Build an agent that can use function calling to do complex extraction tasks
## LangGraph Platform 🧱 {#platform}
### Authentication & Access Control
Add custom authentication and authorization to an existing LangGraph Platform deployment in the following three-part guide:
1. [Setting Up Custom Authentication](auth/getting_started.md): Implement OAuth2 authentication to authorize users on your deployment
2. [Resource Authorization](auth/resource_auth.md): Let users have private conversations
3. [Connecting an Authentication Provider](auth/add_auth_server.md): Add real user accounts and validate using OAuth2
+2 -5
View File
@@ -553,9 +553,6 @@
"metadata": {},
"outputs": [],
"source": [
"from typing import Literal\n",
"\n",
"\n",
"def route_tools(\n",
" state: State,\n",
"):\n",
@@ -1662,7 +1659,7 @@
"id": "584de971-6b10-4931-986e-cc35f7adbb3d",
"metadata": {},
"source": [
"Now the graph is complete, since we've provided the final response message! Since state updates simulate a graph step, they even generate corresponding traces. Inspec the [LangSmith trace](https://smith.langchain.com/public/6d72aeb5-3bca-4090-8684-a11d5a36b10c/r) of the `update_state` call above to see what's going on.\n",
"Now the graph is complete, since we've provided the final response message! Since state updates simulate a graph step, they even generate corresponding traces. Inspect the [LangSmith trace](https://smith.langchain.com/public/6d72aeb5-3bca-4090-8684-a11d5a36b10c/r) of the `update_state` call above to see what's going on.\n",
"\n",
"**Notice** that our new messages are _appended_ to the messages already in the state. Remember how we defined the `State` type?\n",
"\n",
@@ -2653,7 +2650,7 @@
"metadata": {},
"outputs": [],
"source": [
"from typing import Annotated, Literal\n",
"from typing import Annotated\n",
"\n",
"from langchain_anthropic import ChatAnthropic\n",
"from langchain_community.tools.tavily_search import TavilySearchResults\n",
@@ -1,4 +1,4 @@
# Quick Start: Launch Local LangGraph Server
# QuickStart: Launch Local LangGraph Server
This is a quick start guide to help you get a LangGraph app up and running locally.
@@ -10,7 +10,7 @@ This is a quick start guide to help you get a LangGraph app up and running local
## Install the LangGraph CLI
```bash
pip install -U "langgraph-cli[inmem]" python-dotenv
pip install --upgrade "langgraph-cli[inmem]"
```
## 🌱 Create a LangGraph App
@@ -53,21 +53,12 @@ ANTHROPIC_API_KEY=sk-
OPENAI_API_KEY=sk-...
```
<details><summary>Get API Keys</summary>
<ul>
<li> <b>LANGSMITH_API_KEY</b>: Go to the <a href="https://smith.langchain.com/settings">LangSmith Settings page</a>. Then clck <b>Create API Key</b>.
</li>
<li>
<b>ANTHROPIC_API_KEY</b>: Get an API key from <a href="https://console.anthropic.com/">Anthropic</a>.
</li>
<li>
<b>OPENAI_API_KEY</b>: Get an API key from <a href="https://openai.com/">OpenAI</a>.
</li>
<li>
<b>TAVILY_API_KEY</b>: Get an API key on the <a href="https://app.tavily.com/">Tavily website</a>.
</li>
</ul>
</details>
??? note "Get API Keys"
- **LANGSMITH_API_KEY**: Go to the [LangSmith Settings page](https://smith.langchain.com/settings). Then clck **Create API Key**.
- **ANTHROPIC_API_KEY**: Get an API key from [Anthropic](https://console.anthropic.com/).
- **OPENAI_API_KEY**: Get an API key from [OpenAI](https://openai.com/).
- **TAVILY_API_KEY**: Get an API key on the [Tavily website](https://app.tavily.com/).
## 🚀 Launch LangGraph Server
@@ -79,11 +70,11 @@ This will start up the LangGraph API server locally. If this runs successfully,
> Ready!
>
> - API: [http://localhost:8123](http://localhost:8123/)
> - API: [http://localhost:2024](http://localhost:2024/)
>
> - Docs: http://localhost:8123/docs
> - Docs: http://localhost:2024/docs
>
> - LangGraph Studio Web UI: https://smith.langchain.com/studio/?baseUrl=http://127.0.0.1:8123
> - LangGraph Studio Web UI: https://smith.langchain.com/studio/?baseUrl=http://127.0.0.1:2024
!!! note "In-Memory Mode"
@@ -95,9 +86,18 @@ This will start up the LangGraph API server locally. If this runs successfully,
## LangGraph Studio Web UI
Test your graph in the LangGraph Studio Web UI by visiting the URL provided in the output of the `langgraph up` command.
LangGraph Studio Web is a specialized UI that you can connect to LangGraph API server to enable visualization, interaction, and debugging of your application locally. Test your graph in the LangGraph Studio Web UI by visiting the URL provided in the output of the `langgraph dev` command.
> - LangGraph Studio Web UI: https://smith.langchain.com/studio/?baseUrl=http://127.0.0.1:2024
!!! info "Connecting to a server with a custom host/port"
If you are running the LangGraph API server with a custom host / port, you can point the Studio Web UI at it by changing the `baseUrl` URL param. For example, if you are running your server on port 8000, you can change the above URL to the following:
```
https://smith.langchain.com/studio/baseUrl=http://127.0.0.1:8000
```
> - LangGraph Studio Web UI: https://smith.langchain.com/studio/?baseUrl=http://127.0.0.1:8123
!!! warning "Safari Compatibility"
@@ -118,7 +118,7 @@ Test your graph in the LangGraph Studio Web UI by visiting the URL provided in t
```python
from langgraph_sdk import get_client
client = get_client(url="http://localhost:8123")
client = get_client(url="http://localhost:2024")
async for chunk in client.runs.stream(
None, # Threadless run
@@ -149,7 +149,7 @@ Test your graph in the LangGraph Studio Web UI by visiting the URL provided in t
```python
from langgraph_sdk import get_sync_client
client = get_sync_client(url="http://localhost:8123")
client = get_sync_client(url="http://localhost:2024")
for chunk in client.runs.stream(
None, # Threadless run
@@ -180,8 +180,8 @@ Test your graph in the LangGraph Studio Web UI by visiting the URL provided in t
```js
const { Client } = await import("@langchain/langgraph-sdk");
// only set the apiUrl if you changed the default port when calling langgraph up
const client = new Client({ apiUrl: "http://localhost:8123"});
// only set the apiUrl if you changed the default port when calling langgraph dev
const client = new Client({ apiUrl: "http://localhost:2024"});
const streamResponse = client.runs.stream(
null, // Threadless run
@@ -207,7 +207,7 @@ Test your graph in the LangGraph Studio Web UI by visiting the URL provided in t
```bash
curl -s --request POST \
--url "http://localhost:8123/runs/stream" \
--url "http://localhost:2024/runs/stream" \
--header 'Content-Type: application/json' \
--data "{
\"assistant_id\": \"agent\",
@@ -235,7 +235,7 @@ Now that you have a LangGraph app running locally, take your journey further by
### 🌐 Deploy to LangGraph Cloud
- **[LangGraph Cloud QuickStart](../../cloud/quick_start.md)**: Deploy your LangGraph app using LangGraph Cloud.
- **[LangGraph Cloud Quickstart](../../cloud/quick_start.md)**: Deploy your LangGraph app using LangGraph Cloud.
### 📚 Learn More about LangGraph Platform
@@ -870,7 +870,7 @@
" # No deps or all deps satisfied\n",
" # can schedule now\n",
" schedule_task.invoke(dict(task=task, observations=observations))\n",
" # futures.append(executor.submit(schedule_task.invoke dict(task=task, observations=observations)))\n",
" # futures.append(executor.submit(schedule_task.invoke, dict(task=task, observations=observations)))\n",
"\n",
" # All tasks have been submitted or enqueued\n",
" # Wait for them to complete\n",
@@ -135,7 +135,7 @@
"from typing_extensions import TypedDict\n",
"\n",
"from langchain_anthropic import ChatAnthropic\n",
"from langgraph.graph import MessagesState\n",
"from langgraph.graph import MessagesState, END\n",
"from langgraph.types import Command\n",
"\n",
"\n",
@@ -26,7 +26,7 @@
"outputs": [],
"source": [
"%%capture --no-stderr\n",
"%pip install -U --quiet langchain-community tiktoken langchain-openai langchainhub chromadb langchain langgraph langchain-text-splitters"
"%pip install -U --quiet langchain-community tiktoken langchain-openai langchainhub chromadb langchain langgraph langchain-text-splitters beautifulsoup4"
]
},
{
+16 -1
View File
@@ -2,6 +2,7 @@ site_name: ""
site_description: Build language agents as graphs
site_url: https://langchain-ai.github.io/langgraph/
repo_url: https://github.com/langchain-ai/langgraph
edit_uri: edit/main/docs/docs/
theme:
name: material
custom_dir: overrides
@@ -16,6 +17,7 @@ theme:
- content.code.copy
- content.code.select
- content.tabs.link
- content.action.edit
- content.tooltips
- header.autohide
- navigation.expand
@@ -73,7 +75,7 @@ plugins:
allow_inspection: true
heading_level: 2
show_bases: true
show_source: true
show_source: false
summary: true
inherited_members: true
selection:
@@ -141,6 +143,11 @@ nav:
- tutorials/web-navigation/web_voyager.ipynb
- tutorials/usaco/usaco.ipynb
- tutorials/extraction/retries.ipynb
- LangGraph Platform:
- LangGraph Platform: concepts#langgraph-platform
- tutorials/auth/getting_started.md
- tutorials/auth/resource_auth.md
- tutorials/auth/add_auth_server.md
- How-to Guides:
- how-tos/index.md
@@ -201,7 +208,10 @@ nav:
- how-tos/subgraphs-manage-state.ipynb
- how-tos/subgraph-transform-state.ipynb
- Multi-agent:
- Multi-agent: how-tos#multi-agent
- how-tos/agent-handoffs.ipynb
- how-tos/multi-agent-network.ipynb
- how-tos/multi-agent-multi-turn-convo.ipynb
- State Management:
- State Management: how-tos#state-management
- how-tos/state-model.ipynb
@@ -239,6 +249,10 @@ nav:
- cloud/deployment/cloud.md
- how-tos/deploy-self-hosted.md
- how-tos/use-remote-graph.md
- Authentication & Access Control:
- Authentication & Access Control: how-tos#authentication-access-control
- cloud/how-tos/auth/custom_auth_new.md
- cloud/how-tos/auth/openapi_security_new.md
- Assistants:
- Assistants: how-tos#assistants
- cloud/how-tos/configuration_cloud.md
@@ -284,6 +298,7 @@ nav:
- cloud/how-tos/test_local_deployment.md
- cloud/how-tos/invoke_studio.md
- cloud/how-tos/threads_studio.md
- cloud/how-tos/datasets_studio.md
- Troubleshooting:
- Troubleshooting: how-tos#troubleshooting
- troubleshooting/errors/index.md
+10
View File
@@ -164,6 +164,11 @@
.jupyter-wrapper .jp-Notebook .jp-Cell .jp-OutputPrompt {
display: none !important;
}
.md-banner {
background-color: #CFC9FA;
color: #000000;
}
</style>
{% endblock %}
@@ -190,3 +195,8 @@
<title>{{ config.site_name }}</title>
{% endif %}
{% endblock %}
{% block announce %}
To learn more about LangGraph, check out our first LangChain Academy course, <em>Introduction to LangGraph</em>, available for free <a href="https://academy.langchain.com/courses/intro-to-langgraph">here</a>.
{% endblock %}
@@ -19,6 +19,7 @@ from langgraph.checkpoint.base import (
)
from langgraph.checkpoint.postgres import _internal
from langgraph.checkpoint.postgres.base import BasePostgresSaver
from langgraph.checkpoint.postgres.shallow import ShallowPostgresSaver
from langgraph.checkpoint.serde.base import SerializerProtocol
Conn = _internal.Conn # For backward compatibility
@@ -396,4 +397,4 @@ class PostgresSaver(BasePostgresSaver):
yield cur
__all__ = ["PostgresSaver", "BasePostgresSaver", "Conn"]
__all__ = ["PostgresSaver", "BasePostgresSaver", "ShallowPostgresSaver", "Conn"]
@@ -19,6 +19,7 @@ from langgraph.checkpoint.base import (
)
from langgraph.checkpoint.postgres import _ainternal
from langgraph.checkpoint.postgres.base import BasePostgresSaver
from langgraph.checkpoint.postgres.shallow import AsyncShallowPostgresSaver
from langgraph.checkpoint.serde.base import SerializerProtocol
Conn = _ainternal.Conn # For backward compatibility
@@ -379,6 +380,18 @@ class AsyncPostgresSaver(BasePostgresSaver):
Yields:
Iterator[CheckpointTuple]: An iterator of matching checkpoint tuples.
"""
try:
# check if we are in the main thread, only bg threads can block
# we don't check in other methods to avoid the overhead
if asyncio.get_running_loop() is self.loop:
raise asyncio.InvalidStateError(
"Synchronous calls to AsyncSqliteSaver are only allowed from a "
"different thread. From the main thread, use the async interface. "
"For example, use `checkpointer.alist(...)` or `await "
"graph.ainvoke(...)`."
)
except RuntimeError:
pass
aiter_ = self.alist(config, filter=filter, before=before, limit=limit)
while True:
try:
@@ -409,7 +422,7 @@ class AsyncPostgresSaver(BasePostgresSaver):
if asyncio.get_running_loop() is self.loop:
raise asyncio.InvalidStateError(
"Synchronous calls to AsyncPostgresSaver are only allowed from a "
"different thread. From the main thread, use the async interface."
"different thread. From the main thread, use the async interface. "
"For example, use `await checkpointer.aget_tuple(...)` or `await "
"graph.ainvoke(...)`."
)
@@ -464,4 +477,4 @@ class AsyncPostgresSaver(BasePostgresSaver):
).result()
__all__ = ["AsyncPostgresSaver", "Conn"]
__all__ = ["AsyncPostgresSaver", "AsyncShallowPostgresSaver", "Conn"]
@@ -58,8 +58,6 @@ MIGRATIONS = [
);""",
"ALTER TABLE checkpoint_blobs ALTER COLUMN blob DROP not null;",
"""
""",
"""
CREATE INDEX CONCURRENTLY IF NOT EXISTS checkpoints_thread_id_idx ON checkpoints(thread_id);
""",
"""
@@ -0,0 +1,918 @@
import asyncio
import threading
from collections.abc import AsyncIterator, Iterator, Sequence
from contextlib import asynccontextmanager, contextmanager
from typing import Any, Optional
from langchain_core.runnables import RunnableConfig
from psycopg import (
AsyncConnection,
AsyncCursor,
AsyncPipeline,
Capabilities,
Connection,
Cursor,
Pipeline,
)
from psycopg.rows import DictRow, dict_row
from psycopg.types.json import Jsonb
from psycopg_pool import AsyncConnectionPool, ConnectionPool
from langgraph.checkpoint.base import (
WRITES_IDX_MAP,
ChannelVersions,
Checkpoint,
CheckpointMetadata,
CheckpointTuple,
)
from langgraph.checkpoint.postgres import _ainternal, _internal
from langgraph.checkpoint.postgres.base import BasePostgresSaver
from langgraph.checkpoint.serde.base import SerializerProtocol
from langgraph.checkpoint.serde.types import TASKS
"""
To add a new migration, add a new string to the MIGRATIONS list.
The position of the migration in the list is the version number.
"""
MIGRATIONS = [
"""CREATE TABLE IF NOT EXISTS checkpoint_migrations (
v INTEGER PRIMARY KEY
);""",
"""CREATE TABLE IF NOT EXISTS checkpoints (
thread_id TEXT NOT NULL,
checkpoint_ns TEXT NOT NULL DEFAULT '',
type TEXT,
checkpoint JSONB NOT NULL,
metadata JSONB NOT NULL DEFAULT '{}',
PRIMARY KEY (thread_id, checkpoint_ns)
);""",
"""CREATE TABLE IF NOT EXISTS checkpoint_blobs (
thread_id TEXT NOT NULL,
checkpoint_ns TEXT NOT NULL DEFAULT '',
channel TEXT NOT NULL,
type TEXT NOT NULL,
blob BYTEA,
PRIMARY KEY (thread_id, checkpoint_ns, channel)
);""",
"""CREATE TABLE IF NOT EXISTS checkpoint_writes (
thread_id TEXT NOT NULL,
checkpoint_ns TEXT NOT NULL DEFAULT '',
checkpoint_id TEXT NOT NULL,
task_id TEXT NOT NULL,
idx INTEGER NOT NULL,
channel TEXT NOT NULL,
type TEXT,
blob BYTEA NOT NULL,
PRIMARY KEY (thread_id, checkpoint_ns, checkpoint_id, task_id, idx)
);""",
"""
CREATE INDEX CONCURRENTLY IF NOT EXISTS checkpoints_thread_id_idx ON checkpoints(thread_id);
""",
"""
CREATE INDEX CONCURRENTLY IF NOT EXISTS checkpoint_blobs_thread_id_idx ON checkpoint_blobs(thread_id);
""",
"""
CREATE INDEX CONCURRENTLY IF NOT EXISTS checkpoint_writes_thread_id_idx ON checkpoint_writes(thread_id);
""",
]
SELECT_SQL = f"""
select
thread_id,
checkpoint,
checkpoint_ns,
metadata,
(
select array_agg(array[bl.channel::bytea, bl.type::bytea, bl.blob])
from jsonb_each_text(checkpoint -> 'channel_versions')
inner join checkpoint_blobs bl
on bl.thread_id = checkpoints.thread_id
and bl.checkpoint_ns = checkpoints.checkpoint_ns
and bl.channel = jsonb_each_text.key
) as channel_values,
(
select
array_agg(array[cw.task_id::text::bytea, cw.channel::bytea, cw.type::bytea, cw.blob] order by cw.task_id, cw.idx)
from checkpoint_writes cw
where cw.thread_id = checkpoints.thread_id
and cw.checkpoint_ns = checkpoints.checkpoint_ns
and cw.checkpoint_id = (checkpoint->>'id')
) as pending_writes,
(
select array_agg(array[cw.type::bytea, cw.blob] order by cw.task_id, cw.idx)
from checkpoint_writes cw
where cw.thread_id = checkpoints.thread_id
and cw.checkpoint_ns = checkpoints.checkpoint_ns
and cw.channel = '{TASKS}'
) as pending_sends
from checkpoints """
UPSERT_CHECKPOINT_BLOBS_SQL = """
INSERT INTO checkpoint_blobs (thread_id, checkpoint_ns, channel, type, blob)
VALUES (%s, %s, %s, %s, %s)
ON CONFLICT (thread_id, checkpoint_ns, channel) DO UPDATE SET
type = EXCLUDED.type,
blob = EXCLUDED.blob;
"""
UPSERT_CHECKPOINTS_SQL = """
INSERT INTO checkpoints (thread_id, checkpoint_ns, checkpoint, metadata)
VALUES (%s, %s, %s, %s)
ON CONFLICT (thread_id, checkpoint_ns)
DO UPDATE SET
checkpoint = EXCLUDED.checkpoint,
metadata = EXCLUDED.metadata;
"""
UPSERT_CHECKPOINT_WRITES_SQL = """
INSERT INTO checkpoint_writes (thread_id, checkpoint_ns, checkpoint_id, task_id, idx, channel, type, blob)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s)
ON CONFLICT (thread_id, checkpoint_ns, checkpoint_id, task_id, idx) DO UPDATE SET
channel = EXCLUDED.channel,
type = EXCLUDED.type,
blob = EXCLUDED.blob;
"""
INSERT_CHECKPOINT_WRITES_SQL = """
INSERT INTO checkpoint_writes (thread_id, checkpoint_ns, checkpoint_id, task_id, idx, channel, type, blob)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s)
ON CONFLICT (thread_id, checkpoint_ns, checkpoint_id, task_id, idx) DO NOTHING
"""
def _dump_blobs(
serde: SerializerProtocol,
thread_id: str,
checkpoint_ns: str,
values: dict[str, Any],
versions: ChannelVersions,
) -> list[tuple[str, str, str, str, str, Optional[bytes]]]:
if not versions:
return []
return [
(
thread_id,
checkpoint_ns,
k,
*(serde.dumps_typed(values[k]) if k in values else ("empty", None)),
)
for k in versions
]
class ShallowPostgresSaver(BasePostgresSaver):
"""A checkpoint saver that uses Postgres to store checkpoints.
This checkpointer ONLY stores the most recent checkpoint and does NOT retain any history.
It is meant to be a light-weight drop-in replacement for the PostgresSaver that
supports most of the LangGraph persistence functionality with the exception of time travel.
"""
SELECT_SQL = SELECT_SQL
MIGRATIONS = MIGRATIONS
UPSERT_CHECKPOINT_BLOBS_SQL = UPSERT_CHECKPOINT_BLOBS_SQL
UPSERT_CHECKPOINTS_SQL = UPSERT_CHECKPOINTS_SQL
UPSERT_CHECKPOINT_WRITES_SQL = UPSERT_CHECKPOINT_WRITES_SQL
INSERT_CHECKPOINT_WRITES_SQL = INSERT_CHECKPOINT_WRITES_SQL
lock: threading.Lock
def __init__(
self,
conn: _internal.Conn,
pipe: Optional[Pipeline] = None,
serde: Optional[SerializerProtocol] = None,
) -> None:
super().__init__(serde=serde)
if isinstance(conn, ConnectionPool) and pipe is not None:
raise ValueError(
"Pipeline should be used only with a single Connection, not ConnectionPool."
)
self.conn = conn
self.pipe = pipe
self.lock = threading.Lock()
self.supports_pipeline = Capabilities().has_pipeline()
@classmethod
@contextmanager
def from_conn_string(
cls, conn_string: str, *, pipeline: bool = False
) -> Iterator["ShallowPostgresSaver"]:
"""Create a new ShallowPostgresSaver instance from a connection string.
Args:
conn_string (str): The Postgres connection info string.
pipeline (bool): whether to use Pipeline
Returns:
ShallowPostgresSaver: A new ShallowPostgresSaver instance.
"""
with Connection.connect(
conn_string, autocommit=True, prepare_threshold=0, row_factory=dict_row
) as conn:
if pipeline:
with conn.pipeline() as pipe:
yield cls(conn, pipe)
else:
yield cls(conn)
def setup(self) -> None:
"""Set up the checkpoint database asynchronously.
This method creates the necessary tables in the Postgres database if they don't
already exist and runs database migrations. It MUST be called directly by the user
the first time checkpointer is used.
"""
with self._cursor() as cur:
cur.execute(self.MIGRATIONS[0])
results = cur.execute(
"SELECT v FROM checkpoint_migrations ORDER BY v DESC LIMIT 1"
)
row = results.fetchone()
if row is None:
version = -1
else:
version = row["v"]
for v, migration in zip(
range(version + 1, len(self.MIGRATIONS)),
self.MIGRATIONS[version + 1 :],
):
cur.execute(migration)
cur.execute(f"INSERT INTO checkpoint_migrations (v) VALUES ({v})")
if self.pipe:
self.pipe.sync()
def list(
self,
config: Optional[RunnableConfig],
*,
filter: Optional[dict[str, Any]] = None,
before: Optional[RunnableConfig] = None,
limit: Optional[int] = None,
) -> Iterator[CheckpointTuple]:
"""List checkpoints from the database.
This method retrieves a list of checkpoint tuples from the Postgres database based
on the provided config. For ShallowPostgresSaver, this method returns a list with
ONLY the most recent checkpoint.
"""
where, args = self._search_where(config, filter, before)
query = self.SELECT_SQL + where
if limit:
query += f" LIMIT {limit}"
with self._cursor() as cur:
cur.execute(self.SELECT_SQL + where, args, binary=True)
for value in cur:
checkpoint = self._load_checkpoint(
value["checkpoint"],
value["channel_values"],
value["pending_sends"],
)
yield CheckpointTuple(
config={
"configurable": {
"thread_id": value["thread_id"],
"checkpoint_ns": value["checkpoint_ns"],
"checkpoint_id": checkpoint["id"],
}
},
checkpoint=checkpoint,
metadata=self._load_metadata(value["metadata"]),
pending_writes=self._load_writes(value["pending_writes"]),
)
def get_tuple(self, config: RunnableConfig) -> Optional[CheckpointTuple]:
"""Get a checkpoint tuple from the database.
This method retrieves a checkpoint tuple from the Postgres database based on the
provided config (matching the thread ID in the config).
Args:
config (RunnableConfig): The config to use for retrieving the checkpoint.
Returns:
Optional[CheckpointTuple]: The retrieved checkpoint tuple, or None if no matching checkpoint was found.
Examples:
Basic:
>>> config = {"configurable": {"thread_id": "1"}}
>>> checkpoint_tuple = memory.get_tuple(config)
>>> print(checkpoint_tuple)
CheckpointTuple(...)
With timestamp:
>>> config = {
... "configurable": {
... "thread_id": "1",
... "checkpoint_ns": "",
... "checkpoint_id": "1ef4f797-8335-6428-8001-8a1503f9b875",
... }
... }
>>> checkpoint_tuple = memory.get_tuple(config)
>>> print(checkpoint_tuple)
CheckpointTuple(...)
""" # noqa
thread_id = config["configurable"]["thread_id"]
checkpoint_ns = config["configurable"].get("checkpoint_ns", "")
args = (thread_id, checkpoint_ns)
where = "WHERE thread_id = %s AND checkpoint_ns = %s"
with self._cursor() as cur:
cur.execute(
self.SELECT_SQL + where,
args,
binary=True,
)
for value in cur:
checkpoint = self._load_checkpoint(
value["checkpoint"],
value["channel_values"],
value["pending_sends"],
)
return CheckpointTuple(
config={
"configurable": {
"thread_id": thread_id,
"checkpoint_ns": checkpoint_ns,
"checkpoint_id": checkpoint["id"],
}
},
checkpoint=checkpoint,
metadata=self._load_metadata(value["metadata"]),
pending_writes=self._load_writes(value["pending_writes"]),
)
def put(
self,
config: RunnableConfig,
checkpoint: Checkpoint,
metadata: CheckpointMetadata,
new_versions: ChannelVersions,
) -> RunnableConfig:
"""Save a checkpoint to the database.
This method saves a checkpoint to the Postgres database. The checkpoint is associated
with the provided config. For ShallowPostgresSaver, this method saves ONLY the most recent
checkpoint and overwrites a previous checkpoint, if it exists.
Args:
config (RunnableConfig): The config to associate with the checkpoint.
checkpoint (Checkpoint): The checkpoint to save.
metadata (CheckpointMetadata): Additional metadata to save with the checkpoint.
new_versions (ChannelVersions): New channel versions as of this write.
Returns:
RunnableConfig: Updated configuration after storing the checkpoint.
Examples:
>>> from langgraph.checkpoint.postgres import ShallowPostgresSaver
>>> DB_URI = "postgres://postgres:postgres@localhost:5432/postgres?sslmode=disable"
>>> with ShallowPostgresSaver.from_conn_string(DB_URI) as memory:
>>> config = {"configurable": {"thread_id": "1", "checkpoint_ns": ""}}
>>> checkpoint = {"ts": "2024-05-04T06:32:42.235444+00:00", "id": "1ef4f797-8335-6428-8001-8a1503f9b875", "channel_values": {"key": "value"}}
>>> saved_config = memory.put(config, checkpoint, {"source": "input", "step": 1, "writes": {"key": "value"}}, {})
>>> print(saved_config)
{'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1ef4f797-8335-6428-8001-8a1503f9b875'}}
"""
configurable = config["configurable"].copy()
thread_id = configurable.pop("thread_id")
checkpoint_ns = configurable.pop("checkpoint_ns")
copy = checkpoint.copy()
next_config = {
"configurable": {
"thread_id": thread_id,
"checkpoint_ns": checkpoint_ns,
"checkpoint_id": checkpoint["id"],
}
}
with self._cursor(pipeline=True) as cur:
cur.execute(
"""DELETE FROM checkpoint_writes
WHERE thread_id = %s AND checkpoint_ns = %s AND checkpoint_id NOT IN (%s, %s)""",
(
thread_id,
checkpoint_ns,
checkpoint["id"],
configurable.get("checkpoint_id", ""),
),
)
cur.executemany(
self.UPSERT_CHECKPOINT_BLOBS_SQL,
_dump_blobs(
self.serde,
thread_id,
checkpoint_ns,
copy.pop("channel_values"), # type: ignore[misc]
new_versions,
),
)
cur.execute(
self.UPSERT_CHECKPOINTS_SQL,
(
thread_id,
checkpoint_ns,
Jsonb(self._dump_checkpoint(copy)),
self._dump_metadata(metadata),
),
)
return next_config
def put_writes(
self,
config: RunnableConfig,
writes: Sequence[tuple[str, Any]],
task_id: str,
) -> None:
"""Store intermediate writes linked to a checkpoint.
This method saves intermediate writes associated with a checkpoint to the Postgres database.
Args:
config (RunnableConfig): Configuration of the related checkpoint.
writes (List[Tuple[str, Any]]): List of writes to store.
task_id (str): Identifier for the task creating the writes.
"""
query = (
self.UPSERT_CHECKPOINT_WRITES_SQL
if all(w[0] in WRITES_IDX_MAP for w in writes)
else self.INSERT_CHECKPOINT_WRITES_SQL
)
with self._cursor(pipeline=True) as cur:
cur.executemany(
query,
self._dump_writes(
config["configurable"]["thread_id"],
config["configurable"]["checkpoint_ns"],
config["configurable"]["checkpoint_id"],
task_id,
writes,
),
)
@contextmanager
def _cursor(self, *, pipeline: bool = False) -> Iterator[Cursor[DictRow]]:
"""Create a database cursor as a context manager.
Args:
pipeline (bool): whether to use pipeline for the DB operations inside the context manager.
Will be applied regardless of whether the ShallowPostgresSaver instance was initialized with a pipeline.
If pipeline mode is not supported, will fall back to using transaction context manager.
"""
with _internal.get_connection(self.conn) as conn:
if self.pipe:
# a connection in pipeline mode can be used concurrently
# in multiple threads/coroutines, but only one cursor can be
# used at a time
try:
with conn.cursor(binary=True, row_factory=dict_row) as cur:
yield cur
finally:
if pipeline:
self.pipe.sync()
elif pipeline:
# a connection not in pipeline mode can only be used by one
# thread/coroutine at a time, so we acquire a lock
if self.supports_pipeline:
with (
self.lock,
conn.pipeline(),
conn.cursor(binary=True, row_factory=dict_row) as cur,
):
yield cur
else:
# Use connection's transaction context manager when pipeline mode not supported
with (
self.lock,
conn.transaction(),
conn.cursor(binary=True, row_factory=dict_row) as cur,
):
yield cur
else:
with self.lock, conn.cursor(binary=True, row_factory=dict_row) as cur:
yield cur
class AsyncShallowPostgresSaver(BasePostgresSaver):
"""A checkpoint saver that uses Postgres to store checkpoints asynchronously.
This checkpointer ONLY stores the most recent checkpoint and does NOT retain any history.
It is meant to be a light-weight drop-in replacement for the AsyncPostgresSaver that
supports most of the LangGraph persistence functionality with the exception of time travel.
"""
SELECT_SQL = SELECT_SQL
MIGRATIONS = MIGRATIONS
UPSERT_CHECKPOINT_BLOBS_SQL = UPSERT_CHECKPOINT_BLOBS_SQL
UPSERT_CHECKPOINTS_SQL = UPSERT_CHECKPOINTS_SQL
UPSERT_CHECKPOINT_WRITES_SQL = UPSERT_CHECKPOINT_WRITES_SQL
INSERT_CHECKPOINT_WRITES_SQL = INSERT_CHECKPOINT_WRITES_SQL
lock: asyncio.Lock
def __init__(
self,
conn: _ainternal.Conn,
pipe: Optional[AsyncPipeline] = None,
serde: Optional[SerializerProtocol] = None,
) -> None:
super().__init__(serde=serde)
if isinstance(conn, AsyncConnectionPool) and pipe is not None:
raise ValueError(
"Pipeline should be used only with a single AsyncConnection, not AsyncConnectionPool."
)
self.conn = conn
self.pipe = pipe
self.lock = asyncio.Lock()
self.loop = asyncio.get_running_loop()
self.supports_pipeline = Capabilities().has_pipeline()
@classmethod
@asynccontextmanager
async def from_conn_string(
cls,
conn_string: str,
*,
pipeline: bool = False,
serde: Optional[SerializerProtocol] = None,
) -> AsyncIterator["AsyncShallowPostgresSaver"]:
"""Create a new AsyncShallowPostgresSaver instance from a connection string.
Args:
conn_string (str): The Postgres connection info string.
pipeline (bool): whether to use AsyncPipeline
Returns:
AsyncShallowPostgresSaver: A new AsyncShallowPostgresSaver instance.
"""
async with await AsyncConnection.connect(
conn_string, autocommit=True, prepare_threshold=0, row_factory=dict_row
) as conn:
if pipeline:
async with conn.pipeline() as pipe:
yield cls(conn=conn, pipe=pipe, serde=serde)
else:
yield cls(conn=conn, serde=serde)
async def setup(self) -> None:
"""Set up the checkpoint database asynchronously.
This method creates the necessary tables in the Postgres database if they don't
already exist and runs database migrations. It MUST be called directly by the user
the first time checkpointer is used.
"""
async with self._cursor() as cur:
await cur.execute(self.MIGRATIONS[0])
results = await cur.execute(
"SELECT v FROM checkpoint_migrations ORDER BY v DESC LIMIT 1"
)
row = await results.fetchone()
if row is None:
version = -1
else:
version = row["v"]
for v, migration in zip(
range(version + 1, len(self.MIGRATIONS)),
self.MIGRATIONS[version + 1 :],
):
await cur.execute(migration)
await cur.execute(f"INSERT INTO checkpoint_migrations (v) VALUES ({v})")
if self.pipe:
await self.pipe.sync()
async def alist(
self,
config: Optional[RunnableConfig],
*,
filter: Optional[dict[str, Any]] = None,
before: Optional[RunnableConfig] = None,
limit: Optional[int] = None,
) -> AsyncIterator[CheckpointTuple]:
"""List checkpoints from the database asynchronously.
This method retrieves a list of checkpoint tuples from the Postgres database based
on the provided config. For ShallowPostgresSaver, this method returns a list with
ONLY the most recent checkpoint.
"""
where, args = self._search_where(config, filter, before)
query = self.SELECT_SQL + where
if limit:
query += f" LIMIT {limit}"
async with self._cursor() as cur:
await cur.execute(self.SELECT_SQL + where, args, binary=True)
async for value in cur:
checkpoint = await asyncio.to_thread(
self._load_checkpoint,
value["checkpoint"],
value["channel_values"],
value["pending_sends"],
)
yield CheckpointTuple(
config={
"configurable": {
"thread_id": value["thread_id"],
"checkpoint_ns": value["checkpoint_ns"],
"checkpoint_id": checkpoint["id"],
}
},
checkpoint=checkpoint,
metadata=self._load_metadata(value["metadata"]),
pending_writes=await asyncio.to_thread(
self._load_writes, value["pending_writes"]
),
)
async def aget_tuple(self, config: RunnableConfig) -> Optional[CheckpointTuple]:
"""Get a checkpoint tuple from the database asynchronously.
This method retrieves a checkpoint tuple from the Postgres database based on the
provided config (matching the thread ID in the config).
Args:
config (RunnableConfig): The config to use for retrieving the checkpoint.
Returns:
Optional[CheckpointTuple]: The retrieved checkpoint tuple, or None if no matching checkpoint was found.
"""
thread_id = config["configurable"]["thread_id"]
checkpoint_ns = config["configurable"].get("checkpoint_ns", "")
args = (thread_id, checkpoint_ns)
where = "WHERE thread_id = %s AND checkpoint_ns = %s"
async with self._cursor() as cur:
await cur.execute(
self.SELECT_SQL + where,
args,
binary=True,
)
async for value in cur:
checkpoint = await asyncio.to_thread(
self._load_checkpoint,
value["checkpoint"],
value["channel_values"],
value["pending_sends"],
)
return CheckpointTuple(
config={
"configurable": {
"thread_id": thread_id,
"checkpoint_ns": checkpoint_ns,
"checkpoint_id": checkpoint["id"],
}
},
checkpoint=checkpoint,
metadata=self._load_metadata(value["metadata"]),
pending_writes=await asyncio.to_thread(
self._load_writes, value["pending_writes"]
),
)
async def aput(
self,
config: RunnableConfig,
checkpoint: Checkpoint,
metadata: CheckpointMetadata,
new_versions: ChannelVersions,
) -> RunnableConfig:
"""Save a checkpoint to the database asynchronously.
This method saves a checkpoint to the Postgres database. The checkpoint is associated
with the provided config. For AsyncShallowPostgresSaver, this method saves ONLY the most recent
checkpoint and overwrites a previous checkpoint, if it exists.
Args:
config (RunnableConfig): The config to associate with the checkpoint.
checkpoint (Checkpoint): The checkpoint to save.
metadata (CheckpointMetadata): Additional metadata to save with the checkpoint.
new_versions (ChannelVersions): New channel versions as of this write.
Returns:
RunnableConfig: Updated configuration after storing the checkpoint.
"""
configurable = config["configurable"].copy()
thread_id = configurable.pop("thread_id")
checkpoint_ns = configurable.pop("checkpoint_ns")
copy = checkpoint.copy()
next_config = {
"configurable": {
"thread_id": thread_id,
"checkpoint_ns": checkpoint_ns,
"checkpoint_id": checkpoint["id"],
}
}
async with self._cursor(pipeline=True) as cur:
await cur.execute(
"""DELETE FROM checkpoint_writes
WHERE thread_id = %s AND checkpoint_ns = %s AND checkpoint_id NOT IN (%s, %s)""",
(
thread_id,
checkpoint_ns,
checkpoint["id"],
configurable.get("checkpoint_id", ""),
),
)
await cur.executemany(
self.UPSERT_CHECKPOINT_BLOBS_SQL,
_dump_blobs(
self.serde,
thread_id,
checkpoint_ns,
copy.pop("channel_values"), # type: ignore[misc]
new_versions,
),
)
await cur.execute(
self.UPSERT_CHECKPOINTS_SQL,
(
thread_id,
checkpoint_ns,
Jsonb(self._dump_checkpoint(copy)),
self._dump_metadata(metadata),
),
)
return next_config
async def aput_writes(
self,
config: RunnableConfig,
writes: Sequence[tuple[str, Any]],
task_id: str,
) -> None:
"""Store intermediate writes linked to a checkpoint asynchronously.
This method saves intermediate writes associated with a checkpoint to the database.
Args:
config (RunnableConfig): Configuration of the related checkpoint.
writes (Sequence[Tuple[str, Any]]): List of writes to store, each as (channel, value) pair.
task_id (str): Identifier for the task creating the writes.
"""
query = (
self.UPSERT_CHECKPOINT_WRITES_SQL
if all(w[0] in WRITES_IDX_MAP for w in writes)
else self.INSERT_CHECKPOINT_WRITES_SQL
)
params = await asyncio.to_thread(
self._dump_writes,
config["configurable"]["thread_id"],
config["configurable"]["checkpoint_ns"],
config["configurable"]["checkpoint_id"],
task_id,
writes,
)
async with self._cursor(pipeline=True) as cur:
await cur.executemany(query, params)
@asynccontextmanager
async def _cursor(
self, *, pipeline: bool = False
) -> AsyncIterator[AsyncCursor[DictRow]]:
"""Create a database cursor as a context manager.
Args:
pipeline (bool): whether to use pipeline for the DB operations inside the context manager.
Will be applied regardless of whether the AsyncShallowPostgresSaver instance was initialized with a pipeline.
If pipeline mode is not supported, will fall back to using transaction context manager.
"""
async with _ainternal.get_connection(self.conn) as conn:
if self.pipe:
# a connection in pipeline mode can be used concurrently
# in multiple threads/coroutines, but only one cursor can be
# used at a time
try:
async with conn.cursor(binary=True, row_factory=dict_row) as cur:
yield cur
finally:
if pipeline:
await self.pipe.sync()
elif pipeline:
# a connection not in pipeline mode can only be used by one
# thread/coroutine at a time, so we acquire a lock
if self.supports_pipeline:
async with (
self.lock,
conn.pipeline(),
conn.cursor(binary=True, row_factory=dict_row) as cur,
):
yield cur
else:
# Use connection's transaction context manager when pipeline mode not supported
async with (
self.lock,
conn.transaction(),
conn.cursor(binary=True, row_factory=dict_row) as cur,
):
yield cur
else:
async with (
self.lock,
conn.cursor(binary=True, row_factory=dict_row) as cur,
):
yield cur
def list(
self,
config: Optional[RunnableConfig],
*,
filter: Optional[dict[str, Any]] = None,
before: Optional[RunnableConfig] = None,
limit: Optional[int] = None,
) -> Iterator[CheckpointTuple]:
"""List checkpoints from the database.
This method retrieves a list of checkpoint tuples from the Postgres database based
on the provided config. For ShallowPostgresSaver, this method returns a list with
ONLY the most recent checkpoint.
"""
aiter_ = self.alist(config, filter=filter, before=before, limit=limit)
while True:
try:
yield asyncio.run_coroutine_threadsafe(
anext(aiter_), # noqa: F821
self.loop,
).result()
except StopAsyncIteration:
break
def get_tuple(self, config: RunnableConfig) -> Optional[CheckpointTuple]:
"""Get a checkpoint tuple from the database.
This method retrieves a checkpoint tuple from the Postgres database based on the
provided config (matching the thread ID in the config).
Args:
config (RunnableConfig): The config to use for retrieving the checkpoint.
Returns:
Optional[CheckpointTuple]: The retrieved checkpoint tuple, or None if no matching checkpoint was found.
"""
try:
# check if we are in the main thread, only bg threads can block
# we don't check in other methods to avoid the overhead
if asyncio.get_running_loop() is self.loop:
raise asyncio.InvalidStateError(
"Synchronous calls to AsyncShallowPostgresSaver are only allowed from a "
"different thread. From the main thread, use the async interface."
"For example, use `await checkpointer.aget_tuple(...)` or `await "
"graph.ainvoke(...)`."
)
except RuntimeError:
pass
return asyncio.run_coroutine_threadsafe(
self.aget_tuple(config), self.loop
).result()
def put(
self,
config: RunnableConfig,
checkpoint: Checkpoint,
metadata: CheckpointMetadata,
new_versions: ChannelVersions,
) -> RunnableConfig:
"""Save a checkpoint to the database.
This method saves a checkpoint to the Postgres database. The checkpoint is associated
with the provided config. For AsyncShallowPostgresSaver, this method saves ONLY the most recent
checkpoint and overwrites a previous checkpoint, if it exists.
Args:
config (RunnableConfig): The config to associate with the checkpoint.
checkpoint (Checkpoint): The checkpoint to save.
metadata (CheckpointMetadata): Additional metadata to save with the checkpoint.
new_versions (ChannelVersions): New channel versions as of this write.
Returns:
RunnableConfig: Updated configuration after storing the checkpoint.
"""
return asyncio.run_coroutine_threadsafe(
self.aput(config, checkpoint, metadata, new_versions), self.loop
).result()
def put_writes(
self,
config: RunnableConfig,
writes: Sequence[tuple[str, Any]],
task_id: str,
) -> None:
"""Store intermediate writes linked to a checkpoint.
This method saves intermediate writes associated with a checkpoint to the database.
Args:
config (RunnableConfig): Configuration of the related checkpoint.
writes (Sequence[Tuple[str, Any]]): List of writes to store, each as (channel, value) pair.
task_id (str): Identifier for the task creating the writes.
"""
return asyncio.run_coroutine_threadsafe(
self.aput_writes(config, writes, task_id), self.loop
).result()
+1 -1
View File
@@ -1,6 +1,6 @@
[tool.poetry]
name = "langgraph-checkpoint-postgres"
version = "2.0.8"
version = "2.0.10"
description = "Library with a Postgres implementation of LangGraph checkpoint saver."
authors = []
license = "MIT"
+36 -3
View File
@@ -16,7 +16,10 @@ from langgraph.checkpoint.base import (
create_checkpoint,
empty_checkpoint,
)
from langgraph.checkpoint.postgres.aio import AsyncPostgresSaver
from langgraph.checkpoint.postgres.aio import (
AsyncPostgresSaver,
AsyncShallowPostgresSaver,
)
from tests.conftest import DEFAULT_POSTGRES_URI
@@ -103,11 +106,41 @@ async def _base_saver():
await conn.execute(f"DROP DATABASE {database}")
@asynccontextmanager
async def _shallow_saver():
"""Fixture for shallow connection mode testing."""
database = f"test_{uuid4().hex[:16]}"
# create unique db
async with await AsyncConnection.connect(
DEFAULT_POSTGRES_URI, autocommit=True
) as conn:
await conn.execute(f"CREATE DATABASE {database}")
try:
async with await AsyncConnection.connect(
DEFAULT_POSTGRES_URI + database,
autocommit=True,
prepare_threshold=0,
row_factory=dict_row,
) as conn:
checkpointer = AsyncShallowPostgresSaver(conn)
await checkpointer.setup()
yield checkpointer
finally:
# drop unique db
async with await AsyncConnection.connect(
DEFAULT_POSTGRES_URI, autocommit=True
) as conn:
await conn.execute(f"DROP DATABASE {database}")
@asynccontextmanager
async def _saver(name: str):
if name == "base":
async with _base_saver() as saver:
yield saver
elif name == "shallow":
async with _shallow_saver() as saver:
yield saver
elif name == "pool":
async with _pool_saver() as saver:
yield saver
@@ -167,7 +200,7 @@ def test_data():
}
@pytest.mark.parametrize("saver_name", ["base", "pool", "pipe"])
@pytest.mark.parametrize("saver_name", ["base", "pool", "pipe", "shallow"])
async def test_asearch(request, saver_name: str, test_data) -> None:
async with _saver(saver_name) as saver:
configs = test_data["configs"]
@@ -212,7 +245,7 @@ async def test_asearch(request, saver_name: str, test_data) -> None:
} == {"", "inner"}
@pytest.mark.parametrize("saver_name", ["base", "pool", "pipe"])
@pytest.mark.parametrize("saver_name", ["base", "pool", "pipe", "shallow"])
async def test_null_chars(request, saver_name: str, test_data) -> None:
async with _saver(saver_name) as saver:
config = await saver.aput(
@@ -1,5 +1,6 @@
# type: ignore
import re
from contextlib import contextmanager
from typing import Any, Optional
from uuid import uuid4
@@ -782,3 +783,10 @@ def test_scores(
assert len(results) == 1
assert results[0].score == pytest.approx(similarities[0], abs=1e-3)
def test_nonnull_migrations() -> None:
_leading_comment_remover = re.compile(r"^/\*.*?\*/")
for migration in PostgresStore.MIGRATIONS:
statement = _leading_comment_remover.sub("", migration).split()[0]
assert statement.strip()
+37 -3
View File
@@ -1,5 +1,6 @@
# type: ignore
import re
from contextlib import contextmanager
from typing import Any
from uuid import uuid4
@@ -16,7 +17,7 @@ from langgraph.checkpoint.base import (
create_checkpoint,
empty_checkpoint,
)
from langgraph.checkpoint.postgres import PostgresSaver
from langgraph.checkpoint.postgres import PostgresSaver, ShallowPostgresSaver
from tests.conftest import DEFAULT_POSTGRES_URI
@@ -91,11 +92,37 @@ def _base_saver():
conn.execute(f"DROP DATABASE {database}")
@contextmanager
def _shallow_saver():
"""Fixture for regular connection mode testing with a shallow checkpointer."""
database = f"test_{uuid4().hex[:16]}"
# create unique db
with Connection.connect(DEFAULT_POSTGRES_URI, autocommit=True) as conn:
conn.execute(f"CREATE DATABASE {database}")
try:
with Connection.connect(
DEFAULT_POSTGRES_URI + database,
autocommit=True,
prepare_threshold=0,
row_factory=dict_row,
) as conn:
checkpointer = ShallowPostgresSaver(conn)
checkpointer.setup()
yield checkpointer
finally:
# drop unique db
with Connection.connect(DEFAULT_POSTGRES_URI, autocommit=True) as conn:
conn.execute(f"DROP DATABASE {database}")
@contextmanager
def _saver(name: str):
if name == "base":
with _base_saver() as saver:
yield saver
elif name == "shallow":
with _shallow_saver() as saver:
yield saver
elif name == "pool":
with _pool_saver() as saver:
yield saver
@@ -155,7 +182,7 @@ def test_data():
}
@pytest.mark.parametrize("saver_name", ["base", "pool", "pipe"])
@pytest.mark.parametrize("saver_name", ["base", "pool", "pipe", "shallow"])
def test_search(saver_name: str, test_data) -> None:
with _saver(saver_name) as saver:
configs = test_data["configs"]
@@ -198,7 +225,7 @@ def test_search(saver_name: str, test_data) -> None:
} == {"", "inner"}
@pytest.mark.parametrize("saver_name", ["base", "pool", "pipe"])
@pytest.mark.parametrize("saver_name", ["base", "pool", "pipe", "shallow"])
def test_null_chars(saver_name: str, test_data) -> None:
with _saver(saver_name) as saver:
config = saver.put(
@@ -212,3 +239,10 @@ def test_null_chars(saver_name: str, test_data) -> None:
list(saver.list(None, filter={"my_key": "abc"}))[0].metadata["my_key"]
== "abc"
)
def test_nonnull_migrations() -> None:
_leading_comment_remover = re.compile(r"^/\*.*?\*/")
for migration in PostgresSaver.MIGRATIONS:
statement = _leading_comment_remover.sub("", migration).split()[0]
assert statement.strip()

Some files were not shown because too many files have changed in this diff Show More