Skip to content

附录 A 环境与模型接入速查 ​

这一页是用来照抄的,不需要通读。出错时先来这里对一遍。

A.1 安装 ​

bash
# 推荐用 uv,装得快、依赖解析稳
uv venv
uv pip install "agentscope>=2,<3" fastapi uvicorn python-dotenv

# 用 pip 也一样
python -m venv .venv
source .venv/bin/activate          # Windows: .venv\Scripts\activate
pip install "agentscope>=2,<3" fastapi uvicorn python-dotenv

国内网络慢的话加镜像:

bash
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple "agentscope>=2,<3" fastapi uvicorn python-dotenv

验证:

bash
python -c "import agentscope; print(agentscope.__version__)"

A.2 三个环境变量 ​

本书所有章节只认这三个名字。放在 code/.env 里,load_dotenv() 会自动读取。

bash
OPENAI_API_KEY=sk-xxxxxxxx
OPENAI_BASE_URL=https://your-endpoint/v1
OPENAI_MODEL=your-model-name

OPENAI_BASE_URL 为空时会连官方地址。本地部署(vLLM、Ollama、GPUStack)一定要填,否则请求会发到公网上去。

.env 必须进 .gitignore。这一条在第 15.4 节的清单里排第一,因为它出事之后没法撤回。

A.3 常见兼容端点 ​

只要服务商支持 OpenAI 的 /chat/completions 协议,就能直接用 OpenAIChatModel。填的是 base_url,注意结尾的 /v1 要不要带——各家不一样,填错了表现是 404。

服务base_url 形状备注
OpenAI 官方https://api.openai.com/v1留空也行
DeepSeekhttps://api.deepseek.com/v1兼容度高
阿里百炼(DashScope)https://dashscope.aliyuncs.com/compatible-mode/v1必须走 compatible-mode 这个路径
智谱https://open.bigmodel.cn/api/paas/v4版本号是 v4,不是 v1
Moonshothttps://api.moonshot.cn/v1
火山方舟https://ark.cn-beijing.volces.com/api/v3版本号 v3
硅基流动https://api.siliconflow.cn/v1聚合了多家开源模型
本地 vLLMhttp://127.0.0.1:8000/v1
本地 Ollamahttp://127.0.0.1:11434/v1
GPUStackhttp://<内网IP>:<端口>/v1部署在哪儿就填哪儿

不确定的时候,先用 curl 试一次,比在代码里猜快:

bash
curl https://your-endpoint/v1/models \
  -H "Authorization: Bearer $OPENAI_API_KEY"

能列出模型清单,说明地址和 key 都对了。

A.4 常见报错对照 ​

报错原因处理
401 AuthenticationError,报错里的 key 尾号不是你写的那个系统环境里已经有 OPENAI_API_KEY,而 load_dotenv() 默认不覆盖已存在的变量用 load_dotenv(override=True)(本书所有脚本都是),或先清掉系统变量
401 AuthenticationError,尾号对得上key 过期或被禁用去服务商后台重新生成
404 Not Foundbase_url 少了或多了 /v1对照 A.3 的表
404 model not found模型名不对用 A.3 的 curl 列出可用模型名,照抄
Error: file_path must be an absolute pathWrite 只接受绝对路径提示词里直接给出沙箱的绝对路径,不要写 ./sandbox
TypeError: 'async_generator' object is not iterable对异步生成器用了 enumerate自己维护计数器,见第 2 章
400 context length exceeded上下文超了第 8 章的 ContextConfig 调小 trigger_ratio
ConnectError / Timeout网络或代理见下一条
请求全部 502,但浏览器能打开网页系统代理拦截了 Python见下一条
TypeError: object AsyncGenerator can't be used in await把流式响应当协程等了模型调用返回的是异步生成器,不要 await
偶发 httpx.ReadError,重跑一次又好了服务端把连接掐了(内网网关、长连接超时都可能导致)见"连接断了怎么办"

代理问题值得单独说。 环境里设了 http_proxy 时,httpx 默认会走代理,本地地址(127.0.0.1、内网 IP)的请求也会被一起代理出去,表现为 502 或超时。两个办法:

bash
# 办法一:给本地地址开例外
export NO_PROXY=127.0.0.1,localhost,192.168.0.0/16
python
# 办法二:让这个模型的 httpx 客户端彻底不看环境变量
import httpx

model = OpenAIChatModel(
    credential=OpenAICredential(api_key=API_KEY, base_url=BASE_URL or None),
    model=MODEL_NAME,
    context_size=32_768,
    client_kwargs={"http_client": httpx.AsyncClient(trust_env=False)},
)

client_kwargs 会原样传给 openai.AsyncClient,trust_env=False 让它忽略 http_proxy / https_proxy / no_proxy 三个环境变量。内网端点用这个最省事。

连接断了怎么办。 内网端点偶尔会在流式中途断连,报 httpx.ReadError。这不是代码错误,但会中断整轮对话,而且默认不会重试:

  • 模型层 OpenAIChatModel(max_retries=3) 只覆盖建立连接和普通 API 错误
  • Agent 层的 ModelConfig.max_retries 默认是 0,一次失败直接向外抛

要让它自己扛住,给 Agent 配一份 ModelConfig:

python
from agentscope.agent import ModelConfig

agent = Agent(
    ...,
    model_config=ModelConfig(max_retries=1),
)

ModelConfig 只有两个字段:max_retries 和 fallback_model。后者可以挂一个备用模型,主模型彻底失败时切过去——内网只有一个端点的话用不上。

批处理、长任务、自检脚本都建议加上。交互式对话里加不加都行——用户看得见错误,按一下重发比干等两分钟好。

写测试脚本时如果用的是标准库 urllib,要用一个空代理的 opener:

python
opener = urllib.request.build_opener(urllib.request.ProxyHandler({}))

A.5 算自己的成本 ​

不要相信任何"某模型多便宜"的结论,价格变动频繁。用第 14 章的公式,把当前单价填进去自己算:

单次回复成本 = 输入 tokens / 1e6 × 输入单价 + 输出 tokens / 1e6 × 输出单价

要知道的两件事:

  • 输入 token 数随轮次线性增长。 第 9 章之后每轮都带完整上下文,第 10 轮的单次成本大约是第 1 轮的好几倍
  • 缓存输入的价格通常是正常输入的一到两成。 同一个会话连续聊,从第二轮开始普遍有缓存命中;换话题重新开头,命中率会掉下来

所以"一次对话多少钱"这个问题没有统一答案,只有"你的使用方式下多少钱"。第 14 章的面板就是为回答这个问题做的。

附录 B 事件类型全表 ​

28 个事件类型。第二列是"什么时候发",第四列是"前端该怎么做"——照第四列写 reducer,基本不会漏。

字段列只列框架自带的业务字段,id / created_at / metadata / type 这四个每个事件都有,不再重复。

B.1 回复的边界 ​

类型何时发出业务字段前端
REPLY_START一轮回复开始session_id, reply_id, name, role新建一个助手气泡,记下 reply_id
REPLY_END一轮回复结束(正常、被中断或步数用尽)session_id, reply_id, finished_reason, error解锁输入框;interrupted 插提示;exceed_max_iters 补一句"这一轮步数用完了";error 非空时显示错误

B.2 模型调用 ​

类型何时发出业务字段前端
MODEL_CALL_START每次调用模型前reply_id, model_name用于计时和显示模型名(模型名只有这里有)
MODEL_CALL_END模型返回完毕reply_id, input_tokens, output_tokens, cache_input_tokens, cache_creation_input_tokens, finished_reason累加 token 与费用;这是记账的唯一数据源

B.3 文本、思考、数据 ​

类型何时发出业务字段前端
TEXT_BLOCK_START一段正文开始reply_id, block_id建一个文本块
TEXT_BLOCK_DELTA正文增量reply_id, block_id, delta追加 delta,只改 textContent
TEXT_BLOCK_END一段正文结束reply_id, block_id, text可用 text 校准(防丢帧)
THINKING_BLOCK_START思考开始reply_id, block_id建一个默认折叠的思考块
THINKING_BLOCK_DELTA思考增量reply_id, block_id, delta追加
THINKING_BLOCK_END思考结束reply_id, block_id—
DATA_BLOCK_START多模态数据开始reply_id, block_id, media_type, name按 media_type 建图片/音频容器
DATA_BLOCK_DELTA数据增量reply_id, block_id, media_type, data, url追加或直接用 url
DATA_BLOCK_END数据结束reply_id, block_id—

B.4 工具 ​

类型何时发出业务字段前端
TOOL_CALL_START模型决定调用工具reply_id, tool_call_id, tool_call_name建工具卡片。这里没有参数
TOOL_CALL_DELTA参数增量reply_id, tool_call_id, delta累加字符串,拼完再 JSON.parse
TOOL_CALL_END参数发完reply_id, tool_call_id此时才可以解析参数
TOOL_RESULT_START工具开始执行reply_id, tool_call_id, tool_call_name标记运行中。确认续跑时只有这个,没有 TOOL_CALL_START
TOOL_RESULT_TEXT_DELTA工具返回正文增量reply_id, tool_call_id, delta追加到卡片的输出区
TOOL_RESULT_DATA_DELTA工具返回多模态数据reply_id, tool_call_id, block_id, media_type, data, url按类型渲染
TOOL_RESULT_END工具执行完毕reply_id, tool_call_id, statestate == "error" 时卡片变红,流程继续

B.5 交互与异常 ​

类型何时发出业务字段前端
REQUIRE_USER_CONFIRM权限引擎要人拍板reply_id, tool_calls弹确认框;tool_calls[i].input 是 JSON 字符串
REQUIRE_EXTERNAL_EXECUTION工具要交给外部系统执行reply_id, tool_calls弹"由外部执行"面板
USER_CONFIRM_RESULT用户回执(入参,一般不转发)reply_id, confirm_results—
USER_INTERRUPT用户中断(入参,一般不转发)reply_id—
EXTERNAL_EXECUTION_RESULT外部执行结果(入参)reply_id, execution_results—
EXCEED_MAX_ITERS达到 ReAct 迭代上限reply_id, name提示"这一轮步数用完了"
HINT_BLOCK注入块的提示信息reply_id, block_id, source, hint调试面板里显示,不要放进正文
CUSTOM用户自定义name, value按自己的约定处理

B.6 三个最容易记错的点 ​

TOOL_CALL_START 没有 input。 参数在后续的 TOOL_CALL_DELTA 里,一字一句拼出来。工具卡片上那个"参数是流式的"的经验,就来自这个字段缺失。

MODEL_CALL_END 没有 model_name。 它在配对的 MODEL_CALL_START 上,记账时要提前存。

REPLY_END 有四种结束方式。 finished_reason 是 ReplyFinishedReason 枚举,取值 completed / interrupted / exceed_max_iters / error;另一个 error 字段(ErrorInfo)在失败时非空。两个要分开看——exceed_max_iters 时 error 是空的,光看它判不出来。前端四种都要给用户不同的说法:示例代码里 REPLY_END 一到就无条件解锁输入框,所以不会锁死,但如果只认 completed,用户就看不出"被中断"和"步数用尽"的区别。

查字段比读文档快:

bash
python -c "from agentscope.event import ToolCallStartEvent as E; print(E.model_fields.keys())"

附录 C 章节索引 ​

C.1 章 × 代码 × 新概念 ​

章代码目录新增概念关键 API
1code/ch01Agent = Model + Loop + ToolsAgent, reply_stream
2code/ch02消息与内容块UserMsg, TextBlock, EventType
3code/ch03工具与工具箱ToolBase, Toolkit, input_schema
4code/ch04ReAct 循环ReActConfig, max_iters
5code/ch05工作区与路径边界LocalWorkspace, 先读后写
6code/ch06自定义工具ToolBase.call, 错误返回
7code/ch07权限系统PermissionMode, PermissionRule, PermissionEngine
8code/ch08计划模式与上下文压缩ContextConfig, Task, todo
9code/ch09状态持久化AgentState, model_dump_json
10code/ch10中间件与结构化输出MiddlewareBase, structured_schema
11code/ch11后端与事件流FastAPI, StreamingResponse, SSE 帧
12code/ch12前端渲染reducer, ReadableStream, 增量拼接
13code/ch13确认、提问、中断UserConfirmResultEvent, UserInterruptEvent
14code/ch14记账与可观测MODEL_CALL_END 字段, JSONL
15code/ch15交付与自检create_app, GoalPipeline, MCPClient

C.2 想改某个东西,去哪个文件 ​

你想改去这里
换模型 / 换服务商build_model(),每章一份,改一处
加一个工具code/ch06/main.py 里按 ToolBase 三件套写:name / description / input_schema + call()
改权限判定code/ch07/main.py 的规则配置
改上下文压缩阈值ContextConfig(trigger_ratio=...)
加一个接口code/ch13/server.py,注意插在静态目录之前
改界面布局code/ch14/index.html + style.css
改事件渲染code/ch14/app.js 的 handle() switch
加一项统计code/ch14/telemetry.py 的 Recorder.observe

C.3 三种运行模式 ​

模式命令需要 key用途
纯前端双击 code/ch12/mock.html否调界面、截图、给人看效果
假模型MOCK=1 python main.py否调后端、调事件流、跑自检
真模型填好 .env 后 python main.py是实际使用

第三种模式用的是 OpenAI 兼容端点上的 deepseek-v4.1-flash,实测下来一轮普通问答约 0.3 到 20 秒(思考型模型波动大),自检脚本六项全过。前两种模式也保留着——它们的价值不只是省下几块钱:把假模型这条路修好,整个开发过程就不再依赖外部服务。 第 13 章的 fake_model.py 和第 12 章的 mock_data.js 值得你在自己的项目里也留一份。

教程正文采用 CC BY-NC-SA 4.0 许可,code/ 下示例代码可自由使用