Compare commits

..
Author SHA1 Message Date
William Fu-Hinthorn ebb781c0f8 Support mount_prefix in langgraph-sdk
Signed-off-by: William Fu-Hinthorn <13333726+hinthornw@users.noreply.github.com>
2025-04-07 12:36:37 -07:00
Eugene YurtsevandGitHub 05a4fcc8bb cli: release 0.1.89 (#4173)
Release to pick up this: https://github.com/langchain-ai/langgraph/pull/4164
2025-04-04 13:46:47 -04:00
Eugene YurtsevandGitHub adac016e33 cli: support dict format for graph specification in langgraph.json (#4164)
Allow the CLI to work with dict format for the graph specification.

```json
{
  "dependencies": ["./my_agent"],
  "graphs": {
    "agent": {
      "path": "./my_agent/agent.py:graph",
      "description": "this is my agent description"
    }
  },
  "env": ".env"
}
```

And backwards compatible with:

```json
{
  "dependencies": ["./my_agent"],
  "graphs": {
    "agent": "./my_agent/agent.py:graph",
  },
  "env": ".env"
}
```
2025-04-04 10:16:20 -04:00
David DuongandGitHub 2d13904abf release(langgraph): 0.3.25 (#4167) 2025-04-03 22:20:03 +02:00
Tat Dat Duong dfeb9d3b46 release(langgraph): 0.3.25 2025-04-03 22:12:16 +02:00
David DuongandGitHub 81935a73d8 feat(langgraph): Add UI messages API (#4157)
Sample usage:

```python
from typing import Annotated, Sequence, TypedDict

from langchain_core.messages import BaseMessage
from langchain_openai import ChatOpenAI
from langgraph.graph import StateGraph
from langgraph.graph.message import add_messages

from langgraph.graph.ui import AnyUIMessage, ui_message_reducer, push_ui_message


class AgentState(TypedDict):  # noqa: D101
    messages: Annotated[Sequence[BaseMessage], add_messages]
    ui: Annotated[Sequence[AnyUIMessage], ui_message_reducer]


async def agent(state: AgentState):  # noqa: D103
    message = await ChatOpenAI(model="gpt-4o-mini", temperature=0).ainvoke(
        state["messages"]
    )

    # Also directly writes the message to `ui`
    push_ui_message("simple", {"hello": "123"}, message=message, state_key="ui")

    return {"messages": [message]}

```
2025-04-03 22:10:18 +02:00
Tat Dat Duong 615fc8b4ae Update naming 2025-04-03 21:58:16 +02:00
William FHandGitHub 13e6f6cbde Add algolia site verification (#4165) 2025-04-03 12:53:40 -07:00
Vadym BardaandGitHub e89633f30b prebuilt: release 0.1.8 (#4161) 2025-04-03 12:01:18 -04:00
Vadym BardaandGitHub 0bbf5829e8 docs: add a how-to guide for managing message history in create_react_agent (#4149) 2025-04-03 16:00:11 +00:00
David DuongandGitHub 7ed5288f8f release(cli): 0.1.84 (#4158) 2025-04-03 15:25:12 +02:00
Tat Dat Duong cba240e70e release(cli): 0.1.84 2025-04-03 15:15:52 +02:00
Tat Dat Duong e9b5046076 Update docs to include Python API 2025-04-03 14:39:45 +02:00
Tat Dat Duong af6552a17e Move to langgraph/graph 2025-04-03 14:13:26 +02:00
Tat Dat Duong e38c30a434 Other docstring changes 2025-04-03 14:13:26 +02:00
Tat Dat Duong e41dea4cf9 Remove unnecessary return value 2025-04-03 14:13:26 +02:00
Tat Dat Duong f9f8c19ec4 Update docstrings 2025-04-03 14:13:26 +02:00
Tat Dat Duong 64ab3217f6 Add UI messages API 2025-04-03 14:13:26 +02:00
David DuongandGitHub 9af243d138 feat(cli): pass ui and ui config to inmem server, handle Docker setup for UI (#4100) 2025-04-03 14:11:30 +02:00
David DuongandGitHub 3f1d440aee fix(sdk-js): send accepts any input (#4099) 2025-04-03 14:00:31 +02:00
Tat Dat Duong 78901599e6 Add test for UI config 2025-04-03 13:48:14 +02:00
Tat Dat Duong 958c0df2d7 Install Node.js runtime and run the build process to get the UI 2025-04-03 13:48:14 +02:00
Tat Dat Duong 6919de8b3e feat(cli): pass ui and ui config to inmem server 2025-04-03 13:48:14 +02:00
Nuno CamposandGitHub e9a66cef46 Update jinja2 dev dep (#4150) 2025-04-02 16:02:25 -07:00
Nuno CamposandGitHub 728679e48e Bump langchain-core from 0.3.0 to 0.3.15 in /libs/checkpoint-sqlite (#3978)
Bumps [langchain-core](https://github.com/langchain-ai/langchain) from
0.3.0 to 0.3.15.
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/langchain-ai/langchain/commit/25a103187137077d4331e7153fe119e1c0c3ffb6"><code>25a1031</code></a>
community: Fix a validation error for MoonshotChat (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/27801">#27801</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/e4e2aa0b78e6662bb5cebb06b15c19ddbe96ae43"><code>e4e2aa0</code></a>
core[patch]: update image util err msg (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/27803">#27803</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/181bcd05778ff08688ea22f8dab81a6bd27501fd"><code>181bcd0</code></a>
core[patch]: Release 0.3.15 (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/27802">#27802</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/c1e742347f9701aadba8920e4d1f79a636e50b68"><code>c1e7423</code></a>
core[patch]: rm image loading (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/27797">#27797</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/ad0387ac97e0c8feee4272f4ed98f0d65bd616ba"><code>ad0387a</code></a>
Improvement [docs] Improve api docs (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/27787">#27787</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/d9163e7afa0e5e975d36b7482c6a101e5c5dc375"><code>d9163e7</code></a>
community[docs]: Add content for the Lora adapter in the VLLM page. (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/27788">#27788</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/0172d938b4bf1e9da9f3b796dbfce64c565ce565"><code>0172d93</code></a>
community: add AzureOpenAIWhisperParser (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/27796">#27796</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/b631b0a5964bcdd46cf30fa0e91925d724ec7ae8"><code>b631b0a</code></a>
community[patch]: cap SQLAlchemy and update deps (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/27792">#27792</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/8ad7adad8784d01ad1cbbb8b4c5f8102dbf11a63"><code>8ad7ada</code></a>
infra: build api docs from package listing (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/27774">#27774</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/3952ee31b8fc95b1ed74b83429002a7b5da630a3"><code>3952ee3</code></a>
ollama: add pydocstyle linting for ollama (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/27686">#27686</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/langchain-ai/langchain/compare/langchain-core==0.3.0...langchain-core==0.3.15">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=langchain-core&package-manager=pip&previous-version=0.3.0&new-version=0.3.15)](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>
2025-04-02 15:57:14 -07:00
Nuno CamposandGitHub f90c81f280 Bump langchain-core from 0.2.38 to 0.2.43 in /libs/checkpoint (#3979)
Bumps [langchain-core](https://github.com/langchain-ai/langchain) from
0.2.38 to 0.2.43.
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/langchain-ai/langchain/commit/9fdeb74d9965258ad077d535681d9bae84b58e08"><code>9fdeb74</code></a>
core[patch]: Release 0.2.43 (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/27808">#27808</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/7d481f10102f43559cc57bcad7eba291067939ee"><code>7d481f1</code></a>
core[patch]: remove prompt img loading (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/27807">#27807</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/33a53970e1eb9a515d0d38809dd0d7f2e556c4ae"><code>33a5397</code></a>
infra: turn off release attestations (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/27766">#27766</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/283cb50ea69d03d082db5607b68f6d13aa4e65a1"><code>283cb50</code></a>
core[patch]: Release 0.2.42 (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/27763">#27763</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/5e3cee6c98a55d5303847c6f22256e0671caa83f"><code>5e3cee6</code></a>
core[patch]: make get_all_basemodel_annotations public (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/27762">#27762</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/807314661dbd8933d18a0289df05bd381ffbbc4f"><code>8073146</code></a>
Added mapping to fix CI for #langchain-aws:227. (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/27114">#27114</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/6cfd1e846a3f7369d33a77c3fb60314e3e60e202"><code>6cfd1e8</code></a>
core[patch]: Release 0.2.41 (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/26687">#26687</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/c5eca37262132b7b88d0f68835af5fea49d13494"><code>c5eca37</code></a>
core[patch]: Fixed bedrock chat model load. (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/26643">#26643</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/70c992bd61c48ebb4f60bde0ff9bfd7b54393678"><code>70c992b</code></a>
community: poetry lock for cffi dep (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/26674">#26674</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/51fd70be63afeaecf0eff78c2a6f6d3f34330203"><code>51fd70b</code></a>
infra: 0.2 release checkout ref for release note (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/26604">#26604</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/langchain-ai/langchain/compare/langchain-core==0.2.38...langchain-core==0.2.43">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=langchain-core&package-manager=pip&previous-version=0.2.38&new-version=0.2.43)](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>
2025-04-02 15:56:20 -07:00
Nuno Campos f118a61101 Update jinja2 dev dep 2025-04-02 15:56:00 -07:00
dependabot[bot]andNuno Campos 8963bb2b68 Bump langchain-core from 0.3.0 to 0.3.15 in /libs/checkpoint-sqlite
Bumps [langchain-core](https://github.com/langchain-ai/langchain) from 0.3.0 to 0.3.15.
- [Release notes](https://github.com/langchain-ai/langchain/releases)
- [Commits](https://github.com/langchain-ai/langchain/compare/langchain-core==0.3.0...langchain-core==0.3.15)

---
updated-dependencies:
- dependency-name: langchain-core
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-02 15:50:49 -07:00
dependabot[bot]andNuno Campos 499a1812e8 Bump langchain-core from 0.2.38 to 0.2.43 in /libs/checkpoint
Bumps [langchain-core](https://github.com/langchain-ai/langchain) from 0.2.38 to 0.2.43.
- [Release notes](https://github.com/langchain-ai/langchain/releases)
- [Commits](https://github.com/langchain-ai/langchain/compare/langchain-core==0.2.38...langchain-core==0.2.43)

---
updated-dependencies:
- dependency-name: langchain-core
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-02 15:49:49 -07:00
Tat Dat Duong aef463c633 fix(sdk-js): send accepts any input 2025-04-01 00:15:32 +02:00
25 changed files with 1760 additions and 841 deletions
@@ -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
File diff suppressed because one or more lines are too long
+109 -55
View File
@@ -12,10 +12,6 @@ Generative user interfaces (Generative UI) allows agents to go beyond text and g
LangGraph Platform supports colocating your React components with your graph code. This allows you to focus on building specific UI components for your graph while easily plugging into existing chat interfaces such as [Agent Chat](https://agentchat.vercel.app) and loading the code only when actually needed.
!!! warning "LangGraph.js only"
Currently only LangGraph.js supports Generative UI. Support for Python is coming soon.
## Tutorial
### 1. Define and configure UI components
@@ -74,58 +70,105 @@ CSS and Tailwind 4.x is also supported out of the box, so you can freely use Tai
### 2. Send the UI components in your graph
Use the `typedUi` utility to emit UI elements from your agent nodes:
=== "Python"
```typescript title="src/agent/index.ts"
import {
typedUi,
uiMessageReducer,
} from "@langchain/langgraph-sdk/react-ui/server";
```python title="src/agent.py"
import uuid
from typing import Annotated, Sequence, TypedDict
import { ChatOpenAI } from "@langchain/openai";
import { v4 as uuidv4 } from "uuid";
import { z } from "zod";
from langchain_core.messages import AIMessage, BaseMessage
from langchain_openai import ChatOpenAI
from langgraph.graph import StateGraph
from langgraph.graph.message import add_messages
from langgraph.graph.ui import AnyUIMessage, ui_message_reducer, push_ui_message
import type ComponentMap from "./ui.js";
import {
Annotation,
MessagesAnnotation,
StateGraph,
type LangGraphRunnableConfig,
} from "@langchain/langgraph";
class AgentState(TypedDict): # noqa: D101
messages: Annotated[Sequence[BaseMessage], add_messages]
ui: Annotated[Sequence[AnyUIMessage], ui_message_reducer]
const AgentState = Annotation.Root({
...MessagesAnnotation.spec,
ui: Annotation({ reducer: uiMessageReducer, default: () => [] }),
});
export const graph = new StateGraph(AgentState)
.addNode("weather", async (state, config) => {
// Provide the type of the component map to ensure
// type safety of `ui.push()` calls as well as
// pushing the messages to the `ui` and sending a custom event as well.
const ui = typedUi<typeof ComponentMap>(config);
async def weather(state: AgentState):
class WeatherOutput(TypedDict):
city: str
const weather = await new ChatOpenAI({ model: "gpt-4o-mini" })
.withStructuredOutput(z.object({ city: z.string() }))
.withConfig({ tags: ["langsmith:nostream"] })
.invoke(state.messages);
weather: WeatherOutput = (
await ChatOpenAI(model="gpt-4o-mini")
.with_structured_output(WeatherOutput)
.with_config({"tags": ["nostream"]})
.ainvoke(state["messages"])
)
const response = {
id: uuidv4(),
type: "ai",
content: `Here's the weather for ${weather.city}`,
};
message = AIMessage(
id=str(uuid.uuid4()),
content=f"Here's the weather for {weather['city']}",
)
// Emit UI elements with associated AI message
ui.push({ name: "weather", props: weather }, { message: response });
# Emit UI elements associated with the message
push_ui_message("weather", weather, message=message)
return {"messages": [message]}
return { messages: [response] };
})
.addEdge("__start__", "weather")
.compile();
```
workflow = StateGraph(AgentState)
workflow.add_node(weather)
workflow.add_edge("__start__", "weather")
graph = workflow.compile()
```
=== "JS"
Use the `typedUi` utility to emit UI elements from your agent nodes:
```typescript title="src/agent/index.ts"
import {
typedUi,
uiMessageReducer,
} from "@langchain/langgraph-sdk/react-ui/server";
import { ChatOpenAI } from "@langchain/openai";
import { v4 as uuidv4 } from "uuid";
import { z } from "zod";
import type ComponentMap from "./ui.js";
import {
Annotation,
MessagesAnnotation,
StateGraph,
type LangGraphRunnableConfig,
} from "@langchain/langgraph";
const AgentState = Annotation.Root({
...MessagesAnnotation.spec,
ui: Annotation({ reducer: uiMessageReducer, default: () => [] }),
});
export const graph = new StateGraph(AgentState)
.addNode("weather", async (state, config) => {
// Provide the type of the component map to ensure
// type safety of `ui.push()` calls as well as
// pushing the messages to the `ui` and sending a custom event as well.
const ui = typedUi<typeof ComponentMap>(config);
const weather = await new ChatOpenAI({ model: "gpt-4o-mini" })
.withStructuredOutput(z.object({ city: z.string() }))
.withConfig({ tags: ["nostream"] })
.invoke(state.messages);
const response = {
id: uuidv4(),
type: "ai",
content: `Here's the weather for ${weather.city}`,
};
// Emit UI elements associated with the AI message
ui.push({ name: "weather", props: weather }, { message: response });
return { messages: [response] };
})
.addEdge("__start__", "weather")
.compile();
```
### 3. Handle UI elements in your React application
@@ -294,18 +337,29 @@ const { thread, submit } = useStream({
### Remove UI messages from state
Similar to how messages can be removed from the state by appending a RemoveMessage you can remove an UI message from the state by calling `ui.delete` with the ID of the UI message.
Similar to how messages can be removed from the state by appending a RemoveMessage you can remove an UI message from the state by calling `remove_ui_message` / `ui.delete` with the ID of the UI message.
```tsx
// pushed message
const message = ui.push({ name: "weather", props: { city: "London" } });
=== "Python"
// remove said message
ui.delete(message.id);
```python
from langgraph.graph.ui import push_ui_message, delete_ui_message
// return new state to persist changes
return { ui: ui.items };
```
# push message
message = push_ui_message("weather", {"city": "London"})
# remove said message
delete_ui_message(message["id"])
```
=== "JS"
```tsx
// push message
const message = ui.push({ name: "weather", props: { city: "London" } });
// remove said message
ui.delete(message.id);
```
## Learn more
File diff suppressed because one or more lines are too long
+1
View File
@@ -163,6 +163,7 @@ These guides show how to use the prebuilt ReAct agent:
- [How to add human-in-the-loop processes to a ReAct agent](create-react-agent-hitl.ipynb)
- [How to return structured output from a ReAct agent](create-react-agent-structured-output.ipynb)
- [How to add semantic search for long-term memory to a ReAct agent](memory/semantic-search.ipynb#using-in-create-react-agent)
- [How to manage message history in a ReAct agent](create-react-agent-manage-message-history.ipynb)
Interested in further customizing the ReAct agent? This guide provides an
overview of its underlying implementation to help you customize for your own needs:
+1
View File
@@ -185,6 +185,7 @@ nav:
- how-tos/create-react-agent-system-prompt.ipynb
- how-tos/create-react-agent-hitl.ipynb
- how-tos/create-react-agent-structured-output.ipynb
- how-tos/create-react-agent-manage-message-history.ipynb
- how-tos/react-agent-from-scratch.ipynb
- how-tos/react-agent-from-scratch-functional.ipynb
- LangGraph Platform:
+1
View File
@@ -1,6 +1,7 @@
{% extends "base.html" %}
{% block extrahead %}
<meta name="algolia-site-verification" content="165B7E7C89E49946" />
<style>
@import url("https://fonts.googleapis.com/css2?family=Public+Sans&display=swap");
:root {
+240 -695
View File
File diff suppressed because it is too large Load Diff
+2 -1
View File
@@ -50,7 +50,8 @@ langchain-community = "^0.3.0"
langchain-experimental = "^0.3.2"
langchain-mistralai = "^0.2.6"
langgraph-checkpoint-mongodb = "^0.1.0"
langsmith = "^0.2.0"
langmem = "^0.0.19"
langsmith = "^0.3.0"
chromadb = "^0.5.5"
gpt4all = "^2.8.2"
scikit-learn = "^1.5.2"
+34 -14
View File
@@ -1,4 +1,4 @@
# This file is automatically @generated by Poetry 2.0.0 and should not be changed by hand.
# This file is automatically @generated by Poetry 2.1.1 and should not be changed by hand.
[[package]]
name = "aiosqlite"
@@ -51,7 +51,7 @@ typing-extensions = {version = ">=4.1", markers = "python_version < \"3.11\""}
[package.extras]
doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"]
test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"]
test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17) ; platform_python_implementation == \"CPython\" and platform_system != \"Windows\""]
trio = ["trio (>=0.23)"]
[[package]]
@@ -181,7 +181,7 @@ files = [
[package.extras]
dev = ["Pygments", "build", "chardet", "pre-commit", "pytest", "pytest-cov", "pytest-dependency", "ruff", "tomli", "twine"]
hard-encoding-detection = ["chardet"]
toml = ["tomli"]
toml = ["tomli ; python_version < \"3.11\""]
types = ["chardet (>=5.1.0)", "mypy", "pytest", "pytest-cov", "pytest-dependency"]
[[package]]
@@ -267,7 +267,7 @@ idna = "*"
sniffio = "*"
[package.extras]
brotli = ["brotli", "brotlicffi"]
brotli = ["brotli ; platform_python_implementation == \"CPython\"", "brotlicffi ; platform_python_implementation != \"CPython\""]
cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"]
http2 = ["h2 (>=3,<5)"]
socks = ["socksio (==1.*)"]
@@ -326,26 +326,26 @@ files = [
[[package]]
name = "langchain-core"
version = "0.3.0"
version = "0.3.15"
description = "Building applications with LLMs through composability"
optional = false
python-versions = "<4.0,>=3.9"
groups = ["main", "dev"]
files = [
{file = "langchain_core-0.3.0-py3-none-any.whl", hash = "sha256:bee6dae2366d037ef0c5b87401fed14b5497cad26f97724e8c9ca7bc9239e847"},
{file = "langchain_core-0.3.0.tar.gz", hash = "sha256:1249149ea3ba24c9c761011483c14091573a5eb1a773aa0db9c8ad155dd4a69d"},
{file = "langchain_core-0.3.15-py3-none-any.whl", hash = "sha256:3d4ca6dbb8ed396a6ee061063832a2451b0ce8c345570f7b086ffa7288e4fa29"},
{file = "langchain_core-0.3.15.tar.gz", hash = "sha256:b1a29787a4ffb7ec2103b4e97d435287201da7809b369740dd1e32f176325aba"},
]
[package.dependencies]
jsonpatch = ">=1.33,<2.0"
langsmith = ">=0.1.117,<0.2.0"
langsmith = ">=0.1.125,<0.2.0"
packaging = ">=23.2,<25"
pydantic = [
{version = ">=2.5.2,<3.0.0", markers = "python_full_version < \"3.12.4\""},
{version = ">=2.7.4,<3.0.0", markers = "python_full_version >= \"3.12.4\""},
]
PyYAML = ">=5.3"
tenacity = ">=8.1.0,<8.4.0 || >8.4.0,<9.0.0"
tenacity = ">=8.1.0,<8.4.0 || >8.4.0,<10.0.0"
typing-extensions = ">=4.7"
[[package]]
@@ -368,24 +368,28 @@ url = "../checkpoint"
[[package]]
name = "langsmith"
version = "0.1.120"
version = "0.1.147"
description = "Client library to connect to the LangSmith LLM Tracing and Evaluation Platform."
optional = false
python-versions = "<4.0,>=3.8.1"
groups = ["main", "dev"]
files = [
{file = "langsmith-0.1.120-py3-none-any.whl", hash = "sha256:54d2785e301646c0988e0a69ebe4d976488c87b41928b358cb153b6ddd8db62b"},
{file = "langsmith-0.1.120.tar.gz", hash = "sha256:25499ca187b41bd89d784b272b97a8d76f60e0e21bdf20336e8a2aa6a9b23ac9"},
{file = "langsmith-0.1.147-py3-none-any.whl", hash = "sha256:7166fc23b965ccf839d64945a78e9f1157757add228b086141eb03a60d699a15"},
{file = "langsmith-0.1.147.tar.gz", hash = "sha256:2e933220318a4e73034657103b3b1a3a6109cc5db3566a7e8e03be8d6d7def7a"},
]
[package.dependencies]
httpx = ">=0.23.0,<1"
orjson = ">=3.9.14,<4.0.0"
orjson = {version = ">=3.9.14,<4.0.0", markers = "platform_python_implementation != \"PyPy\""}
pydantic = [
{version = ">=1,<3", markers = "python_full_version < \"3.12.4\""},
{version = ">=2.7.4,<3.0.0", markers = "python_full_version >= \"3.12.4\""},
]
requests = ">=2,<3"
requests-toolbelt = ">=1.0.0,<2.0.0"
[package.extras]
langsmith-pyo3 = ["langsmith-pyo3 (>=0.1.0rc2,<0.2.0)"]
[[package]]
name = "mypy"
@@ -454,6 +458,7 @@ description = "Fast, correct Python JSON library supporting dataclasses, datetim
optional = false
python-versions = ">=3.8"
groups = ["main", "dev"]
markers = "platform_python_implementation != \"PyPy\""
files = [
{file = "orjson-3.10.6-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:fb0ee33124db6eaa517d00890fc1a55c3bfe1cf78ba4a8899d71a06f2d6ff5c7"},
{file = "orjson-3.10.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c1c4b53b24a4c06547ce43e5fee6ec4e0d8fe2d597f4647fc033fd205707365"},
@@ -858,6 +863,21 @@ urllib3 = ">=1.21.1,<3"
socks = ["PySocks (>=1.5.6,!=1.5.7)"]
use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"]
[[package]]
name = "requests-toolbelt"
version = "1.0.0"
description = "A utility belt for advanced users of python-requests"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
groups = ["main", "dev"]
files = [
{file = "requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6"},
{file = "requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06"},
]
[package.dependencies]
requests = ">=2.0.1,<3.0.0"
[[package]]
name = "ruff"
version = "0.6.2"
@@ -952,7 +972,7 @@ files = [
]
[package.extras]
brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"]
brotli = ["brotli (>=1.0.9) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\""]
h2 = ["h2 (>=4,<5)"]
socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"]
zstd = ["zstandard (>=0.18.0)"]
+124 -9
View File
@@ -12,6 +12,29 @@ files = [
{file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"},
]
[[package]]
name = "anyio"
version = "4.9.0"
description = "High level compatibility layer for multiple asynchronous event loop implementations"
optional = false
python-versions = ">=3.9"
groups = ["main"]
files = [
{file = "anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c"},
{file = "anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028"},
]
[package.dependencies]
exceptiongroup = {version = ">=1.0.2", markers = "python_version < \"3.11\""}
idna = ">=2.8"
sniffio = ">=1.1"
typing_extensions = {version = ">=4.5", markers = "python_version < \"3.13\""}
[package.extras]
doc = ["Sphinx (>=8.2,<9.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx_rtd_theme"]
test = ["anyio[trio]", "blockbuster (>=1.5.23)", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "trustme", "truststore (>=0.9.1)", "uvloop (>=0.21)"]
trio = ["trio (>=0.26.1)"]
[[package]]
name = "certifi"
version = "2024.7.4"
@@ -177,7 +200,7 @@ version = "1.2.2"
description = "Backport of PEP 654 (exception groups)"
optional = false
python-versions = ">=3.7"
groups = ["dev"]
groups = ["main", "dev"]
markers = "python_version < \"3.11\""
files = [
{file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"},
@@ -187,6 +210,65 @@ files = [
[package.extras]
test = ["pytest (>=6)"]
[[package]]
name = "h11"
version = "0.14.0"
description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1"
optional = false
python-versions = ">=3.7"
groups = ["main"]
files = [
{file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"},
{file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"},
]
[[package]]
name = "httpcore"
version = "1.0.7"
description = "A minimal low-level HTTP client."
optional = false
python-versions = ">=3.8"
groups = ["main"]
files = [
{file = "httpcore-1.0.7-py3-none-any.whl", hash = "sha256:a3fff8f43dc260d5bd363d9f9cf1830fa3a458b332856f34282de498ed420edd"},
{file = "httpcore-1.0.7.tar.gz", hash = "sha256:8551cb62a169ec7162ac7be8d4817d561f60e08eaa485234898414bb5a8a0b4c"},
]
[package.dependencies]
certifi = "*"
h11 = ">=0.13,<0.15"
[package.extras]
asyncio = ["anyio (>=4.0,<5.0)"]
http2 = ["h2 (>=3,<5)"]
socks = ["socksio (==1.*)"]
trio = ["trio (>=0.22.0,<1.0)"]
[[package]]
name = "httpx"
version = "0.28.1"
description = "The next generation HTTP client."
optional = false
python-versions = ">=3.8"
groups = ["main"]
files = [
{file = "httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad"},
{file = "httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc"},
]
[package.dependencies]
anyio = "*"
certifi = "*"
httpcore = "==1.*"
idna = "*"
[package.extras]
brotli = ["brotli", "brotlicffi"]
cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"]
http2 = ["h2 (>=3,<5)"]
socks = ["socksio (==1.*)"]
zstd = ["zstandard (>=0.18.0)"]
[[package]]
name = "idna"
version = "3.7"
@@ -240,19 +322,19 @@ files = [
[[package]]
name = "langchain-core"
version = "0.2.38"
version = "0.2.43"
description = "Building applications with LLMs through composability"
optional = false
python-versions = "<4.0,>=3.8.1"
groups = ["main"]
files = [
{file = "langchain_core-0.2.38-py3-none-any.whl", hash = "sha256:8a5729bc7e68b4af089af20eff44fe4e7ca21d0e0c87ec21cef7621981fd1a4a"},
{file = "langchain_core-0.2.38.tar.gz", hash = "sha256:eb69dbedd344f2ee1f15bcea6c71a05884b867588fadc42d04632e727c1238f3"},
{file = "langchain_core-0.2.43-py3-none-any.whl", hash = "sha256:619601235113298ebf8252a349754b7c28d3cf7166c7c922da24944b78a9363a"},
{file = "langchain_core-0.2.43.tar.gz", hash = "sha256:42c2ef6adedb911f4254068b6adc9eb4c4075f6c8cb3d83590d3539a815695f5"},
]
[package.dependencies]
jsonpatch = ">=1.33,<2.0"
langsmith = ">=0.1.75,<0.2.0"
langsmith = ">=0.1.112,<0.2.0"
packaging = ">=23.2,<25"
pydantic = [
{version = ">=1,<3", markers = "python_full_version < \"3.12.4\""},
@@ -264,23 +346,28 @@ typing-extensions = ">=4.7"
[[package]]
name = "langsmith"
version = "0.1.93"
version = "0.1.147"
description = "Client library to connect to the LangSmith LLM Tracing and Evaluation Platform."
optional = false
python-versions = "<4.0,>=3.8.1"
groups = ["main"]
files = [
{file = "langsmith-0.1.93-py3-none-any.whl", hash = "sha256:811210b9d5f108f36431bd7b997eb9476a9ecf5a2abd7ddbb606c1cdcf0f43ce"},
{file = "langsmith-0.1.93.tar.gz", hash = "sha256:285b6ad3a54f50fa8eb97b5f600acc57d0e37e139dd8cf2111a117d0435ba9b4"},
{file = "langsmith-0.1.147-py3-none-any.whl", hash = "sha256:7166fc23b965ccf839d64945a78e9f1157757add228b086141eb03a60d699a15"},
{file = "langsmith-0.1.147.tar.gz", hash = "sha256:2e933220318a4e73034657103b3b1a3a6109cc5db3566a7e8e03be8d6d7def7a"},
]
[package.dependencies]
orjson = ">=3.9.14,<4.0.0"
httpx = ">=0.23.0,<1"
orjson = {version = ">=3.9.14,<4.0.0", markers = "platform_python_implementation != \"PyPy\""}
pydantic = [
{version = ">=1,<3", markers = "python_full_version < \"3.12.4\""},
{version = ">=2.7.4,<3.0.0", markers = "python_full_version >= \"3.12.4\""},
]
requests = ">=2,<3"
requests-toolbelt = ">=1.0.0,<2.0.0"
[package.extras]
langsmith-pyo3 = ["langsmith-pyo3 (>=0.1.0rc2,<0.2.0)"]
[[package]]
name = "marshmallow"
@@ -369,6 +456,7 @@ description = "Fast, correct Python JSON library supporting dataclasses, datetim
optional = false
python-versions = ">=3.8"
groups = ["main"]
markers = "platform_python_implementation != \"PyPy\""
files = [
{file = "orjson-3.10.6-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:fb0ee33124db6eaa517d00890fc1a55c3bfe1cf78ba4a8899d71a06f2d6ff5c7"},
{file = "orjson-3.10.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c1c4b53b24a4c06547ce43e5fee6ec4e0d8fe2d597f4647fc033fd205707365"},
@@ -773,6 +861,21 @@ urllib3 = ">=1.21.1,<3"
socks = ["PySocks (>=1.5.6,!=1.5.7)"]
use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"]
[[package]]
name = "requests-toolbelt"
version = "1.0.0"
description = "A utility belt for advanced users of python-requests"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
groups = ["main"]
files = [
{file = "requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6"},
{file = "requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06"},
]
[package.dependencies]
requests = ">=2.0.1,<3.0.0"
[[package]]
name = "ruff"
version = "0.6.2"
@@ -801,6 +904,18 @@ files = [
{file = "ruff-0.6.2.tar.gz", hash = "sha256:239ee6beb9e91feb8e0ec384204a763f36cb53fb895a1a364618c6abb076b3be"},
]
[[package]]
name = "sniffio"
version = "1.3.1"
description = "Sniff out which async library your code is running under"
optional = false
python-versions = ">=3.7"
groups = ["main"]
files = [
{file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"},
{file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"},
]
[[package]]
name = "tenacity"
version = "8.5.0"
+2
View File
@@ -665,6 +665,8 @@ def dev(
wait_for_client=wait_for_client,
auth=config_json.get("auth"),
http=config_json.get("http"),
ui=config_json.get("ui"),
ui_config=config_json.get("ui_config"),
studio_url=studio_url,
allow_blocking=allow_blocking,
)
+105 -58
View File
@@ -778,7 +778,22 @@ def _update_graph_paths(
FileNotFoundError: If the local file (module) does not actually exist on disk.
IsADirectoryError: If `module_str` points to a directory instead of a file.
"""
for graph_id, import_str in config["graphs"].items():
for graph_id, data in config["graphs"].items():
if isinstance(data, dict):
# Then we're looking for a 'path' key
if "path" not in data:
raise ValueError(
f"Graph '{graph_id}' must contain a 'path' key if "
f" it is a dictionary."
)
import_str = data["path"]
elif isinstance(data, str):
import_str = data
else:
raise ValueError(
f"Graph '{graph_id}' must be a string or a dictionary with a 'path' key."
)
module_str, _, attr_str = import_str.partition(":")
if not module_str or not attr_str:
message = (
@@ -818,7 +833,10 @@ def _update_graph_paths(
"Add its containing package to 'dependencies' list."
)
# update the config
config["graphs"][graph_id] = f"{module_str}:{attr_str}"
if isinstance(data, dict):
config["graphs"][graph_id]["path"] = f"{module_str}:{attr_str}"
else:
config["graphs"][graph_id] = f"{module_str}:{attr_str}"
def _update_auth_path(
@@ -915,6 +933,66 @@ def _update_http_app_path(
http_config["app"] = f"{module_str}:{attr_str}"
def _get_node_pm_install_cmd(config_path: pathlib.Path, config: Config) -> str:
def test_file(file_name):
full_path = config_path.parent / file_name
try:
return full_path.is_file()
except OSError:
return False
# inspired by `package-manager-detector`
def get_pkg_manager_name():
try:
with open(config_path.parent / "package.json") as f:
pkg = json.load(f)
if (pkg_manager_name := pkg.get("packageManager")) and isinstance(
pkg_manager_name, str
):
return pkg_manager_name.lstrip("^").split("@")[0]
if (
dev_engine_name := (
(pkg.get("devEngines") or {}).get("packageManager") or {}
).get("name")
) and isinstance(dev_engine_name, str):
return dev_engine_name
return None
except Exception:
return None
npm, yarn, pnpm, bun = [
test_file("package-lock.json"),
test_file("yarn.lock"),
test_file("pnpm-lock.yaml"),
test_file("bun.lockb"),
]
if yarn:
install_cmd = "yarn install --frozen-lockfile"
elif pnpm:
install_cmd = "pnpm i --frozen-lockfile"
elif npm:
install_cmd = "npm ci"
elif bun:
install_cmd = "bun i"
else:
pkg_manager_name = get_pkg_manager_name()
if pkg_manager_name == "yarn":
install_cmd = "yarn install"
elif pkg_manager_name == "pnpm":
install_cmd = "pnpm i"
elif pkg_manager_name == "bun":
install_cmd = "bun i"
else:
install_cmd = "npm i"
return install_cmd
def python_config_to_docker(
config_path: pathlib.Path, config: Config, base_image: str
) -> tuple[str, dict[str, str]]:
@@ -995,10 +1073,32 @@ ADD {relpath} /deps/{name}
for fullpath, (relpath, name) in local_deps.real_pkgs.items()
)
ui_inst_str: str = ""
install_node_str: str = ""
if config.get("ui") and local_deps.working_dir:
install_node_str = "RUN /storage/install-node.sh"
ui_inst: list[str] = []
ui_inst.append(f"ENV LANGGRAPH_UI='{json.dumps(config['ui'])}'")
if config.get("ui_config"):
ui_inst.append(
f"ENV LANGGRAPH_UI_CONFIG='{json.dumps(config['ui_config'])}'"
)
ui_inst.append(
f"RUN cd {local_deps.working_dir} && {_get_node_pm_install_cmd(config_path, config)} && tsx /api/langgraph_api/js/build.mts",
)
ui_inst_str = f"""# -- Installing UI dependencies --
{os.linesep.join(ui_inst)}
# -- End of UI dependencies install --"""
installs = f"{os.linesep}{os.linesep}".join(
filter(
None,
[
install_node_str,
pip_config_file_str,
pip_pkgs_str,
pip_reqs_str,
@@ -1039,6 +1139,8 @@ ADD {relpath} /deps/{name}
"# -- End of local dependencies install --",
os.linesep.join(env_vars),
"",
ui_inst_str,
"",
f"WORKDIR {local_deps.working_dir}" if local_deps.working_dir else "",
]
@@ -1059,62 +1161,7 @@ def node_config_to_docker(
config_path: pathlib.Path, config: Config, base_image: str
) -> tuple[str, dict[str, str]]:
faux_path = f"/deps/{config_path.parent.name}"
def test_file(file_name):
full_path = config_path.parent / file_name
try:
return full_path.is_file()
except OSError:
return False
# inspired by `package-manager-detector`
def get_pkg_manager_name():
try:
with open(config_path.parent / "package.json") as f:
pkg = json.load(f)
if (pkg_manager_name := pkg.get("packageManager")) and isinstance(
pkg_manager_name, str
):
return pkg_manager_name.lstrip("^").split("@")[0]
if (
dev_engine_name := (
(pkg.get("devEngines") or {}).get("packageManager") or {}
).get("name")
) and isinstance(dev_engine_name, str):
return dev_engine_name
return None
except Exception:
return None
npm, yarn, pnpm, bun = [
test_file("package-lock.json"),
test_file("yarn.lock"),
test_file("pnpm-lock.yaml"),
test_file("bun.lockb"),
]
if yarn:
install_cmd = "yarn install --frozen-lockfile"
elif pnpm:
install_cmd = "pnpm i --frozen-lockfile"
elif npm:
install_cmd = "npm ci"
elif bun:
install_cmd = "bun i"
else:
pkg_manager_name = get_pkg_manager_name()
if pkg_manager_name == "yarn":
install_cmd = "yarn install"
elif pkg_manager_name == "pnpm":
install_cmd = "pnpm i"
elif pkg_manager_name == "bun":
install_cmd = "bun i"
else:
install_cmd = "npm i"
install_cmd = _get_node_pm_install_cmd(config_path, config)
store_config = config.get("store")
env_additional_config = (
""
+1 -1
View File
@@ -1,6 +1,6 @@
[tool.poetry]
name = "langgraph-cli"
version = "0.1.83"
version = "0.1.89"
description = "CLI for interacting with LangGraph API"
authors = []
license = "MIT"
+44
View File
@@ -196,6 +196,50 @@ def test_dockerfile_command_basic() -> None:
assert save_path.exists()
def test_dockerfile_command_new_style_config() -> None:
"""Test `dockerfile` command with a new style config.
This config format allows specifying agent data as a dictionary.
{
"graphs": {
"agent1": {
"path": ... # path to graph definition,
... # other fields
}
}
}
"""
runner = CliRunner()
config_content = {
"dependencies": ["./my_agent"],
"graphs": {
"agent": {
"path": "./my_agent/agent.py:graph",
"description": "This is a test agent",
}
},
"env": ".env",
}
with temporary_config_folder(config_content) as temp_dir:
save_path = temp_dir / "Dockerfile"
# Add agent.py file
agent_path = temp_dir / "my_agent" / "agent.py"
agent_path.parent.mkdir(parents=True, exist_ok=True)
agent_path.touch()
result = runner.invoke(
cli,
["dockerfile", str(save_path), "--config", str(temp_dir / "config.json")],
)
# Assert command was successful
assert result.exit_code == 0, result.output
assert "✅ Created: Dockerfile" in result.output
# Check if Dockerfile was created
assert save_path.exists()
def test_dockerfile_command_with_docker_compose() -> None:
"""Test the 'dockerfile' command with Docker Compose configuration."""
runner = CliRunner()
+43
View File
@@ -494,6 +494,49 @@ RUN (test ! -f /api/langgraph_api/js/build.mts && echo "Prebuild script not foun
assert additional_contexts == {}
def test_config_to_docker_gen_ui_python():
graphs = {"agent": "./agent.py:graph"}
actual_docker_stdin, additional_contexts = config_to_docker(
PATH_TO_CONFIG,
validate_config(
{
"dependencies": ["."],
"graphs": graphs,
"ui": {"agent": "./graphs/agent.ui.jsx"},
"ui_config": {"shared": ["nuqs"]},
}
),
"langchain/langgraph-api",
)
expected_docker_stdin = """FROM langchain/langgraph-api:3.11
RUN /storage/install-node.sh
# -- Adding non-package dependency unit_tests --
ADD . /deps/__outer_unit_tests/unit_tests
RUN set -ex && \\
for line in '[project]' \\
'name = "unit_tests"' \\
'version = "0.1"' \\
'[tool.setuptools.package-data]' \\
'"*" = ["**/*"]'; do \\
echo "$line" >> /deps/__outer_unit_tests/pyproject.toml; \\
done
# -- End of non-package dependency unit_tests --
# -- Installing all local dependencies --
RUN PYTHONDONTWRITEBYTECODE=1 pip install --no-cache-dir -c /api/constraints.txt -e /deps/*
# -- End of local dependencies install --
ENV LANGSERVE_GRAPHS='{"agent": "/deps/__outer_unit_tests/unit_tests/agent.py:graph"}'
# -- Installing UI dependencies --
ENV LANGGRAPH_UI='{"agent": "./graphs/agent.ui.jsx"}'
ENV LANGGRAPH_UI_CONFIG='{"shared": ["nuqs"]}'
RUN cd /deps/__outer_unit_tests/unit_tests && npm i && tsx /api/langgraph_api/js/build.mts
# -- End of UI dependencies install --
WORKDIR /deps/__outer_unit_tests/unit_tests"""
assert clean_empty_lines(actual_docker_stdin) == expected_docker_stdin
assert additional_contexts == {}
# config_to_compose
def test_config_to_compose_simple_config():
graphs = {"agent": "./agent.py:graph"}
+206
View File
@@ -0,0 +1,206 @@
from typing import Any, Literal, Optional, Union
from uuid import uuid4
from langchain_core.messages import AnyMessage
from typing_extensions import TypedDict
from langgraph.constants import CONF, CONFIG_KEY_SEND
from langgraph.utils.config import get_config, get_stream_writer
class UIMessage(TypedDict):
"""A message type for UI updates in LangGraph.
This TypedDict represents a UI message that can be sent to update the UI state.
It contains information about the UI component to render and its properties.
Attributes:
type: Literal type indicating this is a UI message.
id: Unique identifier for the UI message.
name: Name of the UI component to render.
props: Properties to pass to the UI component.
metadata: Additional metadata about the UI message.
"""
type: Literal["ui"]
id: str
name: str
props: dict[str, Any]
metadata: dict[str, Any]
class RemoveUIMessage(TypedDict):
"""A message type for removing UI components in LangGraph.
This TypedDict represents a message that can be sent to remove a UI component
from the current state.
Attributes:
type: Literal type indicating this is a remove-ui message.
id: Unique identifier of the UI message to remove.
"""
type: Literal["remove-ui"]
id: str
AnyUIMessage = Union[UIMessage, RemoveUIMessage]
def push_ui_message(
name: str,
props: dict[str, Any],
*,
id: Optional[str] = None,
metadata: Optional[dict[str, Any]] = None,
message: Optional[AnyMessage] = None,
state_key: str = "ui",
) -> UIMessage:
"""Push a new UI message to update the UI state.
This function creates and sends a UI message that will be rendered in the UI.
It also updates the graph state with the new UI message.
Args:
name: Name of the UI component to render.
props: Properties to pass to the UI component.
id: Optional unique identifier for the UI message.
If not provided, a random UUID will be generated.
metadata: Optional additional metadata about the UI message.
message: Optional message object to associate with the UI message.
state_key: Key in the graph state where the UI messages are stored.
Defaults to "ui".
Returns:
The created UI message.
Example:
.. code-block:: python
push_ui_message(
name="component-name",
props={"content": "Hello world"},
)
"""
writer = get_stream_writer()
config = get_config()
message_id = None
if message:
if isinstance(message, dict) and "id" in message:
message_id = message.get("id")
elif hasattr(message, "id"):
message_id = message.id
evt: UIMessage = {
"type": "ui",
"id": id or str(uuid4()),
"name": name,
"props": props,
"metadata": {
**(config.get("metadata") or {}),
"tags": config.get("tags", None),
"name": config.get("run_name", None),
"run_id": config.get("run_id", None),
**(metadata or {}),
**({"message_id": message_id} if message_id else {}),
},
}
writer(evt)
config[CONF][CONFIG_KEY_SEND]([(state_key, evt)])
return evt
def delete_ui_message(id: str, *, state_key: str = "ui") -> RemoveUIMessage:
"""Delete a UI message by ID from the UI state.
This function creates and sends a message to remove a UI component from the current state.
It also updates the graph state to remove the UI message.
Args:
id: Unique identifier of the UI component to remove.
state_key: Key in the graph state where the UI messages are stored. Defaults to "ui".
Returns:
The remove UI message.
Example:
.. code-block:: python
delete_ui_message("message-123")
"""
writer = get_stream_writer()
config = get_config()
evt: RemoveUIMessage = {"type": "remove-ui", "id": id}
writer(evt)
config[CONF][CONFIG_KEY_SEND]([(state_key, evt)])
return evt
def ui_message_reducer(
left: Union[list[AnyUIMessage], AnyUIMessage],
right: Union[list[AnyUIMessage], AnyUIMessage],
) -> list[AnyUIMessage]:
"""Merge two lists of UI messages, supporting removing UI messages.
This function combines two lists of UI messages, handling both regular UI messages
and `remove-ui` messages. When a `remove-ui` message is encountered, it removes any
UI message with the matching ID from the current state.
Args:
left: First list of UI messages or single UI message.
right: Second list of UI messages or single UI message.
Returns:
Combined list of UI messages with removals applied.
Example:
.. code-block:: python
messages = ui_message_reducer(
[{"type": "ui", "id": "1", "name": "Chat", "props": {}}],
{"type": "remove-ui", "id": "1"}
)
"""
if not isinstance(left, list):
left = [left]
if not isinstance(right, list):
right = [right]
# merge messages
merged = left.copy()
merged_by_id = {m.get("id"): i for i, m in enumerate(merged)}
ids_to_remove = set()
for msg in right:
msg_id = msg.get("id")
if (existing_idx := merged_by_id.get(msg_id)) is not None:
if msg.get("type") == "remove-ui":
ids_to_remove.add(msg_id)
else:
ids_to_remove.discard(msg_id)
merged[existing_idx] = msg
else:
if msg.get("type") == "remove-ui":
raise ValueError(
f"Attempting to delete an UI message with an ID that doesn't exist ('{msg_id}')"
)
merged_by_id[msg_id] = len(merged)
merged.append(msg)
merged = [m for m in merged if m.get("id") not in ids_to_remove]
return merged
+4 -4
View File
@@ -1,4 +1,4 @@
# This file is automatically @generated by Poetry 2.0.1 and should not be changed by hand.
# This file is automatically @generated by Poetry 2.0.0 and should not be changed by hand.
[[package]]
name = "aiosqlite"
@@ -946,14 +946,14 @@ testing = ["Django", "attrs", "colorama", "docopt", "pytest (<7.0.0)"]
[[package]]
name = "jinja2"
version = "3.1.5"
version = "3.1.6"
description = "A very fast and expressive template engine."
optional = false
python-versions = ">=3.7"
groups = ["dev"]
files = [
{file = "jinja2-3.1.5-py3-none-any.whl", hash = "sha256:aba0f4dc9ed8013c424088f68a5c226f7d6097ed89b246d7749c2ec4175c6adb"},
{file = "jinja2-3.1.5.tar.gz", hash = "sha256:8fefff8dc3034e27bb80d67c671eb8a9bc424c0ef4c0826edbff304cceff43bb"},
{file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"},
{file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"},
]
[package.dependencies]
+1 -1
View File
@@ -1,6 +1,6 @@
[tool.poetry]
name = "langgraph"
version = "0.3.24"
version = "0.3.25"
description = "Building stateful, multi-actor applications with LLMs"
authors = []
license = "MIT"
+1 -1
View File
@@ -1,6 +1,6 @@
[tool.poetry]
name = "langgraph-prebuilt"
version = "0.1.7"
version = "0.1.8"
description = "Library with high-level APIs for creating and executing LangGraph agents and tools."
authors = []
license = "MIT"
+1 -1
View File
@@ -19,7 +19,7 @@ export type StreamEvent =
export interface Send {
node: string;
input: Record<string, unknown> | null;
input: unknown | null;
}
export interface Command {
+7
View File
@@ -162,6 +162,13 @@ def get_client(
transport = httpx.ASGITransport(app, root_path="/noauth")
except Exception:
url = "http://localhost:8123"
try:
from langgraph_api.config import HTTP_CONFIG
if prefix := HTTP_CONFIG.get("mount_prefix"):
url = f"{url}{prefix}"
except ImportError:
pass
if transport is None:
transport = httpx.AsyncHTTPTransport(retries=5)
+1 -1
View File
@@ -1,6 +1,6 @@
[tool.poetry]
name = "langgraph-sdk"
version = "0.1.61"
version = "0.1.62"
description = "SDK for interacting with LangGraph API"
authors = []
license = "MIT"