DeepAgent 实战:SSE 已上线,长期记忆还是半成品

文章来源声明: 原文作者:海边捡石子; 来源站点:掘金; 原文链接:https://juejin.cn/post/7686462220679020594; 本文基于上述来源整理/加工,觅优补充点评,仅供技术学习交流。版权归原作者所有。
觅优短评

对想落地 Agent 工作台的团队,本文提供了 SSE 事件协议、技能双轨和 Reasoner 兼容等实战细节,可快速避坑;但长期记忆尚未持久化,生产使用需自行增强。

很多人第一次把 LangChain / DeepAgent 接到自己的产品里,会卡在同一处:模型能聊,但规划、工具、文件、会话管理散落在脚本里,同事没法点、没法管。公开仓库 [liuyanqun0815/langchain-deep-agent](https://link.juejin.cn?target=https%3A%2F%2Fgithub.com%2Fliuyanqun0815%2Flangchain-deep-agent "https://github.com/liuyanqun0815/langchain-deep-agent") 做的是另一件事——用 Vue 3 工作台把 DeepAgent 包成可运行工程,并且把「已经写完的」「只留了壳的」「设计里还没做的」分得很开。

本文只依据该仓库已公开的 README、docs/、前后端源码,不含本地私有文件或真实密钥。文中会明确标出:当前已实现、半成品/预留、未来计划;设计文档与代码不一致的地方也会直说。

封面:智能助手对接任务规划、技能、长期记忆与工具


一、要解决什么,以及我们选了什么

痛点

  • 单页调 Chat Completions:没有规划、工具、文件系统的统一壳。
  • 纯脚本跑 Agent:难给非开发同事试用,技能开关也难管。
  • 只堆 Prompt:复杂任务靠「多写几句」撑不住。

选型(仓库现状)

选择公开证据
前端Vue 3 + Vite + Naive UI`frontend/`,路由 `ChatView` / `SkillsView` / `ModelsView`
后端FastAPI + SQLModel + SQLite`backend/main.py`、`app/db/`
AgentLangChain deepagents `create_deep_agent``backend/app/agent/factory.py`
部署Docker Compose;本地用 `run_dev.py``docker-compose.yml`、`backend/run_dev.py`

核心务实点有两个:一是产品化外壳(会话 / 消息 / 技能进 SQLite,界面可点);二是双轨扩展(skill.yaml Tools + SKILL.md Skills 同目录共存,一起喂给 DeepAgent)。


二、整体架构

三层架构示意:Vue 前端 → FastAPI 接口层 → 数据库与技能注册

graph TB
  subgraph FE[前端 Vue 3 SPA]
    CV[ChatView 聊天]
    SV[SkillsView 技能]
    MV[ModelsView 模型]
  end

  subgraph API[FastAPI 接口层]
    R1["/api/sessions"]
    R2["/api/skills"]
    R3["/api/models"]
    R4["/api/agent/config"]
    AS[agent_service]
  end

  subgraph AG[Agent 运行时]
    FAC[factory.create_deep_agent]
    REG[skill_registry Tools]
    BE[CompositeBackend]
    CP[MemorySaver checkpointer]
    ST[InMemoryStore]
  end

  subgraph DATA[持久化与磁盘]
    DB[(SQLite sessions/messages/skills)]
    SK["backend/skills/"]
    UP["data/uploads/"]
  end

  CV --> R1
  SV --> R2
  MV --> R3
  R1 --> AS
  AS --> FAC
  REG --> FAC
  FAC --> BE
  FAC --> CP
  FAC --> ST
  AS --> DB
  SK --> REG
  SK --> BE
  UP --> BE

职责边界和目录是对齐的:routers/ 很薄,services/ 做业务编排,真正碰 DeepAgent 的只有 app/agent/。启动时 lifespan 会按固定顺序热身——这很重要,后面会细说。


三、一次请求的真实路径(含 SSE)

请求链路:用户发消息 → FastAPI → Agent 服务 → DeepAgent → 写入并回复

仓库里聊天不止「一次性 invoke」。sessions 路由已经挂了流式端点:POST /{session_id}/messages/stream 与带上传的 .../upload/stream,返回 StreamingResponse(..., media_type="text/event-stream"),内部调 agent_service.chat_stream / chat_stream_with_files

sequenceDiagram
  participant U as 用户
  participant FE as Vue 前端
  participant API as FastAPI sessions
  participant Svc as agent_service
  participant AG as DeepAgent
  participant DB as SQLite

  U->>FE: 输入消息(可附文件)
  FE->>API: POST .../messages/stream
  API->>Svc: chat_stream
  Svc->>DB: 写入 user 消息
  Svc-->>FE: SSE event=start
  Svc->>AG: agent.stream(messages, thread_id)
  loop 增量事件
    AG-->>Svc: AI/Tool 消息增量
    Svc-->>FE: inference_step / chunk
  end
  Svc->>DB: 写入 assistant(Reasoner 附 reasoning_content)
  Svc-->>FE: SSE event=end
  FE-->>U: 展示正文与推理框

启动热身(lifespan)

公开 backend/main.py

<span>@asynccontextmanager</span>
<span>async</span> <span>def</span> <span>lifespan</span>(<span>app: FastAPI</span>):
    init_db()
    settings.skills_root_dir.mkdir(parents=<span>True</span>, exist_ok=<span>True</span>)
    sync_skills_from_disk()
    skill_registry.reload_from_db()
    get_agent()
    <span>yield</span>

顺序含义很直白:先保证表存在 → 磁盘 skills 同步进 DB → 注册表按 DB 启用状态装 Tools → 预创建 Agent 单例。第一句聊天不会撞上冷启动拼装。

设计文档 vs 代码:SSE 这件事要诚实说

docs/TECHNICAL_DESIGN.md 第 9 节「风险与扩展」仍写着:

流式输出:若后续支持 SSE,需在 agent_service 中改为 yield……

但公开代码里 SSE 已经落地chat_stream 会推 startinference_stepchunkend(失败时还有 error)。这是典型的「设计文档落后于实现」。写文章或二次开发时,以 agent_service.py + sessions 路由为准,不要被第 9 节误导成「流式还没做」。


四、深挖 factory:DeepAgent 是怎么装起来的

公开文件 backend/app/agent/factory.py 是整仓最值得读的一块。

4.1 模型选择

默认走 DeepSeek(ChatDeepSeekdeepseek-chat);其它模型名尝试 init_chat_model,再 fallback 到 ChatOpenAI。温度、max_tokens 来自 config.settings

4.2 Tools + Skills 双通道注入

tools = skill_registry.get_tools()
skill_paths = _get_skill_paths_for_agent()
create_kw = <span>dict</span>(
    model=model,
    tools=tools,
    store=_store,
    backend=_make_backend,
    checkpointer=_checkpointer,
)
<span>if</span> skill_paths:
    create_kw[<span>"skills"</span>] = [<span>"/skills/"</span>]
_agent_graph = create_deep_agent(**create_kw)

要点:

  1. Tools:来自注册表(磁盘 skill.yaml 同步到 DB 后,按启用状态转成 LangChain Tool)。
  2. Skills:扫描 skills_root 下含 SKILL.md 的子目录;只要存在至少一个,就传 skills=["/skills/"]——这是后端虚拟根路径,不是宿主机绝对路径。
  3. store / checkpointerInMemoryStore + MemorySaver,进程内有效;重启即丢(长期记忆增强另说)。
  4. rebuild_agent():技能或配置变更后先 reload_from_db() 再重建图,避免「UI 关了技能,Agent 还握着旧工具表」。

4.3 CompositeBackend:四块虚拟盘 + 本地 Shell

_make_backend 组装路由:

虚拟路径后端用途
`/memories/``StoreBackend(runtime)`DeepAgent 内置长期记忆读写
`/skills/``FilesystemBackend(skills_root)`官方 SKILL.md 与资源
`/uploads/``FilesystemBackend(data/uploads)`用户上传文件
默认`LocalShellBackend(backend 根目录)``execute` 跑本地 shell

代码注释写得很清楚:LocalShellBackend 仅用于本地开发;生产应换沙箱(如 BaseSandbox)或 HITL 审批。另外给 shell 环境塞了 PYTHONUNBUFFERED=1,避免 Windows 上 Python 脚本 stdout 缓冲导致 execute 抓不到 print。

开发侧还有一个救命细节:run_dev.py 用 uvicorn --reload,并 reload_excludesskills/**。否则从本机路径添加技能、复制文件时,服务会误重启,聊天中途直接断。README 也专门提醒了 PowerShell 展开通配符的坑。


五、深挖 agent_service:invoke、SSE、Reasoner、上传路径

5.1 非流式 chat()

流程:读历史 → 转 LangChain 消息 → 写 user → agent.invoke → 抽推理步骤 → 写 assistant。thread_id 不是裸 session_id,而是:

<span>def</span> <span>_thread_id</span>(<span>session_id: <span>int</span></span>) -> <span>str</span>:
    model_key = (settings.default_model <span>or</span> <span>""</span>).strip().lower()
    <span>return</span> <span>f"<span>{session_id}</span>:<span>{model_key}</span>"</span>

原因写在注释里:切到 deepseek-reasoner 时,旧线程状态里可能缺 reasoning_content,DeepSeek 会 400。按「会话 + 模型」隔离 checkpointer,避免状态污染。这是读源码才能看到的工程判断,不是 README 口号。

5.2 流式 chat_stream() 事件协议

对前端来说,协议比实现细节更重要:

event含义
`start`开始,可显示「正在思考」
`inference_step`思考 / 工具调用 / 工具结果(去重后推送)
`chunk`最终答案增量;`tool_content` 区分工具回显与最终结果
`end`落库完成,带 `message_id`、`inference_steps`
`error`异常信息

实现上走 agent.stream(..., stream_mode="messages", subgraphs=True),兼容带 namespace 与不带 subgraphs 的两种事件元组形态。有 reasoningtool_calls 的 content 进推理框,不抢最终答案 chunk;干净的 content 才按 delta 推 chunk

5.3 DeepSeek-Reasoner

  • 历史 assistant:若当前是 reasoner,从 metadata_.reasoning_content 注入 AIMessage.additional_kwargs
  • 本轮回复:content 入库为最终回答;reasoner 把 reasoning_content 写入 message metadata,供下一轮合规。

这不是「展示好看」的装饰,而是 API 契约:多轮 + 工具调用时,缺少 reasoning 字段会直接报错。

5.4 文件上传如何喂给 Agent

_augment_message_with_files 把「文件名 → 绝对物理路径」拼进 user_message,并附使用说明:纯文本用 read_file + 绝对路径;PDF/Word 优先走技能;execute 也用绝对路径,不要用虚拟路径。流式与非流式分别走 chat_stream_with_files / chat_with_files,底层复用同一套注入逻辑。

上传落盘路径形如 data/uploads/session_{id}/...,同时 CompositeBackend 把 uploads 挂到 /uploads/,两边都能摸到文件。


六、双轨 Skills:电钻和说明书

工具 Tools 与技能 Skills 双轨汇入同一 DeepAgent

flowchart LR
  subgraph Disk[backend/skills 目录]
    Y[skill.yaml 子目录]
    M[SKILL.md 子目录]
  end

  Y -->|sync + registry| T[tools 列表]
  M -->|扫描存在则启用| S[&#34;skills=['/skills/']&#34;]
  T --> DA[create_deep_agent]
  S --> DA
  DA --> Out[可调用函数 + 渐进式说明书]

Tools(本项目)Skills(官方 SKILL.md)
清单`skill.yaml` / yml / json`SKILL.md`(YAML frontmatter + 正文)
作用注册可调用函数提供说明与步骤,按需读取
注入`tools=[...]``skills=["/skills/"]`
示例可按规范自建 python/http 工具仓库已带 `backend/skills/pdf/`、`doc/`

同一 backend/skills/ 下两者可共存、同时生效。官方 Skills 遵循 Agent Skills 规范:启动只读 frontmatter,任务匹配后再加载全文(渐进式披露)。docs/SKILLS.md 还说明了从本地子目录或 GitHub skills/* 导入、以及启用/禁用走 DB 的流程。


七、状态表:已实现 / 半成品 / 未来

这一节是全文的「防踩坑地图」。只写公开文件能证实的事。

当前已实现

能力证据
Vue3 聊天 / 技能 / 模型三页`frontend/src/router` → ChatView、SkillsView、ModelsView
sessions / skills / models / agent\_config API`backend/app/routers/`
lifespan:init\_db → sync\_skills → reload → get\_agent`main.py`
factory:tools + skills + CompositeBackend + checkpointer`factory.py`
`chat` invoke + `chat_stream` SSE(start/inference\_step/chunk/end)`agent_service.py`、`sessions.py`
DeepSeek-Reasoner 的 reasoning\_content 历史注入与落库`agent_service.py`
thread\_id 按 session + model 隔离`_thread_id`
上传文件路径注入 + upload/stream`chat_*_with_files`、sessions 路由
双轨 skills;pdf/doc 示例目录`backend/skills/`、`docs/SKILLS.md`
Docker Compose;开发 reload 排除 skills`docker-compose.yml`、`run_dev.py`

还在考虑或半成品

现状(公开代码)
`memory.py`几乎全是注释:说明长期记忆目前靠 DeepAgent 内置 `/memories/` + StoreBackend;模块本身预留给「以后从 DB/向量库检索再注入」
`context.py`仅 `get_session_context_dir(session_id)` 辅助函数,维护 `session_{id}` 目录路径
设计文档第 9 节 SSE文档仍写「若后续支持」;**代码已实现**——文档滞后
向量库记忆设计文档 4.4 写明第一版可用 SQLite+文本或简单 embedding,独立向量库为后续选项;**未看到完整向量检索实现**
Store / Checkpointer当前为进程内 `InMemoryStore` / `MemorySaver`,不是跨进程持久记忆方案

未来计划(设计提及,勿当成已做)

  • 多用户鉴权,Session/Message 增加 user_id 隔离(TECHNICAL_DESIGN §9)。
  • 生产环境用沙箱替代 LocalShellBackend(factory 注释 + 设计安全边界)。
  • 更丰富的长期记忆:从 DB / 向量库检索片段并注入上下文(memory.py 注释与设计 4.4)。

八、本地复现(无真实密钥)

Docker Compose(推荐)

git <span>clone</span> https://github.com/liuyanqun0815/langchain-deep-agent.git
<span>cd</span> langchain-deep-agent
<span>cp</span> .env.example .<span>env</span>
<span># 编辑 .env,填入你自己的 DEEPSEEK_API_KEY 等;不要提交真实密钥</span>
docker-compose up -d
docker-compose logs -f

按 README:前端 http://localhost ,后端 API http://localhost:8000

本地开发

<span># 后端</span>
<span>cd</span> backend
pip install -r requirements.txt
python run_dev.py   <span># 已排除 skills/ 热重载</span>

<span># 前端</span>
<span>cd</span> frontend
npm install
npm run dev         <span># /api 代理到 127.0.0.1:8000</span>

冒烟

curl http://127.0.0.1:8000/api/health
<span># 预期 {"status":"ok"}</span>

常见问题:模型 Key / 默认模型名不对;加技能后服务狂重启(没用 run_dev.py);SKILL.md 不在子目录根下导致 skills 通道未启用;PowerShell 展开 skills/* 通配符。

环境变量示例(占位符):

DEEPSEEK_API_KEY=sk-xxxxxxxx
LANGCHAIN_DEBUG=0
# LANGSMITH_API_KEY=...
# DATABASE_URL=sqlite:///./data/agent_app.db


九、读完源码后的判断

这仓的价值不在「又包了一层 Chat API」,而在三件事叠在一起:

  1. 产品壳完整:会话、技能开关、模型配置都有 API 和 Vue 页,能给别人点。
  2. Agent 装配诚实:factory 把 tools、SKILL.md、虚拟文件系统、shell、store、checkpointer 一次接清;Reasoner 与 thread 隔离这种坑也处理了。
  3. 边界写得清楚:Shell 仅限本地、记忆模块先留壳、设计里的鉴权/向量库还没当做成品卖——读代码时不会被 README 吹过头。

不足也很实在:长期记忆仍是 DeepAgent 文件系统路径为主,进程内 Store;SSE 文档未同步;上生产前必须换掉 LocalShell,并补鉴权与隔离。

若你也在把 DeepAgent 接到办公助手或内部工作台,直接看源码比看概念文有效:

github.com/liuyanqun08…

(插图为配套示意图;发布掘金时可与 images/ 下 cover、architecture、request-flow、skills-dual 一并上传。Mermaid 在支持该语法的编辑器中可直接渲染。)