连接 LangGraph 平台部署¶
创建部署¶
我们刚刚为模块5中的 task_maistro
应用创建了一个部署。
- 我们使用 LangGraph CLI 为包含
task_maistro
图的 LangGraph 服务器构建了 Docker 镜像 - 我们使用提供的
docker-compose.yml
文件创建了三个独立的容器,基于定义的服务:langgraph-redis
:使用官方 Redis 镜像创建新容器langgraph-postgres
:使用官方 Postgres 镜像创建新容器langgraph-api
:使用我们预构建的task_maistro
Docker 镜像创建新容器
$ cd module-6/deployment
$ docker compose up
运行后,我们可以通过以下方式访问部署:
- API:http://localhost:8123
- 文档:http://localhost:8123/docs
- LangGraph Studio:https://smith.langchain.com/studio/?baseUrl=http://127.0.0.1:8123
使用 API¶
LangGraph 服务器暴露了多个 API 端点用于与部署的智能体交互。
我们可以将这些端点归类为几个常见的智能体需求:
- 运行:原子级的智能体执行
- 线程:多轮交互或人工介入
- 存储:长期记忆
我们可以直接在API 文档中测试请求。
SDK¶
LangGraph SDK(包含 Python 和 JS 版本)为开发者提供了友好的接口,用于与上文介绍的 LangGraph 服务端 API 进行交互。
%%capture --no-stderr
%pip install -U langgraph_sdk
from langgraph_sdk import get_client
# Connect via SDK
url_for_cli_deployment = "http://localhost:8123"
client = get_client(url=url_for_cli_deployment)
%%capture --no-stderr
%pip install -U langchain_openai langgraph langchain_core
from langgraph.pregel.remote import RemoteGraph
from langchain_core.messages import convert_to_messages
from langchain_core.messages import HumanMessage, SystemMessage
# Connect via remote graph
url = "http://localhost:8123"
graph_name = "task_maistro"
remote_graph = RemoteGraph(graph_name, url=url)
# Create a thread
thread = await client.threads.create()
thread
{'thread_id': '7f71c0dd-768b-4e53-8349-42bdd10e7caf', 'created_at': '2024-11-14T19:36:08.459457+00:00', 'updated_at': '2024-11-14T19:36:08.459457+00:00', 'metadata': {}, 'status': 'idle', 'config': {}, 'values': None}
# Check any existing runs on a thread
thread = await client.threads.create()
runs = await client.runs.list(thread["thread_id"])
print(runs)
[]
# Ensure we've created some ToDos and saved them to my user_id
user_input = "Add a ToDo to finish booking travel to Hong Kong by end of next week. Also, add a ToDo to call parents back about Thanksgiving plans."
config = {"configurable": {"user_id": "Test"}}
graph_name = "task_maistro"
run = await client.runs.create(thread["thread_id"], graph_name, input={"messages": [HumanMessage(content=user_input)]}, config=config)
# Kick off a new thread and a new run
thread = await client.threads.create()
user_input = "Give me a summary of all ToDos."
config = {"configurable": {"user_id": "Test"}}
graph_name = "task_maistro"
run = await client.runs.create(thread["thread_id"], graph_name, input={"messages": [HumanMessage(content=user_input)]}, config=config)
# Check the run status
print(await client.runs.get(thread["thread_id"], run["run_id"]))
{'run_id': '1efa2c00-63e4-6f4a-9c5b-ca3f5f9bff07', 'thread_id': '641c195a-9e31-4250-a729-6b742c089df8', 'assistant_id': 'ea4ebafa-a81d-5063-a5fa-67c755d98a21', 'created_at': '2024-11-14T19:38:29.394777+00:00', 'updated_at': '2024-11-14T19:38:29.394777+00:00', 'metadata': {}, 'status': 'pending', 'kwargs': {'input': {'messages': [{'id': None, 'name': None, 'type': 'human', 'content': 'Give me a summary of all ToDos.', 'example': False, 'additional_kwargs': {}, 'response_metadata': {}}]}, 'config': {'metadata': {'created_by': 'system'}, 'configurable': {'run_id': '1efa2c00-63e4-6f4a-9c5b-ca3f5f9bff07', 'user_id': 'Test', 'graph_id': 'task_maistro', 'thread_id': '641c195a-9e31-4250-a729-6b742c089df8', 'assistant_id': 'ea4ebafa-a81d-5063-a5fa-67c755d98a21'}}, 'webhook': None, 'subgraphs': False, 'temporary': False, 'stream_mode': ['values'], 'feedback_keys': None, 'interrupt_after': None, 'interrupt_before': None}, 'multitask_strategy': 'reject'}
我们可以看到它显示 'status': 'pending'
,因为运行仍在进行中。
如果我们想等待运行完成,使其成为阻塞式运行该怎么办?
可以使用 client.runs.join
来等待运行完成。
这样可以确保在当前线程上的运行完成之前,不会启动新的运行。
# Wait until the run completes
await client.runs.join(thread["thread_id"], run["run_id"])
print(await client.runs.get(thread["thread_id"], run["run_id"]))
{'run_id': '1efa2c00-63e4-6f4a-9c5b-ca3f5f9bff07', 'thread_id': '641c195a-9e31-4250-a729-6b742c089df8', 'assistant_id': 'ea4ebafa-a81d-5063-a5fa-67c755d98a21', 'created_at': '2024-11-14T19:38:29.394777+00:00', 'updated_at': '2024-11-14T19:38:29.394777+00:00', 'metadata': {}, 'status': 'success', 'kwargs': {'input': {'messages': [{'id': None, 'name': None, 'type': 'human', 'content': 'Give me a summary of all ToDos.', 'example': False, 'additional_kwargs': {}, 'response_metadata': {}}]}, 'config': {'metadata': {'created_by': 'system'}, 'configurable': {'run_id': '1efa2c00-63e4-6f4a-9c5b-ca3f5f9bff07', 'user_id': 'Test', 'graph_id': 'task_maistro', 'thread_id': '641c195a-9e31-4250-a729-6b742c089df8', 'assistant_id': 'ea4ebafa-a81d-5063-a5fa-67c755d98a21'}}, 'webhook': None, 'subgraphs': False, 'temporary': False, 'stream_mode': ['values'], 'feedback_keys': None, 'interrupt_after': None, 'interrupt_before': None}, 'multitask_strategy': 'reject'}
现在运行状态显示为 'status': 'success'
,表示已完成。
user_input = "What ToDo should I focus on first."
async for chunk in client.runs.stream(thread["thread_id"],
graph_name,
input={"messages": [HumanMessage(content=user_input)]},
config=config,
stream_mode="messages-tuple"):
if chunk.event == "messages":
print("".join(data_item['content'] for data_item in chunk.data if 'content' in data_item), end="", flush=True)
You might want to focus on "Call parents back about Thanksgiving plans" first. It has a shorter estimated time to complete (15 minutes) and doesn't have a specific deadline, so it could be a quick task to check off your list. Once that's done, you can dedicate more time to "Finish booking travel to Hong Kong," which is more time-consuming and has a deadline.
线程(Threads)¶
虽然单次运行(run)仅代表图(graph)的一次执行,但线程支持多轮交互。
当客户端使用 thread_id
执行图时,服务器会将运行中的所有检查点(步骤)保存到 Postgres 数据库的对应线程中。
服务器允许我们查看已创建线程的状态。
检查线程状态¶
此外,我们可以轻松访问保存到特定线程的所有状态检查点。
thread_state = await client.threads.get_state(thread['thread_id'])
for m in convert_to_messages(thread_state['values']['messages']):
m.pretty_print()
================================ Human Message ================================= Give me a summary of all ToDos. ================================== Ai Message ================================== Here's a summary of your current ToDo list: 1. **Task:** Finish booking travel to Hong Kong - **Status:** Not started - **Deadline:** November 22, 2024 - **Solutions:** - Check flight prices on Skyscanner - Book hotel through Booking.com - Arrange airport transfer - **Estimated Time to Complete:** 120 minutes 2. **Task:** Call parents back about Thanksgiving plans - **Status:** Not started - **Deadline:** None - **Solutions:** - Check calendar for availability - Discuss travel arrangements - Confirm dinner plans - **Estimated Time to Complete:** 15 minutes Let me know if there's anything else you'd like to do with your ToDo list! ================================ Human Message ================================= What ToDo should I focus on first. ================================== Ai Message ================================== You might want to focus on "Call parents back about Thanksgiving plans" first. It has a shorter estimated time to complete (15 minutes) and doesn't have a specific deadline, so it could be a quick task to check off your list. Once that's done, you can dedicate more time to "Finish booking travel to Hong Kong," which is more time-consuming and has a deadline.
# Copy the thread
copied_thread = await client.threads.copy(thread['thread_id'])
# Check the state of the copied thread
copied_thread_state = await client.threads.get_state(copied_thread['thread_id'])
for m in convert_to_messages(copied_thread_state['values']['messages']):
m.pretty_print()
================================ Human Message ================================= Give me a summary of all ToDos. ================================== Ai Message ================================== Here's a summary of your current ToDo list: 1. **Task:** Finish booking travel to Hong Kong - **Status:** Not started - **Deadline:** November 22, 2024 - **Solutions:** - Check flight prices on Skyscanner - Book hotel through Booking.com - Arrange airport transfer - **Estimated Time to Complete:** 120 minutes 2. **Task:** Call parents back about Thanksgiving plans - **Status:** Not started - **Deadline:** None - **Solutions:** - Check calendar for availability - Discuss travel arrangements - Confirm dinner plans - **Estimated Time to Complete:** 15 minutes Let me know if there's anything else you'd like to do with your ToDo list! ================================ Human Message ================================= What ToDo should I focus on first. ================================== Ai Message ================================== You might want to focus on "Call parents back about Thanksgiving plans" first. It has a shorter estimated time to complete (15 minutes) and doesn't have a specific deadline, so it could be a quick task to check off your list. Once that's done, you can dedicate more time to "Finish booking travel to Hong Kong," which is more time-consuming and has a deadline.
# Get the history of the thread
states = await client.threads.get_history(thread['thread_id'])
# Pick a state update to fork
to_fork = states[-2]
to_fork['values']
{'messages': [{'content': 'Give me a summary of all ToDos.', 'additional_kwargs': {'example': False, 'additional_kwargs': {}, 'response_metadata': {}}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '3680da45-e3a5-4a47-b5b1-4fd4d3e8baf9', 'example': False}]}
to_fork['values']['messages'][0]['id']
'3680da45-e3a5-4a47-b5b1-4fd4d3e8baf9'
to_fork['next']
['task_mAIstro']
to_fork['checkpoint_id']
'1efa2c00-6609-67ff-8000-491b1dcf8129'
让我们来编辑状态。还记得 messages
的 reducer 是如何工作的吗:
- 默认会追加消息,除非我们提供了消息 ID
- 当我们提供消息 ID 时,会覆盖原有消息而非追加到状态中!
forked_input = {"messages": HumanMessage(content="Give me a summary of all ToDos that need to be done in the next week.",
id=to_fork['values']['messages'][0]['id'])}
# Update the state, creating a new checkpoint in the thread
forked_config = await client.threads.update_state(
thread["thread_id"],
forked_input,
checkpoint_id=to_fork['checkpoint_id']
)
# Run the graph from the new checkpoint in the thread
async for chunk in client.runs.stream(thread["thread_id"],
graph_name,
input=None,
config=config,
checkpoint_id=forked_config['checkpoint_id'],
stream_mode="messages-tuple"):
if chunk.event == "messages":
print("".join(data_item['content'] for data_item in chunk.data if 'content' in data_item), end="", flush=True)
Here's a summary of your ToDos that need to be done in the next week: 1. **Finish booking travel to Hong Kong** - **Status:** Not started - **Deadline:** November 22, 2024 - **Solutions:** - Check flight prices on Skyscanner - Book hotel through Booking.com - Arrange airport transfer - **Estimated Time to Complete:** 120 minutes It looks like this task is due soon, so you might want to prioritize it. Let me know if there's anything else you need help with!
跨线程内存¶
在模块5中,我们介绍了如何使用 LangGraph 内存存储 来保存跨线程的信息。
我们部署的图 task_maistro
使用 store
来保存信息(例如待办事项),这些信息通过 user_id
进行命名空间隔离。
我们的部署包含一个 PostgreSQL 数据库,用于存储这些长期(跨线程)记忆。
通过 LangGraph SDK,我们提供了多种与存储交互的方法。
搜索条目¶
task_maistro
图默认使用 store
保存按 (todo
, todo_category
, user_id
) 命名空间隔离的待办事项。
默认情况下,todo_category
设置为 general
(如你在 deployment/configuration.py
中所见)。
我们只需提供这个元组即可搜索所有待办事项。
items = await client.store.search_items(
("todo", "general", "Test"),
limit=5,
offset=0
)
items['items']
[{'value': {'task': 'Finish booking travel to Hong Kong', 'status': 'not started', 'deadline': '2024-11-22T23:59:59', 'solutions': ['Check flight prices on Skyscanner', 'Book hotel through Booking.com', 'Arrange airport transfer'], 'time_to_complete': 120}, 'key': '18524803-c182-49de-9b10-08ccb0a06843', 'namespace': ['todo', 'general', 'Test'], 'created_at': '2024-11-14T19:37:41.664827+00:00', 'updated_at': '2024-11-14T19:37:41.664827+00:00'}, {'value': {'task': 'Call parents back about Thanksgiving plans', 'status': 'not started', 'deadline': None, 'solutions': ['Check calendar for availability', 'Discuss travel arrangements', 'Confirm dinner plans'], 'time_to_complete': 15}, 'key': '375d9596-edf8-4de2-985b-bacdc623d6ef', 'namespace': ['todo', 'general', 'Test'], 'created_at': '2024-11-14T19:37:41.664827+00:00', 'updated_at': '2024-11-14T19:37:41.664827+00:00'}]
from uuid import uuid4
await client.store.put_item(
("testing", "Test"),
key=str(uuid4()),
value={"todo": "Test SDK put_item"},
)
items = await client.store.search_items(
("testing", "Test"),
limit=5,
offset=0
)
items['items']
[{'value': {'todo': 'Test SDK put_item'}, 'key': '3de441ba-8c79-4beb-8f52-00e4dcba68d4', 'namespace': ['testing', 'Test'], 'created_at': '2024-11-14T19:56:30.452808+00:00', 'updated_at': '2024-11-14T19:56:30.452808+00:00'}, {'value': {'todo': 'Test SDK put_item'}, 'key': '09b9a869-4406-47c5-a635-4716bd79a8b3', 'namespace': ['testing', 'Test'], 'created_at': '2024-11-14T19:53:24.812558+00:00', 'updated_at': '2024-11-14T19:53:24.812558+00:00'}]
[item['key'] for item in items['items']]
['3de441ba-8c79-4beb-8f52-00e4dcba68d4', '09b9a869-4406-47c5-a635-4716bd79a8b3']
await client.store.delete_item(
("testing", "Test"),
key='3de441ba-8c79-4beb-8f52-00e4dcba68d4',
)
items = await client.store.search_items(
("testing", "Test"),
limit=5,
offset=0
)
items['items']
[{'value': {'todo': 'Test SDK put_item'}, 'key': '09b9a869-4406-47c5-a635-4716bd79a8b3', 'namespace': ['testing', 'Test'], 'created_at': '2024-11-14T19:53:24.812558+00:00', 'updated_at': '2024-11-14T19:53:24.812558+00:00'}]
# Getting Started with React
## Installation
To install React, run the following command in your terminal:
```bash
npm install react react-dom
Creating a New Project¶
The easiest way to create a new React project is by using Create React App:
npx create-react-app my-app
cd my-app
npm start
Project Structure¶
A newly created React project has the following structure:
my-app/
├── node_modules/
├── public/
│ ├── favicon.ico
│ ├── index.html
│ └── ...
├── src/
│ ├── App.css
│ ├── App.js
│ ├── index.js
│ └── ...
├── package.json
└── README.md
Basic Concepts¶
React applications are built using components. Here's a simple example:
function Welcome() {
return <h1>Hello, World!</h1>;
}
Key Features¶
- Declarative: Makes your code more predictable
- Component-Based: Build encapsulated components
- Learn Once, Write Anywhere: Works with other libraries
```markdown
# React 入门指南
## 安装
要安装 React,请在终端中运行以下命令:
```bash
npm install react react-dom
创建新项目¶
使用 Create React App 是创建新 React 项目最简单的方式:
npx create-react-app my-app
cd my-app
npm start
项目结构¶
新创建的 React 项目具有以下目录结构:
my-app/
├── node_modules/
├── public/
│ ├── favicon.ico
│ ├── index.html
│ └── ...
├── src/
│ ├── App.css
│ ├── App.js
│ ├── index.js
│ └── ...
├── package.json
└── README.md
基本概念¶
React 应用通过组件构建。以下是一个简单示例:
function Welcome() {
return <h1>Hello, World!</h1>;
}
核心特性¶
- 声明式:使代码更具可预测性
- 组件化:构建封装的组件
- 一次学习,随处编写:可与其他库协同工作