forked from lingyuzeng/agent
update
This commit is contained in:
332
README.md
332
README.md
@@ -1,46 +1,308 @@
|
||||
## Agent
|
||||
# Agent
|
||||
|
||||
自建基于llama-box启动qwen3-code-flash-1M 的 AI Agent 代码仓库
|
||||
自建基于 **llama-box** 启动 **Qwen3-Coder(-Flash)** 的 AI Agent 代码仓库。
|
||||
|
||||
本仓库新增 `langgraph_qwen` 轻量适配层,帮助你在 **LangGraph** 中直接接入 Qwen3(含工具调用、流式增量、OpenAI 兼容接口)。并提供 **MCP**(Model Context Protocol)工具注入示例。
|
||||
|
||||
### llama-box 启动参数
|
||||
---
|
||||
|
||||
注意:
|
||||
添加 --enable-reasoning:这个参数对 Qwen3 模型的工具调用非常重要,能够提升推理能力。
|
||||
添加 --jinja:这个参数用于加载 Jinja 模板,Qwen3 模型需要使用 Jinja 模板进行推理。、
|
||||
## 特性一览
|
||||
|
||||
启动参数参考:
|
||||
- **LangGraph 适配**:
|
||||
- `get_qwen_chat_model`:创建 LangChain ChatModel(优先 OpenAI 兼容路径,失败回退 DashScope)。
|
||||
- `bind_qwen_tools`:将 LangChain 工具绑定到模型,支持 `tool_choice`。
|
||||
- 预置示例:`examples/qwen_langgraph_react.py`、`examples/qwen_langgraph_stream.py`、`examples/qwen_langgraph_custom_model.py`。
|
||||
|
||||
- **自定义 ChatModel(强烈推荐)**:
|
||||
- `langgraph_qwen.ChatQwenOpenAICompat`:
|
||||
- 直接对接 OpenAI 兼容 **/v1/chat/completions**(可指向 DashScope 或自建网关/llama-box)。
|
||||
- **非流式**:完整解析 `tool_calls`,装入 `AIMessage.tool_calls`,可被 LangGraph ToolNode 正确消费。
|
||||
- **流式**:实现 SSE 增量组装器,安全缓冲 `delta.tool_calls[].function.name/arguments` 的碎片,在 `[DONE]` 时产出完整 `tool_calls`;文本 token 即时输出。
|
||||
- **工具注入**:`.bind_tools([...]).bind(tool_choice="auto")`;`extra_body` 透传服务端定制参数。
|
||||
- **JSON Schema 兼容性增强**:内部使用 `convert_to_openai_tool` + 自定义归一化,避免网关对工具 schema 的严格校验导致 4xx/5xx。
|
||||
|
||||
- **工具名称校验**(默认启用):
|
||||
- 仅允许 `[a-zA-Z0-9_-]`、长度 ≤ 64;违规会报错并给出修复建议。
|
||||
- 公共函数:`langgraph_qwen.validators.validate_tool_names` / `sanitize_tool_name`。
|
||||
|
||||
- **MCP 工具注入**:
|
||||
- 通过官方库 **`langchain-mcp-adapters`** 获取 MCP 服务器工具,并注入 LangGraph。
|
||||
- 示例:`examples/mcp_adapters/inject_to_langgraph.py`,包含 `streamable_http` 快速演示服务。
|
||||
|
||||
---
|
||||
|
||||
## 快速开始
|
||||
|
||||
### 1) 安装
|
||||
|
||||
```bash
|
||||
llama-box \
|
||||
--host 0.0.0.0 \
|
||||
--port 8080 \
|
||||
--model /Volumes/long990max/gpustack_data/unsloth/Qwen3-Coder-30B-A3B-Instruct-1M-GGUF/Qwen3-Coder-30B-A3B-Instruct-1M-UD-Q4_K_XL.gguf \
|
||||
--chat-template chatml \
|
||||
--jinja \
|
||||
--enable-reasoning \
|
||||
--flash-attn \
|
||||
--cache-type-k q4_0 \
|
||||
--cache-type-v q4_0 \
|
||||
--ctx-size 262144 \
|
||||
--gpu-layers 49 \
|
||||
--threads 12 \
|
||||
--threads-batch 16 \
|
||||
--threads-http 16 \
|
||||
--batch-size 1024 \
|
||||
--ubatch-size 1024 \
|
||||
--defrag-thold -1 \
|
||||
uv pip install -U langgraph langchain httpx
|
||||
# 可选:
|
||||
uv pip install -U langchain-openai # 如需走 ChatOpenAI 生态
|
||||
uv pip install -U '.[tongyi]' # 如需走 ChatTongyi(DashScope SDK)
|
||||
uv pip install -U '.[mcp-adapters]' # 注入 MCP 工具
|
||||
uv pip install -U '.[viz]' # 导出 Graph PNG 可视化
|
||||
uv pip install -U '.[all]' # 一次装全(包含上面所有可选)
|
||||
```
|
||||
|
||||
> 项目已组织为可安装包,建议在仓库根目录执行:
|
||||
>
|
||||
> ```bash
|
||||
> uv pip install -e '.[openai,custom]'
|
||||
> # 或:uv pip install -e '.[openai]'
|
||||
> # 或:uv pip install -e '.[custom]'
|
||||
> ```
|
||||
|
||||
### 2) 环境变量(必看)
|
||||
|
||||
> 适配器会自动读取 `.env`(已内置加载),也可直接 `export`。同名变量的优先级按示例说明。
|
||||
|
||||
**核心**
|
||||
|
||||
- `QWEN_BASE_URL`:OpenAI 兼容基地址,如 `https://host/v1`;也可填完整端点 `.../v1/chat/completions`。
|
||||
- `QWEN_MODEL`:模型名,如 `qwen3-coder-flash-1M`(以后端服务可用名为准)。
|
||||
- `QWEN_API_KEY`:客户端访问网关的 Key(若网关启用鉴权必填)。
|
||||
|
||||
**兼容型 API Key(择一)**
|
||||
|
||||
- `QWEN_API_KEY`(优先)
|
||||
- `GPUSTACK_API_KEY`(自建 gpustack 网关时可用)
|
||||
- `OPENAI_API_KEY`(OpenAI 风格兼容)
|
||||
- `DASHSCOPE_API_KEY`(阿里云百炼 Key)
|
||||
|
||||
**鉴权/网络/调试**
|
||||
|
||||
- `QWEN_AUTH_HEADER`:默认 `Authorization`;改为 `X-API-Key` 可适配自定义头。
|
||||
- `QWEN_AUTH_SCHEME`:默认 `Bearer`;若网关只要裸 Key,设为空字符串 `''`。
|
||||
- `QWEN_TIMEOUT`:请求超时秒数,默认 `60`,建议长上下文设大如 `180`。
|
||||
- `QWEN_HTTP_TRUST_ENV`:是否继承系统代理,默认 `1`;设 `0` 可禁用代理环境变量。
|
||||
- `QWEN_DEBUG`:`1` 打印方法/URL/超时/代理选项。
|
||||
- `QWEN_DEBUG_BODY`:`1` 打印请求 JSON(自动打码密钥)。
|
||||
- `QWEN_DEBUG_RESP`:`1` 打印响应/错误体。
|
||||
|
||||
**MCP(示例工具用)**
|
||||
|
||||
- `WEATHER_MCP_URL`:MCP 工具服务地址,如 `http://127.0.0.1:8010/mcp`。
|
||||
- `WEATHER_TRANSPORT`:`streamable_http`(HTTP 流)或 `stdio`。
|
||||
|
||||
### 3) 最小示例(ReAct + 工具调用)
|
||||
|
||||
```bash
|
||||
export QWEN_BASE_URL='https://ai.jmsu.top/v1'
|
||||
export QWEN_MODEL='qwen3-coder-flash-1M'
|
||||
# 若你的网关启用了 Bearer 鉴权:export QWEN_API_KEY='your_token'
|
||||
# 推荐调试:export QWEN_HTTP_TRUST_ENV=0 QWEN_DEBUG=1 QWEN_DEBUG_BODY=1 QWEN_DEBUG_RESP=1
|
||||
|
||||
python examples/qwen_langgraph_react.py
|
||||
```
|
||||
|
||||
### 4) 流式示例(SSE 增量)
|
||||
|
||||
```bash
|
||||
python examples/qwen_langgraph_stream.py
|
||||
```
|
||||
> 实时输出 token;在 `[DONE]` 时产出完整 `tool_calls`。
|
||||
|
||||
### 5) 自定义模型示例
|
||||
|
||||
```bash
|
||||
python examples/qwen_langgraph_custom_model.py
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 自定义 ChatModel:`ChatQwenOpenAICompat`
|
||||
|
||||
**最小用法**
|
||||
|
||||
```python
|
||||
from langgraph_qwen import ChatQwenOpenAICompat
|
||||
from langgraph.prebuilt import create_react_agent
|
||||
from langchain_core.messages import HumanMessage
|
||||
from langchain.tools import tool
|
||||
|
||||
@tool
|
||||
def ping(_: str = "") -> str:
|
||||
return "pong"
|
||||
|
||||
model = ChatQwenOpenAICompat(temperature=0).bind_tools([ping]).bind(tool_choice="auto")
|
||||
agent = create_react_agent(model, [ping])
|
||||
res = agent.invoke({"messages": [HumanMessage(content="调用工具 ping 并返回结果。")]})
|
||||
print(res["messages"][-1].content)
|
||||
```
|
||||
|
||||
**为什么用它**
|
||||
|
||||
- 避免第三方封装差异,统一工具/流式行为。
|
||||
- 可靠的工具 schema 归一化(`convert_to_openai_tool` + 兜底修复)。
|
||||
- 自定义鉴权头/鉴权前缀、超时、是否继承系统代理等。
|
||||
|
||||
---
|
||||
|
||||
## MCP:将远程工具注入 LangGraph
|
||||
|
||||
### 启动一个最小 `streamable_http` MCP 服务
|
||||
|
||||
示例在 `examples/mcp_adapters/`,你也可以用 FastAPI/fastmcp 写个简易 weather 工具。
|
||||
|
||||
### 注入并调用
|
||||
|
||||
```bash
|
||||
uv pip install -U '.[mcp-adapters]'
|
||||
|
||||
export WEATHER_MCP_URL='http://127.0.0.1:8010/mcp'
|
||||
export WEATHER_TRANSPORT='streamable_http'
|
||||
|
||||
export QWEN_BASE_URL='https://ai.jmsu.top/v1'
|
||||
export QWEN_MODEL='qwen3-coder-flash-1M'
|
||||
# export QWEN_API_KEY='your_token' # 如网关要求鉴权
|
||||
|
||||
export QWEN_DEBUG=1
|
||||
export QWEN_DEBUG_BODY=1
|
||||
export QWEN_DEBUG_RESP=1
|
||||
|
||||
python examples/mcp_adapters/inject_to_langgraph.py
|
||||
```
|
||||
|
||||
> 若看到 5xx,多半是网关的模板/工具字段解析不兼容。适配器已做 schema 瘦身与修正;仍有问题可先把 `tool_choice="none"` 做两阶段调用(先思考,后仅注入目标工具)。
|
||||
|
||||
---
|
||||
|
||||
## 与网关/llama-box 的部署建议
|
||||
|
||||
- **直连 llama-box**:将 `QWEN_BASE_URL` 指向外部可达的 OpenAI 兼容端(如 Caddy 反代到 llama-box)。
|
||||
- **Caddy 鉴权(推荐)**:
|
||||
- 在 `/v1*` 反代前匹配 `Authorization: Bearer {env.API_TOKEN}`;未命中返回 `401`。
|
||||
- 反代配置应开启:`flush_interval -1`、`transport http { versions 1.1; keepalive 0; }`、移除 `Accept-Encoding`,避免 SSE 被缓冲或压缩。
|
||||
- 客户端侧填 `QWEN_API_KEY=$API_TOKEN`。如用自定义头,设置 `QWEN_AUTH_HEADER`/`QWEN_AUTH_SCHEME`。
|
||||
- **避免多级模板改写**:某些中间层会对 `tools` 进行模板渲染或字段改写,导致 5xx;直连或使用“透传”配置最稳。
|
||||
|
||||
---
|
||||
|
||||
## 常见问题(FAQ)
|
||||
|
||||
### 1) `StructuredTool does not support sync invocation.`
|
||||
使用 `agent.ainvoke(...)` 或用 LangGraph 的异步入口;确保工具函数是异步或由运行时在工具节点异步执行。
|
||||
|
||||
### 2) 401 / `InvalidApiKey`
|
||||
- 网关启用鉴权但客户端未传:设置 `QWEN_API_KEY`。
|
||||
- 使用了自定义头:配置 `QWEN_AUTH_HEADER` 和(必要时)置空 `QWEN_AUTH_SCHEME`。
|
||||
|
||||
### 3) 502 / 500(带 tools 时)
|
||||
- 工具 schema 不被网关接受:使用本适配器(已做 JSON Schema 归一化),或先将 `tool_choice='none'` 做两阶段调用。
|
||||
- 上游超时:调大 `QWEN_TIMEOUT`,检查上游模型负载。
|
||||
- 中间层模板渲染异常:改为直连 llama-box 或关闭模板改写。
|
||||
|
||||
### 4) 请求莫名走系统代理
|
||||
设置 `QWEN_HTTP_TRUST_ENV=0`;或在命令前写 `HTTPS_PROXY= HTTP_PROXY=`。
|
||||
|
||||
### 5) 流式输出与工具参数碎片
|
||||
由 `ChatQwenOpenAICompat` 负责缓冲与合并,无需额外处理;最终分块含完整 `tool_calls`。
|
||||
|
||||
---
|
||||
|
||||
## 项目结构
|
||||
|
||||
```
|
||||
.
|
||||
├─ langgraph_qwen/
|
||||
│ ├─ __init__.py
|
||||
│ ├─ factory.py
|
||||
│ ├─ chat_model.py
|
||||
│ ├─ utils.py
|
||||
│ └─ validators.py
|
||||
├─ examples/
|
||||
│ ├─ qwen_langgraph_react.py
|
||||
│ ├─ qwen_langgraph_stream.py
|
||||
│ ├─ qwen_langgraph_custom_model.py
|
||||
│ ├─ mcp_adapters/
|
||||
│ └─ stream_modes/
|
||||
├─ server/ #(可选)演示用 weather MCP 服务
|
||||
│ └─ weather_server.py
|
||||
├─ pyproject.toml
|
||||
├─ README.md
|
||||
└─ qwen3_coder_with_qwen_agent.py
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## `curl` 快速探针
|
||||
|
||||
**A:带工具 + `tool_choice:auto`**
|
||||
```bash
|
||||
curl -i 'https://<host>/v1/chat/completions' \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer $QWEN_API_KEY" \# 如无鉴权可省略
|
||||
-d '{
|
||||
"model":"qwen3-coder-flash-1M",
|
||||
"messages":[{"role":"user","content":"纽约天气"}],
|
||||
"tools":[{"type":"function","function":{
|
||||
"name":"get_weather","description":"Get weather",
|
||||
"parameters":{"type":"object","properties":{"location":{"type":"string"}},"required":["location"]}
|
||||
}}],
|
||||
"tool_choice":"auto"
|
||||
}'
|
||||
```
|
||||
|
||||
**B:带工具 + `tool_choice:none`(只思考不调用)**
|
||||
```bash
|
||||
curl -i 'https://<host>/v1/chat/completions' \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer $QWEN_API_KEY" \
|
||||
-d '{
|
||||
"model":"qwen3-coder-flash-1M",
|
||||
"messages":[{"role":"user","content":"纽约天气"}],
|
||||
"tools":[{"type":"function","function":{
|
||||
"name":"get_weather","parameters":{"type":"object","properties":{"location":{"type":"string"}}}
|
||||
}}],
|
||||
"tool_choice":"none"
|
||||
}'
|
||||
```
|
||||
|
||||
**C:最小化(无工具)**
|
||||
```bash
|
||||
curl -i 'https://<host>/v1/chat/completions' \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer $QWEN_API_KEY" \
|
||||
-d '{
|
||||
"model":"qwen3-coder-flash-1M",
|
||||
"messages":[{"role":"user","content":"你好"}]
|
||||
}'
|
||||
```
|
||||
|
||||
> 如需直连测试,临时禁用代理:`HTTPS_PROXY= HTTP_PROXY=`。
|
||||
|
||||
---
|
||||
|
||||
## llama-box 启动参考
|
||||
|
||||
> 对 Qwen3 的工具/推理友好:`--jinja`、`--enable-reasoning` 建议开启。
|
||||
|
||||
```bash
|
||||
llama-box \
|
||||
--host 0.0.0.0 \
|
||||
--port 8080 \
|
||||
--model /path/to/Qwen3-Coder-…-GGUF.gguf \
|
||||
--chat-template chatml \
|
||||
--jinja \
|
||||
--enable-reasoning \
|
||||
--flash-attn \
|
||||
--cache-type-k q4_0 \
|
||||
--cache-type-v q4_0 \
|
||||
--ctx-size 262144 \
|
||||
--gpu-layers 49 \
|
||||
--threads 12 \
|
||||
--threads-batch 16 \
|
||||
--threads-http 16 \
|
||||
--batch-size 1024 \
|
||||
--ubatch-size 1024 \
|
||||
--defrag-thold -1 \
|
||||
--no-context-shift
|
||||
```
|
||||
|
||||
### 安装环境
|
||||
---
|
||||
|
||||
## 贡献与许可证
|
||||
|
||||
欢迎提交 Issue/PR。许可证见仓库 `LICENSE`(如未提供,默认以仓库许可为准)。
|
||||
|
||||
```bash
|
||||
uv venv --managed-python -p 3.12 --seed .venv
|
||||
source .venv/bin/activate
|
||||
git clone https://github.com/hotwa/Qwen-Agent
|
||||
cd Qwen-Agent
|
||||
uv pip install -e ./"[gui,rag,code_interpreter,mcp]"
|
||||
cd ..
|
||||
python qwen3_coder_with_qwen_agent.py
|
||||
```
|
||||
Reference in New Issue
Block a user