|
|
1 kuukausi sitten | |
|---|---|---|
| agents | 1 kuukausi sitten | |
| api | 1 kuukausi sitten | |
| billing | 1 kuukausi sitten | |
| db | 1 kuukausi sitten | |
| obs | 1 kuukausi sitten | |
| scheduler | 1 kuukausi sitten | |
| scripts | 1 kuukausi sitten | |
| skills | 1 kuukausi sitten | |
| static | 1 kuukausi sitten | |
| tests | 1 kuukausi sitten | |
| tools | 1 kuukausi sitten | |
| .env.example | 1 kuukausi sitten | |
| .gitignore | 1 kuukausi sitten | |
| AGENTS.md | 1 kuukausi sitten | |
| README.md | 1 kuukausi sitten | |
| config.py | 1 kuukausi sitten | |
| main.py | 1 kuukausi sitten | |
| pytest.ini | 1 kuukausi sitten | |
| requirements.txt | 1 kuukausi sitten |
基于 deepagents 的深度智能体服务骨架。
主入口是 POST /api/chat,返回 SSE 流。
对外契约与 1.5 完全兼容:POST /api/chat 的 multipart 入参、SSE 帧格式、
错误码语义都逐字对齐,1.5 的 static/index.html 不改一行即可连上。
对内实现全部重写:1.5 的业务工具、业务技能、MCP 工具池不在本仓库范围内。 骨架提供的是结构与纪律,附 4 个演示工具与 1 个示例技能示范扩展方式。
# 1) 依赖
pip install -r requirements.txt
# 2) 配置
cp .env.example .env
# 至少填 OPENAI_API_KEY 与 OPENAI_BASE_URL
# 想用真实 MySQL:把 DB_DIALECT 改成 mysql 并填 DB_* 连接信息
# 3) 跑起来
python main.py
# 或 bash scripts/start_server.sh start (生产,带 PID 文件与优雅停止)
# 4) 打开测试页
# http://127.0.0.1:8080
开箱配置走 SQLite 开发档(DB_DIALECT=sqlite),无需任何数据库服务即可
跑起来。生产/联调改 DB_DIALECT=mysql 并执行 db/mysql_schema.sql。
8080(1.5 是 8070,避开冲突)。改 PORT 即可。
主入口与 1.5 一致,额外新增了若干辅助接口。
| 方法 | 路径 | 说明 |
|---|---|---|
| POST | /api/chat |
主入口,SSE 流式 |
| POST | /api/chat/only |
同管线,仅计费类型不同(chat_only) |
| POST | /api/chat/resume |
恢复被中断挂起的对话 |
| GET | /api/chat/interrupt |
查挂起的中断(刷新页面补渲染卡片);有则 200,无则 204 |
| GET | /api/chat/history/{session_id} |
会话历史 |
POST /api/chat 请求体是 multipart/form-data:
| 字段 | 必填 | 说明 |
|---|---|---|
message |
是 | 用户消息,支持 #skill:<name> 与 #session:<uuid> 前缀 |
session_id |
否 | 缺省则新建会话 |
thread_id |
否 | 必须等于 session_id,否则 404 |
mode |
否 | plan(先出计划待批)或 full(直接执行) |
file |
否 | 附件,落到 /workspace/uploads/ |
user_name |
否 | 归属用户,缺省 default(2.0 新增,替代 1.5 的鉴权依赖) |
响应是 text/event-stream,头含 X-Session-Id 与 X-Trace-Id。
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /api/sessions |
会话列表(置顶优先) |
| GET | /api/sessions/search?q= |
标题模糊搜索 |
| DELETE | /api/sessions/{id} |
删除会话(同时清图状态) |
| DELETE | /api/messages/{id} |
删除单条消息 |
| GET/PUT | /api/sessions/{id}/mode |
权限模式 |
| PUT | /api/sessions/{id}/pin · /title |
置顶 · 重命名 |
| GET | /api/sessions/{id}/context |
上下文用量明细与压缩存档索引 |
| GET | /api/usage/today |
当日用量与额度 |
| GET | /api/usage/recent |
最近用量流水 |
| PUT | /api/usage/quota |
调整每日额度(0 = 不限) |
| GET | /api/usage/pricing |
模型单价表 |
| GET | /api/preferences · PUT · DELETE |
用户偏好(每轮自动注入) |
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /api/model |
模型配置与 agent 组装摘要 |
| GET | /api/model/tools |
工具清单(按分组) |
| POST | /api/model/thinking |
切换思考级别(触发 agent 重建) |
| POST | /api/model/reload |
强制重建 agent |
| 方法 | 路径 | 说明 |
|---|---|---|
| GET/POST | /api/schedules |
列表 / 新建 |
| GET/PUT/DELETE | /api/schedules/{id} |
详情 / 更新 / 删除 |
| POST | /api/schedules/{id}/pause · /resume · /run |
暂停 / 恢复 / 立即执行 |
| GET | /api/schedules/{id}/runs |
执行记录 |
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /health |
逐子系统健康状态 |
| GET | /api/health/billing |
计费自检(单价是否覆盖当前模型) |
| GET | / |
重定向到测试页 |
| GET | /static/index.html |
极简测试页 |
14 种业务事件 + 1 种通用错误。字段名是从 1.5 前端逐行反查出来的, 不是猜的。
| 事件 | 前端读取的字段 |
|---|---|
agent_start |
agent · cn_agent |
thinking |
message · cn_agent |
thinking_token |
content |
executing |
message · cn_agent · tools[] · cn_tools[] |
tool_call |
tool · cn_tool · args · agent_id |
tool_result |
tool · cn_tool · content · error · agent_id |
token |
content |
updated_todo_list |
todos |
progress |
message |
interrupt |
tool_approval{tool,args,reason,side_effect} 或 ask_user{questions[]} 或 message |
citations |
items[] · model_only |
agent_done |
agent · cn_agent · duration_ms · progress · context_tokens |
agent_error |
agent · cn_agent · message · duration_ms |
done |
duration_ms · usage{} |
error |
message · code · retry_after |
帧格式:data: {json}\n\n,每帧恒带 session_id,绑定 trace 时带 trace_id。
HTTP 错误语义:额度超限 429;并发超限 429(带 retry_after);上传 400/413;
会话不存在或越权 404;恢复时无挂起中断 409。
契约的唯一出口是 api/sse_events.py,其他任何地方都不得手拼 data:
字符串。改字段必须同步改 tests/test_sse_contract.py。
这是 2.0 与 1.5 的主要差异所在。
| 做法 | |
|---|---|
| 1.5 | 从子图命名空间字符串 tools:<X> 反查 subagent_type;id 被上下文压缩裁掉后反查落空,子智能体身份丢失 |
| 2.0 | 收到 task 调用的那一刻就建 tool_call_id → {类型, 描述} 表,之后只按 id 查表;表由跟踪器持有,不受历史窗口影响 |
见 api/subagent_tracker.py。
| 做法 | |
|---|---|
| 1.5 | tiktoken 本地估算优先,为此专门开了 fix/context-usage-accounting 分支修口径 |
| 2.0 | 真实 usage 优先(usage_metadata),provider 不给才回落估算并标 usage_source=estimate |
两个量刻意分开:input_tokens 累加值是计费口径,context_tokens(最近一次
调用的 input)是当前上下文占用。
见 api/context_meter.py。
| 做法 | |
|---|---|
| 1.5 | 自研 auto_compact.py,默认关闭,未被信任到敢开 |
| 2.0 | 直接用 deepagents 的 SummarizationMiddleware:85% 窗口触发,把被淘汰的历史追加落盘到 /conversation_history/{thread_id}.md 并把路径嵌进摘要,agent 能自己读回来 |
调节旋钮是 CONTEXT_LIMIT(声明窗口),不是阈值比例——阈值由框架固定为
85%。压缩存档位置可在 GET /api/sessions/{id}/context 的 archives 里查。
| 做法 | |
|---|---|
| 1.5 | 靠提示词让模型自觉在正文写引用,再用启发式规则去正文里刮 |
| 2.0 | 工具侧结构化产出:工具返回 sourced(data, sources),SSE 层从 ToolMessage 抽取、聚合、去重 |
模型漏写和编造这两种失败模式,在读工具返回值的路径上根本不存在。
见 api/source_tracker.py 与 tools/registry.py。
# tools/my_tools.py
from tools.registry import register, source, sourced
@register(group="我的分组", label="中文显示名", summary="一句话说明")
def my_tool(query: str, limit: int = 5) -> dict:
"""做某事。
Args:
query: 查询词。
limit: 最多返回几条。
Returns:
含 data 与 sources 的结构;sources 会被渲染成引用标注。
"""
return sourced(
data=[...],
sources=[source("标题", "https://...", "摘要", score=0.9)],
)
然后把模块名加进 tools/registry.py 的 _BUILTIN_MODULES。工具名全局唯一,
重名会在启动时抛错。
docstring 是给模型看的:第一行说清做什么,正文说清何时用、参数含义、 返回什么。模型选错工具多半是因为 docstring 写得含糊。
在 skills/<name>/SKILL.md 写带 frontmatter 的操作手册:
---
name: my-skill
description: 什么时候该用这个技能(这段会被模型看到,要写具体)
---
# 标题
## 步骤
1. ...
agent 启动时只读 frontmatter,按需才 read_file 读全文(渐进披露)。
用户用 #skill:my-skill 前缀显式指定。
改 agents/agent_factory.py 的 _build_subagents()。注意 tools 必须是
BaseTool 列表(用 registry.get_tools_by_name(...)),system_prompt
不继承父级。
全部配置项见 .env.example,每项都有默认值。几个容易搞错的:
| 变量 | 说明 |
|---|---|
DB_DIALECT |
mysql(生产)/ sqlite(本地开发与测试,无需数据库服务) |
CONTEXT_LIMIT |
声明的上下文窗口,压缩的唯一调节旋钮。0 = 用模型登记值 |
CTX_COMPACT_ENABLED |
关掉会同时失去历史落盘能力,长会话会超窗报错 |
CHECKPOINTER |
sqlite(默认,落盘)/ memory(重启丢图状态) |
HITL_TOOLS |
追加需要人工审批的工具名,逗号分隔。如 calculate 便于自测中断闭环 |
BILLING_ALLOW_UNPRICED |
模型未定价时是否按 0 计费。默认 0 = 直接报错(fail-closed) |
DEEP_AGENT_SKIP_DOTENV |
测试/CI 用:完全跳过 .env |
TFAGENTS_DAILY_TOKEN_LIMIT |
每用户每日 token 限额,0 = 不限 |
配置冲突会被大声报出来:若机器上有同名进程环境变量且值与 .env 不同,
启动日志会打 WARNING,/health 的 config_conflicts 也能看到。项目 .env
优先——这条反直觉但必要,原因见 config.py 的模块 docstring。
# 离线单元 + SSE 契约测试(不需要密钥,不需要数据库服务)
python -m pytest
# 结构自检:导入、建表、agent 编译、工具注册
python scripts/selfcheck.py
# HTTP 层端到端(需服务已启动 + 可用密钥)
python scripts/http_e2e.py
http_e2e.py 覆盖 49 项断言:SSE 帧格式与响应头、落库与顺序、错误语义
(404/400/409)、中断/恢复闭环、定时任务的手动触发与 cron 校验。
main.py FastAPI 入口(lifespan / 健康检查 / 静态挂载)
config.py 配置唯一入口(.env 读取与冲突检测)
api/ chat · sse_core · session · model · usage · schedule · concurrency
sse_events.py ★ SSE 契约唯一事实源
sse_core.py ★ 流式内核(读流 → 派事件 → 收尾)
subagent_tracker.py 子智能体生命周期跟踪
context_meter.py 上下文计量(真实 usage 优先)
source_tracker.py 来源标注抽取与聚合
agents/ agent_factory · agent_registry · model_factory · prompts · middleware · agent_memory
tools/ registry(工具唯一事实源)· demo_tools(示例工具)
skills/demo-skill/ 示例技能
db/ db_conn(双方言)· mysql_schema.sql · schema_sqlite.sql · 三个 store
billing/ pricing · guard(预检)· meter(入账)
scheduler/ scheduler(APScheduler)· runner(独立会话执行)
obs/ log_setup · tracing(trace_id)
static/index.html 极简测试页
tests/ pytest(141 项,离线可跑)
scripts/ selfcheck.py · http_e2e.py · start_server.sh
标 ★ 的两个文件是核心,改动前先读 AGENTS.md。
当前只适合内网或开发环境,主要因为:
user_name 由客户端自报,可伪造。计费与会话归属都建立在
这个值上。接真实鉴权只需替换 api/chat_routes._resolve_user。其余有意留空的部分(无业务工具/技能、无 MCP、标题用截断、remember 未实现)
见 AGENTS.md §六。
工程纪律、踩过的 11 个坑、分层依赖规则见 AGENTS.md。