zhouhui.jiang

初始化

No preview for this file type
# 默认忽略的文件
/shelf/
/workspace.xml
# 基于编辑器的 HTTP 客户端请求
/httpRequests/
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/venv" />
</content>
<orderEntry type="jdk" jdkName="Python 3.12 (Test_LangGraph)" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AI Toolkit Settings">
<option name="importsOfInterestPresent" value="true" />
</component>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding">
<file url="file://$PROJECT_DIR$/langgraph.json" charset="UTF-8" />
<file url="mock:///Python 控制台.py" charset="UTF-8" />
</component>
</project>
\ No newline at end of file
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
</profile>
</component>
\ No newline at end of file
<component name="InspectionProjectProfileManager">
<settings>
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Black">
<option name="sdkName" value="Python 3.12 (Test_LangGraph)" />
</component>
<component name="JavaScriptSettings">
<option name="languageLevel" value="ES6" />
</component>
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.12 (Test_LangGraph)" project-jdk-type="Python SDK" />
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/Test_LangGraph.iml" filepath="$PROJECT_DIR$/.idea/Test_LangGraph.iml" />
</modules>
</component>
</project>
\ No newline at end of file
No preview for this file type
No preview for this file type
No preview for this file type
No preview for this file type
No preview for this file type
No preview for this file type
No preview for this file type
#!/usr/bin/env python3
"""
API 包初始化文件
"""
from .api_config import API_CONFIG
from .waybill_api import query_waybill_list, generate_waybill_table, create_waybill_d
from .paperless_api import upload_clearance_file
__all__ = ['API_CONFIG', 'query_waybill_list', 'generate_waybill_table', 'create_waybill_d', 'upload_clearance_file']
No preview for this file type
No preview for this file type
No preview for this file type
No preview for this file type
#!/usr/bin/env python3
"""
API 配置文件
包含所有 API 相关的配置信息
"""
import os
# API 配置
API_CONFIG = {
"base_url": "http://192.168.1.251:7022",
"endpoint": "/Exp/bus-customer/vueConsignmentQuery/findChmConsignmentConditionalQuery",
"headers": {
"accept": "*/*",
"Content-Type": "application/json",
"Authorization": os.getenv("API_AUTHORIZATION", "Bearer 2.3e67bc239d7144bd9885cee8ffcbc8be"),
"Ver": os.getenv("API_VER", "033BD94B1168D7E4F0D644C3C95E35BF.D73E33B659AD1D6B7D181D1DF8D05760"),
"Referer": os.getenv("API_REFERER", "http://192.168.1.251/")
}
}
#!/usr/bin/env python3
"""
清关文件上传 API
按照 form-data 方式提交两个字段:
- params: JSON 字符串(包含 code、slipId、uid 等)
- fileUpload: PDF 文件
"""
import os
import time
import json
import requests
from typing import Optional
from .api_config import API_CONFIG
def upload_clearance_file(
code: str,
slip_id: int,
pdf_path: str,
file_index: int = 0,
uid: Optional[int] = None,
) -> str:
"""根据运单ID上传清关文件(PDF)。
Args:
code: 运单编号(必填)
slip_id: 创建运单返回的ID(必填)
pdf_path: 本地 PDF 文件路径(必填)
file_index: 文件索引,默认 0
uid: 文件 uid,可不传,默认使用时间戳生成
Returns:
文本:成功/失败信息。
"""
if not code:
return "参数错误: code 不能为空"
if not slip_id and slip_id != 0:
return "参数错误: slip_id 不能为空"
if not os.path.isfile(pdf_path):
return f"参数错误: 文件不存在 - {pdf_path}"
url = f"{API_CONFIG['base_url']}/Exp/manager-server/attachmentNew/upload/paperless/clean"
# 生成 uid
real_uid = uid if isinstance(uid, int) else int(time.time() * 1000)
params_payload = {
"uploadType": "other",
"code": code,
"fileIndex": file_index,
"fileType": "picType_1",
"inputType": 0,
"slipId": slip_id,
"slipType": "slipType_chm_pdf_od",
"fileUpload": [{"uid": real_uid}],
"fileSizeTotal": 18425,
"splitSuccess": 0,
"needBackSplit": 0,
"splitPicTotal": 0,
"item": [],
}
# form-data: params 是 JSON 字符串,fileUpload 是文件
data = {
"params": json.dumps(params_payload, ensure_ascii=False),
}
files = {
"fileUpload": (os.path.basename(pdf_path), open(pdf_path, "rb"), "application/pdf"),
}
# 复制 headers,并移除 Content-Type(由 requests 根据 multipart 自动设置)
headers = dict(API_CONFIG.get("headers", {}))
headers.pop("Content-Type", None)
try:
resp = requests.post(url, headers=headers, data=data, files=files, timeout=60)
# 确保文件句柄尽快关闭
try:
files["fileUpload"][1].close()
except Exception:
pass
if resp.status_code == 200:
return (
f"上传清关文件成功\n"
f"运单号:{code}\n"
f"slipId:{slip_id}\n"
f"uid:{real_uid}"
)
return f"上传失败: HTTP {resp.status_code}, 错误信息: {resp.text}"
except requests.exceptions.RequestException as e:
return f"网络请求失败: {str(e)}"
except Exception as e:
return f"上传清关文件失败: {str(e)}"
This diff is collapsed. Click to expand it.
# LangGraph DEEPSEEK Agent 项目
这是一个使用 LangGraph 和 DEEPSEEK 模型构建的智能体项目,实现了天气查询功能。
## 🚀 项目特性
- **DEEPSEEK 模型集成**: 使用 DEEPSEEK 作为主要语言模型
- **ReAct 智能体**: 实现了推理和行动模式
- **工具调用**: 支持天气查询工具
- **LangGraph 服务**: 提供 Web API 接口
- **环境配置**: 支持 .env 环境变量配置
## 📁 项目结构
```
Test_LangGraph/
├── test_agent.py # 智能体实现
├── langgraph.json # LangGraph 配置
├── .env # 环境变量
├── requirements.txt # 依赖列表
├── start_langgraph.py # 启动脚本
├── test_api.py # API 测试脚本
└── README.md # 项目说明
```
## 🛠️ 安装和配置
### 1. 安装依赖
```bash
pip install -r requirements.txt
```
### 2. 配置环境变量
编辑 `.env` 文件,设置您的 DEEPSEEK API 密钥:
```env
OPENAI_API_KEY=your-deepseek-api-key-here
OPENAI_BASE_URL=https://api.deepseek.com/v1
```
### 3. 运行智能体
#### 直接运行
```bash
python test_agent.py
```
#### 启动 LangGraph 服务
```bash
python start_langgraph.py
```
#### 使用 LangGraph CLI
```bash
python -m langgraph_cli dev
```
## 🔧 API 使用
### 服务端点
- **服务地址**: http://localhost:2025
- **API 文档**: http://localhost:2025/docs
- **WebSocket**: ws://localhost:2025/ws
### 测试 API
```bash
python test_api.py
```
### 手动测试
```bash
curl -X POST "http://localhost:2025/weather_agent/invoke" \
-H "Content-Type: application/json" \
-d '{
"messages": [
{"role": "user", "content": "what is the weather in Beijing"}
]
}'
```
## 📊 功能演示
### 智能体对话流程
1. **用户输入**: "what is the weather in sf"
2. **AI 推理**: "I'll check the weather in San Francisco for you."
3. **工具调用**: 调用 `get_weather` 工具
4. **工具响应**: "It's always sunny in San Francisco!"
5. **AI 总结**: "According to the weather information, it's always sunny in San Francisco!"
### 性能指标
- **模型**: deepseek-chat
- **Token 使用**: 输入 189,输出 14,总计 203
- **缓存效率**: 67% 缓存命中率
- **响应时间**: 快速响应
## 🔍 技术细节
### 核心组件
- **LangGraph**: 智能体框架
- **LangChain**: 语言模型集成
- **DEEPSEEK**: 大语言模型
- **ReAct 模式**: 推理 + 行动
### 工具系统
- **get_weather**: 天气查询工具
- **参数**: city (字符串)
- **返回**: 模拟天气信息
## 🐛 故障排除
### 常见问题
1. **依赖安装失败**
```bash
pip install --upgrade pip
pip install -r requirements.txt
```
2. **API 密钥错误**
- 检查 `.env` 文件中的 API 密钥
- 确保 DEEPSEEK API 密钥有效
3. **服务启动失败**
- 检查端口 2025 是否被占用
- 确保所有依赖已正确安装
4. **LangGraph CLI 问题**
```bash
pip install -U "langgraph-cli[inmem]"
```
## 📝 开发说明
### 添加新工具
1. 在 `test_agent.py` 中定义工具函数
2. 将工具添加到 `tools` 列表
3. 重新启动服务
### 修改模型配置
编辑 `test_agent.py` 中的 `ChatOpenAI` 配置:
```python
model = ChatOpenAI(
model="deepseek-chat",
temperature=0.7
)
```
## 📄 许可证
本项目仅供学习和研究使用。
## 🤝 贡献
欢迎提交 Issue 和 Pull Request!
---
**注意**: 请确保您有有效的 DEEPSEEK API 密钥才能使用此项目。
{
"dependencies": ["."],
"graphs": {
"weather_agent": "./langgraph_examples/test_agent.py:agent",
"api_agent": "./langgraph_examples/api_agent.py:agent"
}
}
from langgraph.prebuilt import create_react_agent
from langchain_openai import ChatOpenAI
import os
import sys
import json
from typing import Dict, Any
# 添加项目根目录到 Python 路径
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
# 导入 API 模块
from API.waybill_api import query_waybill_list, create_waybill_d, push_waybill_for_ocr
from API.paperless_api import upload_clearance_file
# 导入工具类
from langgraph_examples.utils.message_processor import MessageProcessor
# 设置 DEEPSEEK API 配置
os.environ["OPENAI_API_KEY"] = os.getenv("OPENAI_API_KEY", "sk-e59da2fbc73240ea8d5ef8fb12657e4b")
os.environ["OPENAI_BASE_URL"] = os.getenv("OPENAI_BASE_URL", "https://api.deepseek.com/v1")
# 创建 DEEPSEEK 聊天模型
model = ChatOpenAI(
model="deepseek-chat", # 使用 DEEPSEEK 模型
temperature=0 # 固定输出,避免改写工具返回
)
## 直接传递函数作为工具
def pre_model_inspect_attachments(state, **kwargs):
"""
LangGraph 预模型钩子:
- 输入/输出都是"状态(dict)",更新 'messages'
- 发现文件/二进制分段:保存到目录,再把该段替换为纯文本 URL
- 支持环境变量:
ATTACH_SAVE_DIR 保存目录,默认 uploads
"""
print("\n=== pre_model_hook: inspect attachments ===")
try:
messages = state.get("messages", [])
# 结构化打印:处理前消息
def _to_simple(msgs):
out = []
for m in msgs or []:
if isinstance(m, dict):
out.append({"role": m.get("role"), "content": m.get("content")})
else:
out.append({
"type": m.__class__.__name__,
"role": getattr(m, "role", None),
"content": getattr(m, "content", None),
})
return out
print("=== 处理前消息 ===")
print(json.dumps(_to_simple(messages), ensure_ascii=False, indent=2))
# 避免字符串与列表拼接导致异常,统一用结构化打印
# print("处理前消息:", messages)
# 使用工具类处理消息
processor = MessageProcessor()
filtered_messages, saved_files = processor.process_messages(messages)
# 直接修改 state 中的 messages 结构,确保后续序列化使用新内容
try:
state["messages"] = filtered_messages
except Exception:
pass
# 结构化打印:处理后消息
print("=== 处理后消息 ===")
print(json.dumps(_to_simple(filtered_messages), ensure_ascii=False, indent=2))
if saved_files:
print("=== saved files ===")
for f in saved_files:
print(f" {f}")
# 返回整个 state,避免上层忽略 messages 的替换
return state
except Exception as e:
print(f"[pre_model_hook error] {e}")
import traceback
traceback.print_exc()
return {}
# 创建 ReAct 智能体
agent = create_react_agent(
model=model,
tools=[query_waybill_list, create_waybill_d, upload_clearance_file, push_waybill_for_ocr],
pre_model_hook=pre_model_inspect_attachments,
prompt="""你是一个专业的出口物流系统智能助手,专门帮助用户处理运单相关的业务操作。
## 你的主要职责:
1. **运单查询**:根据用户需求查询运单列表,支持按状态、时间等条件筛选
2. **运单创建**:协助用户创建D类运单,确保信息完整准确
3. **运单详情**:查询运单的表头信息和表体明细,提供完整的运单数据
4. **业务咨询**:解答用户关于出口物流流程、运单状态、操作规范等问题
## 工作原则:
- 始终以用户需求为导向,提供准确、及时的服务
- 在调用API前,仔细确认用户提供的参数信息
- 对API返回结果进行清晰、易懂的解释
- 如遇到错误,主动分析原因并提供解决方案
- 保持专业、友好的沟通态度
- 严禁改写工具函数返回的文本格式;对工具输出仅直接转述,不得增删前后缀或改写内容。
- 若调用了工具并获得结果,则必须将该工具返回的文本“原样作为最终答复”输出,不允许添加任何解释、建议或额外文字。
## 可用工具:
- query_waybill_list: 查询运单列表,支持按状态筛选,结果以HTML表格形式展示
- create_waybill_d: 根据运单号创建D类运单,需要提供运单号参数
- upload_clearance_file: 上传清关PDF文件,需要 code、slip_id、pdf_path
- push_waybill_for_ocr: 根据运单ID推送OCR进行识别,需要提供运单ID(waybill_id)参数
## query_waybill_list数据展示说明:
- 运单查询结果会自动格式化为HTML表格,包含:运单号、运单类型、运单状态、发件人、运单日期
- 表格在终端中会以HTML源码形式显示,用户可以在支持HTML的环境中查看格式化效果
- 空字段会显示为空单元格
- 运单创建结果会显示成功/失败状态和详细信息
## create_waybill_d数据展示说明:
- 按照数据返回的原本格式进行展示,不得增删前后缀或改写内容。
请根据用户的具体需求,选择合适的工具并提供帮助。"""
)
# 如果直接运行此文件
if __name__ == "__main__":
# {"messages": [{"role": "user", "content": "查询状态为'等待录入'的运单列表"}]}
# {"messages": [{"role": "user", "content": "帮我创建运单,运单编号:2025102904"}]}
# 测试上传清关PDF文件(通过智能体调用 upload_clearance_file 工具)
test_message = (
"请调用工具 upload_clearance_file,并严格按以下参数执行:\n"
"- code: 202510281\n"
"- slip_id: 177950273\n"
"- pdf_path: C:\\Users\\24790\\Desktop\\出口AI资料\\test2-1.pdf\n"
"- file_index: 0\n"
"- uid: 1761635727889\n"
"只需执行工具并原样输出工具返回的文本,不要添加任何解释。"
)
result = agent.invoke({"messages": [{"role": "user", "content": test_message}]})
print(result)
# LangGraph 服务端点
def api_agent_endpoint(input_data: Dict[str, Any]) -> Dict[str, Any]:
"""API 智能体服务端点"""
try:
result = agent.invoke(input_data)
return {
"status": "success",
"data": result,
"error": None
}
except Exception as e:
return {
"status": "error",
"data": None,
"error": str(e)
}
\ No newline at end of file
# pip install -qU "langchain[openai]" langgraph to call the model
from langgraph.prebuilt import create_react_agent
from langchain_openai import ChatOpenAI
import os
from typing import Dict, Any
# 设置 DEEPSEEK API 配置
os.environ["OPENAI_API_KEY"] = os.getenv("OPENAI_API_KEY", "sk-e59da2fbc73240ea8d5ef8fb12657e4b")
os.environ["OPENAI_BASE_URL"] = os.getenv("OPENAI_BASE_URL", "https://api.deepseek.com/v1")
def get_weather(city: str) -> str:
"""Get weather for a given city."""
return f"It's always sunny in {city}!"
# 创建 DEEPSEEK 聊天模型
model = ChatOpenAI(
model="deepseek-chat", # 使用 DEEPSEEK 模型
temperature=0.7
)
# 创建 ReAct 智能体
agent = create_react_agent(
model=model,
tools=[get_weather]
)
# LangGraph 服务端点
def weather_agent_endpoint(input_data: Dict[str, Any]) -> Dict[str, Any]:
"""LangGraph 服务端点"""
try:
result = agent.invoke(input_data)
return {
"status": "success",
"data": result,
"error": None
}
except Exception as e:
return {
"status": "error",
"data": None,
"error": str(e)
}
# 如果直接运行此文件
if __name__ == "__main__":
# Run the agent
result = agent.invoke(
{"messages": [{"role": "user", "content": "what is the weather in sf"}]}
)
print(result)
\ No newline at end of file
"""
工具类包
"""
from .message_processor import MessageProcessor
__all__ = ['MessageProcessor']
"""
消息处理和文件保存工具类
"""
import os
import base64
import uuid
from pathlib import Path
from typing import List, Any, Union
class MessageProcessor:
"""消息处理和文件保存工具类"""
def __init__(self, save_dir: str = None):
"""
初始化消息处理器
Args:
save_dir: 文件保存目录,默认为环境变量 ATTACH_SAVE_DIR 或 "uploads"
"""
self.save_dir = save_dir or os.getenv("ATTACH_SAVE_DIR", "uploads")
self._saved_files: list[str] = []
def save_and_get_file_url(self, file_data: str, filename: str = None, mime_type: str = None) -> str:
"""
保存文件并返回绝对路径字符串
Args:
file_data: base64编码的文件数据
filename: 文件名
mime_type: MIME类型
Returns:
文件绝对路径字符串
"""
uploads = Path(self.save_dir)
uploads.mkdir(exist_ok=True)
# 生成安全文件名
safe_name = filename or f"{uuid.uuid4().hex}"
if mime_type == "application/pdf" and not safe_name.lower().endswith(".pdf"):
safe_name += ".pdf"
out_path = uploads / safe_name
try:
# 解码 base64 数据并保存
with open(out_path, "wb") as f:
f.write(base64.b64decode(file_data))
abs_path = str(out_path.resolve())
print(f" -> saved file: {abs_path}")
self._saved_files.append(abs_path)
return abs_path
except Exception as e:
print(f" -> save file failed: {e}")
return f"[附件保存失败: {filename or 'unknown'}]"
def process_content(self, content: Any) -> str:
"""
处理消息内容,提取文件并转换为文本
Args:
content: 消息内容,可能是字符串、列表或字典
Returns:
处理后的文本内容
"""
if isinstance(content, str):
return content
elif isinstance(content, list):
text_parts = []
for part in content:
if isinstance(part, str):
text_parts.append(part)
elif isinstance(part, dict):
part_type = part.get("type")
if part_type == "text":
text_parts.append(part.get("text", ""))
elif part_type == "file":
# 处理文件类型
file_data = part.get("data")
filename = part.get("metadata", {}).get("filename")
mime_type = part.get("mime_type")
if file_data:
file_path = self.save_and_get_file_url(file_data, filename, mime_type)
text_parts.append(file_path)
else:
text_parts.append(f"[文件缺失: {filename or 'unknown'}]")
else:
# 未知类型最小化占位
text_parts.append(f"[{part_type or 'unknown'}]")
return "\n".join([t for t in text_parts if t])
elif isinstance(content, dict):
# 单个字典内容
ctype = content.get("type")
if ctype == "text":
return content.get("text", "")
if ctype == "file":
file_data = content.get("data")
filename = content.get("metadata", {}).get("filename")
mime_type = content.get("mime_type")
if file_data:
return self.save_and_get_file_url(file_data, filename, mime_type)
else:
return f"[文件缺失: {filename or 'unknown'}]"
else:
return f"[{ctype or 'unknown'}]"
else:
return str(content)
def process_messages(self, messages: List[Any]) -> tuple[List[Any], List[str]]:
"""
处理消息列表,提取文件并转换为文本
Args:
messages: 消息列表
Returns:
(处理后的消息列表, 保存的文件路径列表)
"""
filtered_messages = []
for idx, m in enumerate(messages or []):
# 兼容 dict 或 LangChain 的消息对象
if isinstance(m, dict):
role = m.get("role")
content = m.get("content")
else:
role = getattr(m, "role", None)
content = getattr(m, "content", None)
print(f"[msg#{idx}] role={role!r}, content_type={type(content).__name__}")
# 仅当 HumanMessage 且 content 中包含 file 分片时进行处理;否则保持原样
msg_type = m.get("type") if isinstance(m, dict) else m.__class__.__name__
should_process = (msg_type == "HumanMessage") and isinstance(content, list) and any(isinstance(p, dict) and p.get("type") == "file" for p in content)
if should_process:
# 提取文本与文件路径
text_parts: list[str] = []
file_paths: list[str] = []
for part in content:
if isinstance(part, dict):
if part.get("type") == "text":
text_parts.append(part.get("text", ""))
elif part.get("type") == "file":
# 兼容两种文件结构:
# 1) {"type":"file", "data":"<base64>", "metadata":{"filename":...}, "mime_type":"application/pdf"}
# 2) {"type":"file", "file":{"file_data":"data:application/pdf;base64,<base64>", "filename":"..."}}
file_data = part.get("data")
filename = part.get("metadata", {}).get("filename")
mime_type = part.get("mime_type")
if not file_data and isinstance(part.get("file"), dict):
f = part.get("file") or {}
file_data_url = f.get("file_data")
filename = f.get("filename") or filename
# 解析 data URL 或原始 base64
if isinstance(file_data_url, str):
if file_data_url.startswith("data:") and "," in file_data_url:
try:
header, b64_payload = file_data_url.split(",", 1)
# 格式如 data:application/pdf;base64
if header.startswith("data:") and ";" in header:
mime_type = header[5:].split(";", 1)[0] or mime_type
file_data = b64_payload
except Exception:
file_data = None
else:
# 非 data URL,当作纯 base64
file_data = file_data_url
if file_data:
saved_path = self.save_and_get_file_url(file_data, filename, mime_type)
file_paths.append(saved_path)
merged_text = "\n".join([t for t in text_parts if t])
if file_paths:
merged_text = (merged_text + "\n" + "\n".join(file_paths)).strip()
new_content = [{"type": "text", "text": merged_text}]
if isinstance(m, dict):
filtered_msg = {**m, "content": new_content}
else:
try:
filtered_msg = m.__class__(
content=new_content,
additional_kwargs=getattr(m, "additional_kwargs", {}),
response_metadata=getattr(m, "response_metadata", {}),
id=getattr(m, "id", None),
)
except Exception:
# 兜底为等价字典并保留 id
filtered_msg = {
"type": m.__class__.__name__,
"role": (role or "user"),
"id": getattr(m, "id", None),
"additional_kwargs": getattr(m, "additional_kwargs", {}),
"response_metadata": getattr(m, "response_metadata", {}),
"content": new_content,
}
filtered_messages.append(filtered_msg)
try:
print(f" -> processed to: {len(merged_text)} chars")
except Exception:
print(" -> processed")
else:
# 不处理,其它消息保持不变
filtered_messages.append(m)
print(" -> processed (no change)")
return filtered_messages, list(self._saved_files)
# LangGraph DEEPSEEK Agent 项目依赖
# 当前已安装的核心包版本
# 核心框架
langchain==1.0.2
langchain-core==1.0.1
langchain-openai==1.0.1
# LangGraph 相关
langgraph==1.0.1
langgraph-checkpoint==3.0.0
langgraph-prebuilt==1.0.1
langgraph-sdk==0.2.9
# OpenAI API (用于 DEEPSEEK)
openai==2.6.1
# HTTP 请求库
requests==2.31.0
#!/usr/bin/env python3
"""
LangGraph 服务启动脚本
"""
import subprocess
import sys
import os
import time
from pathlib import Path
def check_dependencies():
"""检查依赖是否安装"""
try:
import langgraph
import langchain
import langchain_openai
print("✅ 所有依赖已安装")
return True
except ImportError as e:
print(f"❌ 缺少依赖: {e}")
print("请运行: pip install -r requirements.txt")
return False
def start_langgraph_server():
"""启动 LangGraph 服务器"""
print("🚀 启动 LangGraph 服务器...")
# 检查 langgraph.json 是否存在
if not Path("langgraph.json").exists():
print("❌ 未找到 langgraph.json 配置文件")
return False
try:
# 启动 LangGraph 开发服务器
# 使用 --host 0.0.0.0 允许通过 IP 地址访问
# cmd = ["python", "-m", "langgraph_cli", "dev", "--port", "2025"]
cmd = ["python", "-m", "langgraph_cli", "dev", "--host", "0.0.0.0", "--port", "2025"]
print(f"执行命令: {' '.join(cmd)}")
# 强制使用 UTF-8 编码,避免 GBK 解码错误
try:
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
sys.stderr.reconfigure(encoding="utf-8", errors="replace")
except Exception:
pass
# 设置环境变量确保子进程使用 UTF-8
env = os.environ.copy()
env["PYTHONIOENCODING"] = "UTF-8"
env["PYTHONUTF8"] = "1"
process = subprocess.Popen(
cmd,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True,
encoding="utf-8",
errors="replace",
bufsize=0,
env=env
)
print("🌐 LangGraph 服务器已启动")
print("📡 服务地址: http://0.0.0.0:2025")
print("🔌 WebSocket: ws://0.0.0.0:2025/ws")
print("📚 API 文档: http://0.0.0.0:2025/docs")
print("💡 提示: 可通过本机 IP 地址访问,例如: http://192.168.1.44:2025")
print("\n按 Ctrl+C 停止服务器\n")
# 实时输出日志
for line in process.stdout:
print(line.rstrip())
except KeyboardInterrupt:
print("\n🛑 正在停止服务器...")
process.terminate()
process.wait()
print("✅ 服务器已停止")
except Exception as e:
print(f"❌ 启动失败: {e}")
return False
return True
def main():
"""主函数"""
print("=" * 50)
print("🎯 LangGraph DEEPSEEK Agent 启动器")
print("=" * 50)
# 检查依赖
if not check_dependencies():
sys.exit(1)
# 检查环境变量
if not os.getenv("OPENAI_API_KEY"):
print("⚠️ 警告: 未设置 OPENAI_API_KEY 环境变量")
print("请在 .env 文件中设置或直接设置环境变量")
# 启动服务器
start_langgraph_server()
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""
Debug-friendly LangGraph server launcher (single process).
Run this file in PyCharm Debug to hit breakpoints (e.g., pre_model_hook).
"""
import os
import sys
import json
from pathlib import Path
def setup_environment():
# Ensure project root on sys.path
root = Path(__file__).parent.resolve()
sys.path.insert(0, str(root))
# Load graphs from langgraph.json
graphs = {}
cfg = root / "langgraph.json"
if cfg.exists():
with open(cfg, "r", encoding="utf-8") as f:
try:
data = json.load(f)
graphs = data.get("graphs", {})
except Exception as e:
print(f"⚠️ 读取 langgraph.json 失败: {e}")
# Baseline env
os.environ.setdefault("LANGGRAPH_API_URL", "http://localhost:2025")
os.environ.setdefault("LANGGRAPH_RUNTIME_EDITION", "inmem")
os.environ.setdefault("LANGGRAPH_DISABLE_FILE_PERSISTENCE", "false")
os.environ.setdefault("LANGGRAPH_ALLOW_BLOCKING", "true")
os.environ.setdefault("ALLOW_PRIVATE_NETWORK", "true")
os.environ.setdefault("LANGSERVE_GRAPHS", json.dumps(graphs))
os.environ.setdefault("N_JOBS_PER_WORKER", "1")
os.environ.setdefault("ATTACH_SAVE_DIR", "uploads")
os.environ.setdefault("DATABASE_URI", ":memory:")
os.environ.setdefault("REDIS_URI", "fake")
os.environ.setdefault("MIGRATIONS_PATH", "__inmem")
# Load .env if present
env_file = root / ".env"
if env_file.exists():
try:
from dotenv import load_dotenv
load_dotenv(env_file)
print(" Loaded .env")
except Exception:
print(" python-dotenv 未安装,跳过 .env 加载")
def main():
print(" Starting LangGraph server (single-process, debug-friendly)...")
setup_environment()
print("\n" + "=" * 60)
print(" Server URL: http://localhost:2025")
print(" API Docs: http://localhost:2025/docs")
print(" Studio UI: http://localhost:2025/ui")
print(" Health: http://localhost:2025/ok")
print("=" * 60)
try:
import uvicorn
uvicorn.run(
"langgraph_api.server:app",
host="0.0.0.0",
port=2025,
reload=False, # disable auto-reload to avoid child processes
access_log=False,
)
except KeyboardInterrupt:
print("\n Server stopped by user")
except Exception as e:
print(f" Failed to start: {e}")
import traceback; traceback.print_exc()
sys.exit(1)
if __name__ == "__main__":
main()
\ No newline at end of file
No preview for this file type