Skip to content

Commit 3141d4b

Browse files
added session1 (#71)
2 parents 7a9a2ff + e3b823b commit 3141d4b

File tree

21 files changed

+880
-0
lines changed

21 files changed

+880
-0
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
OPENAI_API_KEY=# <--- Set OpenAI api key here
2+
OPENAI_API_BASE=https://ark.cn-beijing.volces.com/api/v3
3+
MODEL_NAME=deepseek-v3-1-terminus
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
# Session 1 - 标准 LangChain Agent 的 AgentKit 化改造
2+
3+
## 概述
4+
5+
本 Workshop 旨在演示如何将一个基于 LangChain 构建的标准 Agent,通过 AgentKit SDK Python 进行轻量级改造,使其能够一键部署到火山引擎 AgentKit 平台上。
6+
7+
### Agent 改造指南
8+
9+
我们将对比 `langchain_agent.py` (原生实现) 和 `agent.py` (AgentKit 适配版),改造过程仅需以下 **3 点** 极小改动:
10+
11+
1. **引入 SDK 并初始化应用**
12+
13+
```python
14+
# 引入 AgentKit SDK
15+
from agentkit.apps import AgentkitSimpleApp
16+
17+
# 初始化应用实例
18+
app = AgentkitSimpleApp()
19+
```
20+
21+
2. **标记入口函数**
22+
使用 `@app.entrypoint` 装饰器标记您的主逻辑函数。
23+
24+
```python
25+
@app.entrypoint
26+
async def run(payload: dict, headers: dict):
27+
# 您的业务逻辑...
28+
```
29+
30+
3. **按照标准协议返回**
31+
将原本直接打印到控制台的输出,改为 `yield` 返回标准 JSON 格式的 Event 数据。
32+
33+
```python
34+
# 原生 LangChain: print(chunk)
35+
# AgentKit 适配:
36+
yield json.dumps(event_data)
37+
```
38+
39+
这些改动是非侵入式的,您原有的 Chain 定义、Tool 定义和 Prompt 逻辑完全不需要修改。
40+
41+
## 核心功能
42+
43+
1. **构建 LangChain Agent**:使用 LangChain 1.0 标准范式构建具备工具调用能力的 ReAct Agent。
44+
2. **AgentKit 快速适配**:通过 SDK 将本地 Agent 转换为生产级微服务,无需修改核心 Chain 逻辑。
45+
3. **云端一键部署**:利用 AgentKit CLI 实现代码打包、镜像构建及环境变量的自动同步。
46+
47+
## Agent 能力
48+
49+
本 Agent 具备以下基础能力:
50+
51+
- **自动化推理**:基于 ReAct 范式,自动分析用户问题并规划工具调用顺序。
52+
- **工具调用**
53+
- `get_word_length`: 计算单词长度。
54+
- `add_numbers`: 执行数值加法运算。
55+
- **流式响应**:支持 SSE 标准协议,实时输出思考过程和最终结果。
56+
57+
## 目录结构说明
58+
59+
```bash
60+
session1/
61+
├── agent.py # 适配后的 AgentKit 应用 (核心文件)
62+
├── langchain_agent.py # 适配前的原生 LangChain 脚本 (对比参考)
63+
├── local_client.py # 本地流式调用测试客户端
64+
├── agentkit.yaml # 部署配置文件
65+
├── .env # 环境变量配置文件 (部署时自动同步)
66+
└── README.md # 说明文档
67+
```
68+
69+
## 本地运行
70+
71+
### 前置准备
72+
73+
1. **依赖安装**
74+
75+
```bash
76+
uv sync
77+
source .venv/bin/activate
78+
```
79+
80+
2. **配置环境变量**
81+
82+
```bash
83+
cp .env.sample .env
84+
# 编辑 .env 文件,填入 OPENAI_API_KEY 等必填项
85+
86+
# 火山引擎访问凭证(必需)
87+
export VOLCENGINE_ACCESS_KEY=<Your Access Key>
88+
export VOLCENGINE_SECRET_KEY=<Your Secret Key>
89+
```
90+
91+
### 调试方法
92+
93+
**方式一:运行原生脚本** (验证 Agent 逻辑)
94+
95+
```bash
96+
uv run langchain_agent.py
97+
```
98+
99+
**方式二:运行 AgentKit 服务** (模拟生产环境)
100+
101+
```bash
102+
# 启动服务 (监听 8000 端口)
103+
uv run agent.py
104+
105+
# 在新终端运行客户端测试
106+
uv run client.py
107+
```
108+
109+
## AgentKit 部署
110+
111+
部署过程完全自动化,支持 `.env` 环境变量自动同步。
112+
113+
### 1. 初始化配置
114+
115+
```bash
116+
agentkit config
117+
```
118+
119+
此命令会引导您选择项目空间和镜像仓库等信息,生成 `agentkit.yaml`
120+
121+
### 2. 部署上线
122+
123+
```bash
124+
agentkit launch
125+
```
126+
127+
> **重要**`agentkit launch` 命令会自动读取您本地项目根目录下的 `.env` 文件,并将其中的所有环境变量自动注入到云端 Runtime 环境中。这意味着您**无需**在控制台手动配置 `OPENAI_API_KEY``MODEL_NAME` 等敏感信息,CLI 帮您完成了一切环境同步工作,确保云端运行环境与本地完全一致。
128+
129+
### 3. 在线测试
130+
131+
部署完成后,您可以使用 CLI 直接调用云端 Agent:
132+
133+
```bash
134+
# <URL> 是 launch 命令输出的服务访问地址
135+
agentkit invoke 'Hello, can you calculate 10 + 20 and tell me the length of the word "AgentKit"?'
136+
```
137+
138+
## 示例提示词
139+
140+
- "What is the length of the word 'Volcengine'?"
141+
- "Calculate 123 + 456."
142+
- "Hello, can you calculate 10 + 20 and tell me the length of the word 'AgentKit'?"
143+
- "Tell me a fun fact about Python." (此Agent不具备通用知识,会尝试使用工具或拒绝回答)
144+
145+
## 效果展示
146+
147+
- **本地脚本**:终端直接输出 ReAct 思考链,展示 Agent 的推理过程和最终结果。
148+
- **HTTP 服务**:客户端接收 SSE 流式事件,包含详细的 `on_llm_chunk` (LLM思考过程)、`on_tool_start` (工具调用开始)、`on_tool_end` (工具调用结束) 等状态信息,提供丰富的交互体验。
149+
150+
## 常见问题
151+
152+
- **Q: 为什么提示 API Key 无效?**
153+
- A: 请确保 `.env` 文件中的 `OPENAI_API_KEY` 或其他模型服务商的 API Key 配置正确,且 `OPENAI_API_BASE` 与您使用的服务商(如火山方舟、OpenAI)匹配。
154+
155+
- **Q: 部署时环境变量未生效?**
156+
- A: 请确认 `.env` 文件位于运行 `agentkit launch` 命令的当前项目根目录下。CLI 会自动查找并同步该文件。
157+
158+
- **Q: Agent 无法回答通用知识问题?**
159+
- A: 本示例 Agent 主要演示工具调用能力,未集成通用知识库。如需回答通用知识,请扩展 Agent 的工具集或连接到知识库。
160+
161+
## 代码许可
162+
163+
本工程遵循 Apache 2.0 License。
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import os
2+
from langchain_openai import ChatOpenAI
3+
from langchain.agents import create_agent
4+
from langchain_core.tools import tool
5+
from dotenv import load_dotenv
6+
from agentkit.apps import AgentkitSimpleApp
7+
import logging
8+
import json
9+
10+
11+
# Load environment variables (especially OPENAI_API_KEY)
12+
load_dotenv()
13+
logger = logging.getLogger(__name__)
14+
logger.setLevel(logging.INFO)
15+
16+
17+
# 1. Define tools
18+
@tool
19+
def get_word_length(word: str) -> int:
20+
"""Returns the length of a word."""
21+
return len(word)
22+
23+
24+
@tool
25+
def add_numbers(a: int, b: int) -> int:
26+
"""Adds two numbers together."""
27+
return a + b
28+
29+
30+
# Create the agent
31+
# Fix: Ensure environment variables are mapped to the correct arguments
32+
agent = create_agent(
33+
model=ChatOpenAI(
34+
model=os.getenv("MODEL_NAME", "gpt-4o-mini"),
35+
api_key=os.getenv("OPENAI_API_KEY"),
36+
base_url=os.getenv("OPENAI_API_BASE", "https://api.openai.com/v1"),
37+
temperature=0,
38+
),
39+
tools=[get_word_length, add_numbers],
40+
system_prompt="You are a helpful assistant. You have access to tools to help answer questions.",
41+
)
42+
43+
44+
app = AgentkitSimpleApp()
45+
46+
47+
@app.entrypoint
48+
async def run(payload: dict, headers: dict):
49+
prompt = payload.get("prompt")
50+
user_id = headers.get("user_id")
51+
session_id = headers.get("session_id")
52+
53+
# Default values if still missing
54+
user_id = user_id or "default_user"
55+
session_id = session_id or "default_session"
56+
57+
logger.info(
58+
f"Running agent with prompt: {prompt}, user_id: {user_id}, session_id: {session_id}"
59+
)
60+
61+
inputs = {"messages": [{"role": "user", "content": prompt}]}
62+
63+
# stream returns an iterator of updates
64+
# To get the final result, we can just iterate or use invoke
65+
async for chunk in agent.astream(inputs, stream_mode="updates"):
66+
# chunk is a dict with node names as keys and state updates as values
67+
for node, state in chunk.items():
68+
logger.debug(f"--- Node: {node} ---")
69+
70+
if "messages" in state:
71+
last_msg = state["messages"][-1]
72+
73+
for block in last_msg.content_blocks:
74+
# 返回的event_data数据结构要求符合 adk event规范: https://google.github.io/adk-docs/events/#identifying-event-origin-and-type
75+
event_data = {
76+
"content": {
77+
"parts": [
78+
{"text": block.get("text") or block.get("reasoning")}
79+
]
80+
}
81+
}
82+
yield json.dumps(event_data)
83+
84+
85+
@app.ping
86+
def ping() -> str:
87+
return "pong!"
88+
89+
90+
async def local_test():
91+
"""Helper to run the agent locally without server"""
92+
print("Running local test...")
93+
query = "What is the length of the word 'LangChain' and what is that length plus 5?"
94+
print(f"Query: {query}")
95+
async for event in run({"prompt": query}, {"user_id": "1", "session_id": "1"}):
96+
print(f"Received event: {event}")
97+
98+
99+
if __name__ == "__main__":
100+
app.run(host="0.0.0.0", port=8000)
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import requests
2+
import json
3+
4+
5+
def main():
6+
# Target URL
7+
url = "http://localhost:8000/invoke"
8+
9+
# Payload parameters
10+
payload = {
11+
"prompt": "Hello, can you calculate 10 + 20 and tell me the length of the word 'AgentKit'?"
12+
}
13+
14+
# Headers
15+
headers = {"user_id": "test_user_001", "session_id": "session_test_001"}
16+
17+
print(f"Sending POST request to {url} with payload: {payload}")
18+
19+
try:
20+
response = requests.post(url, json=payload, headers=headers, stream=True)
21+
22+
if response.status_code == 200:
23+
print("\n--- Streaming Response ---")
24+
for line in response.iter_lines():
25+
if line:
26+
decoded_line = line.decode("utf-8")
27+
if decoded_line.startswith("data: "):
28+
decoded_line = decoded_line[6:]
29+
30+
if decoded_line.strip() == "[DONE]":
31+
continue
32+
33+
# Expecting SSE or JSON lines depending on implementation
34+
# The agent code yields json.dumps(event_data)
35+
try:
36+
data = json.loads(decoded_line)
37+
print(json.dumps(data, indent=2, ensure_ascii=False))
38+
except json.JSONDecodeError:
39+
print(f"Raw: {decoded_line}")
40+
print("\n--- End of Stream ---")
41+
else:
42+
print(f"Request failed with status code: {response.status_code}")
43+
print(response.text)
44+
45+
except requests.exceptions.ConnectionError:
46+
print(
47+
"Error: Could not connect to the server. Is the agent running on port 8000?"
48+
)
49+
except Exception as e:
50+
print(f"An error occurred: {e}")
51+
52+
53+
if __name__ == "__main__":
54+
main()

0 commit comments

Comments
 (0)