146 KiB
DeerFlow Backend
公文助手与网页报告校验优化(2026-10-07)
公文助手新增预置 run_gongwen.py deliver / verify-file,一次交付调用完成
生成或修订、正文复验、已确认发文要素注入和 DOCX 结构检查。网关复用本次
完整校验结果,旧版自定义技能缺少结果时保留一次检查回退,减少模型临时生成
Python 脚本与重复检查。结构检查不替代事实核实和真实分页检查。
内置种子升级至 v3,更新后重启后端同步智能体与 public 技能;custom 同名技能
仍由现场维护。前端公文工作台的发文要素指令使用同一交付命令。
网页报告写入和校验时自动补齐深色组件及图表文字颜色。校验返回自动修复项、
完整错误列表和 repair_checklist,要求集中完成局部修复后再校验,避免逐条
循环和整页重写。遗留小字号声明不再单独拦截。
沙箱流式展示与切换保护(2026-10-07)
普通问答和智能体问答的 write_file 没有有效路径时,不创建占位制品来打开
空沙箱,而在步骤条内用绿色小字流式展示正文;后续完整路径到达后恢复正常
文件预览,正常文件的自动打开与沙箱实时展示保留。
当前预览文件正在生成或全文改写时,沙箱阻止切换到其他文件。用户操作时提示: 「当前文件正在生成,请稍等片刻,完成后就可以切换查看其他文件。」 自动切换静默跳过,生成完成、停止或离开会话后解除保护。
普通知识库默认详情页面:管理员可在知识库顶部选择“本系统 Wiki 索引”或“嵌入 WeKnora”,对应 ordinary_knowledge_landing(默认 wiki)。Wiki 库首次进入默认展示本系统索引,右上角“打开 WeKnora”切换到内嵌管理页;内嵌页可返回索引。非 Wiki 库仍进入 WeKnora 文档页。设置通过 /api/system-settings/ordinary-knowledge-landing 保存,仅管理员可修改;指定文章、文档、图谱的直达链接优先于默认设置。
助手知识库可独立于 WeKnora 使用:管理员进入助手知识库,选择或创建库后点击“上传文件生成 Wiki”。支持 Markdown/TXT/CSV、PDF、Word、Excel、PowerPoint(单文件 100 MB,解析正文上限 400 万字符)。文件先写入运行目录 assistant-knowledge/files/{job_id},后台分段调用已配置的语言模型生成分类 Wiki、实体关系,再通过 DeerFlow 编码器对正文分段向量化,保存到助手库并汇入知识梳理总库。无需配置 WeKnora URL;配置后仍可直接上传,也保留 WeKnora 数据包导入。文件处理沿用管理员写入权限。日常界面默认采用 knowledge_base_product_version=v1,普通用户只展示普通知识库;管理员始终可管理助手知识库,并可在知识库顶部设置中切换到 v2,向普通用户开放助手知识库入口、选择器和关联跳转。该开关是可恢复的产品展示开关:隐藏时日常问答的新运行不带助手库选择,但不会删除已有助手库或历史绑定;助手知识的既有读/搜索接口契约不因该开关改变。
导入记录显示解析、Wiki 生成和向量化阶段;失败或服务中断后可“重试 / 恢复处理”,复用已落盘的 Wiki 和相同模型指纹的向量。离线编码器须预先放置模型并在管理员设置中配置;模型或编码器不可用会明确失败,不把摘要冒充 Wiki 或虚构向量。扫描件是否可解析取决于部署的文档解析/OCR 能力。
DeerFlow is a LangGraph-based AI super agent with sandbox execution, persistent memory, and extensible tool integration. The backend enables AI agents to execute code, browse the web, manage files, delegate tasks to subagents, and retain context across conversations - all in isolated, per-thread environments.
The dedicated wx chat entry (#/page/workspace/wx/chats/new) is fixed to the
deep-blue presentation and has no global header or page sidebar. Its retained
workspace panel keeps its original recommendation logic until wx selects an
Agent Gallery category: 我的, a configured publish square such as
默认广场, or 舆情分析. The shared optional selection is stored in
system_settings.json → wx_common_agents; the wx account and
administrators may update or clear it through
/api/system-settings/wx-common-agents.
Architecture
┌──────────────────────────────────────┐
│ Nginx (Port 2026) │
│ Unified reverse proxy │
└───────┬──────────────────┬───────────┘
│ │
/api/langgraph/* │ │ /api/* (other)
▼ ▼
┌────────────────────┐ ┌────────────────────────┐
│ LangGraph Server │ │ Gateway API (8001) │
│ (Port 2024) │ │ FastAPI REST │
│ │ │ │
│ ┌────────────────┐ │ │ Models, MCP, Skills, │
│ │ Lead Agent │ │ │ Memory, Uploads, │
│ │ ┌──────────┐ │ │ │ Artifacts │
│ │ │Middleware│ │ │ └────────────────────────┘
│ │ │ Chain │ │ │
│ │ └──────────┘ │ │
│ │ ┌──────────┐ │ │
│ │ │ Tools │ │ │
│ │ └──────────┘ │ │
│ │ ┌──────────┐ │ │
│ │ │Subagents │ │ │
│ │ └──────────┘ │ │
│ └────────────────┘ │
└────────────────────┘
Request Routing (via Nginx):
/api/langgraph/*→ LangGraph Server - agent interactions, threads, streaming/api/*(other) → Gateway API - models, MCP, skills, memory, artifacts, uploads, thread-local cleanup/(non-API) → Frontend - Next.js web interface
Health probes are always unauthenticated, even when API authentication is enabled: GET|HEAD /health, /api/health, and /api/langgraph/health all return the Gateway health status without requiring a cookie or bearer token.
Core Components
项目空间
前端入口为 /page/workspace/projects。项目作为现有问答的分类容器,保留工作区左侧聊天记录。创建项目时选择问答、策略智能或方案生成,创建后不可修改模式;可设置默认智能体,并在新建每条项目对话时另选智能体。方案生成复用「工作流配置 · 报告结构」的结构库和现有写作研究管线;策略智能支持可配置 Lens、多视角独立分析、策略辩论与综合决策。
Gateway /api/analysis-projects 提供列表、创建、查询、更新及 /{id}/threads 新建和查询对话。项目与对话归属存储在 analysis_projects / analysis_project_threads,智能体、Lens、报告结构等扩展配置存储在 analysis_project_configs;这些表随数据库初始化自动创建,内存部署不跨重启保存。项目按登录用户隔离,模式和归属不接受客户端元数据覆盖。策略智能可在无知识库时基于用户条件推演,但必须标出假设与资料缺口。方案生成调用现有 deep_research_report 研究管线,使用项目选择的固定或弹性报告结构,复用方案画布和后续修改流程。
普通问答项目支持一期、二期跨会话记忆。该能力仅受当前 mode=qa 项目的记忆开关控制,并与 system_settings.json -> memory.enabled 管理的通用/个人问答记忆相互独立;因此通用记忆总开关关闭时,已启用的项目记忆仍会召回、注入和异步沉淀。成功回答结束后,Gateway 从服务端 checkpoint 投影最近一轮问答,经脱敏后写入可恢复的异步任务,再提取事实、决策、约束、偏好、术语、未决事项与会话结论。记忆严格按 (owner_user_id, project_id) 隔离,项目绑定以 analysis_project_threads 为唯一权威;项目问答会完全绕开通用用户/智能体记忆中间件和 Hindsight 工具,避免两个记忆域混用。
一期的 SQL 权威数据为 analysis_project_memory_states、analysis_project_memory_items、analysis_project_memory_jobs,模型已注册到统一 Base.metadata.create_all 启动建表流程(与 analysis_projects 一致,不需要新增 Alembic revision)。任务使用幂等键、租约、心跳、最大重试与写入围栏;暂停、清空、编辑或删除来源时会取消在途任务,过期 worker 不能把旧内容写回。消息编辑/删除先持久化记忆失效,再修改 checkpoint;重建失败时项目记忆保持 fail-closed。任务状态按项目内单调因果代次判断,不依赖 worker 完成时间或随机 UUID;同一重建批次写入持久化 seal 后才允许转为 ready。dispatcher 会租约认领并重放超过宽限期、无活动任务的 rebuilding/recovering 状态,因此进程在 checkpoint 修改与重建入队之间崩溃也可自行恢复。全量重建保留用户手工或固定条目,重新生成自动条目,并按 checkpoint 来源时间而不是 worker 完成顺序决定同一 logical key 的新旧,防止旧会话覆盖较新的纠正。
二期召回采用关键词与可选向量混合排序;向量只有模型和 fingerprint 一致时才参与,编码服务不可用或超时时自动降级为确定性关键词召回。注入内容经过条数、长度与 token budget 限制,并以转义 JSON 的非指令资料块进入系统提示,当前用户消息和当前会话的更新信息始终优先。项目设置和对话页可查看状态、快照、来源,手工新增、编辑、固定、删除、清空或从历史对话重建。管理接口为 GET /{id}/memory、PATCH /{id}/memory/settings、POST /{id}/memory/rebuild、DELETE /{id}/memory 及 /{id}/memory/items*。
方案生成的首份交付对用户呈现为简单两步:先像普通问答一样讨论并自然理解需求,系统会先用自然语言复述识别到的真实目标、面向对象、成功结果与边界,并随用户补充持续修正;仅在影响方案的关键信息缺失时少量追问。信息足够时给出简短、非表单式的「意图理解」,并在不可见的 HTML 注释中写入包含目标、受众、成功结果、范围、约束、交付物、验收标准及待确认问题的结构化快照。只有服务器从最新助手回复解析出字段完整、open_questions 为空的唯一快照和就绪标记,且本轮用户点击「结束问答并生成方案」时,才进入正式方案生成;该权威快照会随运行上下文交给报告生成。客户端传入的 solution_requirements_confirmed 或 solution_intent 会被覆盖,不能跳过闸门。已有 report.md 的对话仍可直接进入后续修改。
关键问题通过统一澄清卡呈现:普通方案需求第一步至少给出 1 个最关键的决策问题,每个问题提供 2–4 个可点击选项并保留「其他/我补充」入口,总量控制在 1–3 个,不再只输出一段无法继续的普通文本或未经选择便直接就绪。用户也可随时点击「直接生成方案」;Gateway 仅接受当前最后一条用户消息中的专用令牌,忽略客户端伪造的上下文字段,并复用最近的合法意图快照(没有则从完整对话推断),把缺失项明确标成暂定假设后直接生成。确认或强制生成轮必须实际调用研究管线并产出 /mnt/user-data/outputs/report.md,否则中间件会拒绝“已生成/已展示”式的虚假完成回复;本轮真实生成失败时明确提示失败并结束,不在后台自动循环重试,用户可点击「重新生成方案」发起新一轮。
方案项目的最终 report.md 不使用 [1]、【1】 等数字引用标记。研究管线仍可使用检索资料,但会在 solution 专属写作要求和落盘后处理两层保证正文无数字标记;参考来源保留为无编号项目列表。该规则仅影响方案项目,普通写作与深度研究保持原有编号引用行为。
项目线程会由服务端同步只读展示元数据 analysis_project_id、analysis_project_mode、analysis_project_name,供最近对话显示项目类型与所属项目并在项目改名后刷新;真正的项目归属仍只以 analysis_project_threads 绑定表为准,客户端不能借此移动线程。
跨会话记忆专项验证:python -m pytest tests/test_analysis_project_memory.py tests/test_analysis_project_memory_store.py tests/test_analysis_project_memory_runtime.py tests/test_analysis_project_memory_integration.py tests/test_analysis_projects.py -q。其余项目空间验证:python -m pytest tests/test_deep_research_report_tool.py tests/test_writing_mode_report_middleware.py -q。
Lead Agent
The single LangGraph agent (lead_agent) is the runtime entry point, created via make_lead_agent(config). It combines:
- Dynamic model selection with thinking and vision support
- Middleware chain for cross-cutting concerns (9 middlewares)
- Tool system with sandbox, MCP, community, and built-in tools
- Subagent delegation for parallel task execution
- System prompt with skills injection, memory context, and working directory guidance
普通问答采用严格的输出载体门禁:只有本次请求明确要求 HTML、网页、页面、看板、大屏或 .html 文件时,才允许读取网页生成技能、调用页面模板工具或创建 HTML;“画流程图”“画图表”“画地图”“画思维导图”“做可视化”本身都不构成 HTML 意图。普通流程图直接在消息中输出 Mermaid,普通图表沿用既有消息内方式,普通地图输出 map-json。只有本次请求明确要求“思维导图 / 脑图 / 知识导图”或点名 analysis-mindmap 时,才会输出专用 mindmap-json;普通分析、总结、提纲和结构化 JSON 不会自动生成导图。未闭合的 mindmap-json(兼容历史 mindmap 围栏)会原样作为代码块流式显示,闭合且本地校验通过后才懒加载横向分析树预览。导图必须是一棵含唯一根节点的受限层级树:目标 12–24 个节点、硬上限 36 个,最多 4 层(含中心主题),中心主题至多 6 个一级维度、每个后续节点至多 3 个子节点;不得把报告的每个数据、段落或引用逐条转成节点。点击父节点本身,或其连线起点处的+/−,即可展开/收起子节点,且连接线明确可见;全量弹窗的右上角含关闭与视图工具、右下角含由实际布局绘制的全图预览,消息预览同样支持拖动、滚轮缩放和视图工具;消息卡片的单个下载图标会打开 PNG、SVG、JSON 选项。明确的 HTML 请求必须进入沙箱生成 /mnt/user-data/outputs/*.html 并通过 present_files 打开右侧制品预览,禁止用聊天中的完整 HTML 代码块代替文件。前端负责本地校验和渲染,并兼容模型误写成普通 json 围栏的合法地图契约;只有包含地图字段且通过完整 schema 校验的 JSON 才会按地图渲染,其他 JSON 保持原代码块。地图卡片占满消息内容区,与步骤条左右边界对齐;格式错误时显示可读错误与原始数据,不中断整条消息。
生成 .html 网页、看板或大屏时使用独立的离线绘图契约:流程图使用 data-df-flow + AntV G6(不使用 Mermaid),图表使用 data-df-chart,地图使用本地 GeoJSON + ECharts Canvas 的 data-df-geo。Gateway 从公开路径 /api/public/html-runtime/ 提供浏览器版 g6.min.js、echarts.min.js、bootstrap.min.css、bootstrap.bundle.min.js、alpine.min.js 和统一初始化脚本 deerflow-runtime.js,以及中国省级、美国州级、日本都道府县、台湾地区县市四份地图资源(china-provinces / us-states / japan-prefectures / taiwan-counties)。Bootstrap 负责布局与标准弹窗,Alpine 负责轻量状态和动态列表;生成的 HTML 源码会直接引用这些后台文件,不依赖前端 React 构建产物、Leaflet、地图瓦片或公网 CDN。运行时会等隐藏的逻辑页真实可见后再初始化 ECharts,并通过 ResizeObserver 跟随沙箱和页面容器尺寸变化,避免地图或图表首屏以零宽度渲染。区域/标点的 description 必须为纯文本,点击后由运行时显示详情浮层;需要筛选或切到明细页时监听 deerflow:geo-map-click,不能把链接、HTML 或 onclick 写进 description。深色 HTML 需声明 data-df-theme="dark",其 ECharts 的全局、标题、图例、坐标轴和可见系列标签都必须显式使用浅色文字,避免 ECharts 默认黑字。这些文件作为 packages/harness/deerflow/html_runtime/assets/ 的包资源随 harness wheel 一起发布,第三方许可同目录保存。config.yaml -> html_runtime.base_url 必须填写浏览器可访问的 Gateway Runtime 地址(本机默认 http://localhost:8001/api/public/html-runtime),运行时据此解析本地 GeoJSON 地址;内网部署只需把它替换为实际 Gateway 地址。用户上传的栅格图片若要作为生成 HTML 的背景图,智能体会使用同一 Gateway 源下的签名地址 /api/public/html-images/{token} 写入 background-image,不要写沙箱路径或制品接口。该能力不改变普通问答消息列表,普通问答地图仍走原有 map-json 前端瓦片渲染与 frontend-web/public/runtime-config.js 的 VITE_MAP_TILE_URL。地图大屏使用“中间地图舞台”而非整页地图背景:中间地图保持可见和可点击,两侧为窄信息栏、底部为摘要面板;提到的城市和行政区必须分别写入 markers 与高亮 regions。
从已有 Markdown 报告生成 HTML 时,必须根据材料盘点 Mermaid/流程图、图表及地图,并在 page_report_template validate 的 required_features 和 page_brief 中如实声明。流程图必须包含 g6.min.js、deerflow-runtime.js、.df-flow 与 data-df-flow,并拒绝 Mermaid、手写 HTML/CSS 流程节点以及页面内自行初始化 G6.Graph;图表和地图分别检查 ECharts 资源与 data-df-chart / data-df-geo 声明式数据块。data-df-chart 必须是严格 JSON,校验会拒绝函数、箭头函数和其他 JavaScript 回调;颜色使用字面量或数据项 itemStyle,提示文本使用字符串模板。地图只能使用上述四个内置 GeoJSON,校验会拒绝 Leaflet、data-df-map、瓦片和页面内自行初始化 ECharts。默认页面从零开始直接写入最终 outputs HTML,不读取或预览模板;只有用户明确要求模板时,prepare 才会把要求持久化到同名 brief,validate 会自动恢复并与调用参数合并。校验失败时不得交付,必须修复后重新校验。
HTML 默认采用单页面:validate 在 preferred_page_count 省略时按 1 处理;只有用户直接提出“多页面 / 多页 / 多个页面 / 分页门户”或指定页数时,才会启用轻量交互门户模式。未指定页数时默认规划 3 页;生成物仍是一个 HTML 文件,内部使用 .pages + 带 id/data-page 的 .page 页面和 href="#page-..." 导航,因此可以在 Blob 制品预览中可靠切页,不会跳转不可用的相对 page2.html。该模式强制本地引用 Bootstrap 和 Alpine,并由 validate 检查三项运行库、页面容器、逻辑页面和锚点导航:真实 <a> 的目标必须匹配唯一 .page id,每页均须有导航入口,脚本设置的 location.hash 也必须使用同一 #page-... id;CSS 注释或错误的 #overview/#stations 不能伪造合格导航。详情弹窗使用 Bootstrap modal。上传图片若指定为其中一页的背景,只会施加给对应 .page,不影响其他页,也不叠加全屏蒙层。除提示词和 validate 外,MultiPageHtmlMiddleware 还会在 present_files 真正展示 .html 前读取当前制品并执行同一门户结构检查;模型漏调 validate 或写成自定义 tabs 时,交付会被拦住并收到可修复的工具错误。用户明确要求模板时,prepare 会严格排除页数模式不符的模板,并把 requested_page_count 与 page_mode 写入 brief。单页模式检测到多个 data-page、tabpanel、.report-page 或 .view 页面视图时验证失败,内容长度和 Markdown 章节数不构成多页理由。
多页面 HTML 的默认快速路径不读取 catalog/prepare:首个 write_file 直接创建完整最终制品,首次 validate 通过即交付。未明确要求模板时,MultiPageHtmlMiddleware 会拒绝 catalog/prepare;若校验失败,结果会附带局部修复策略,智能体必须保留当前文件并用最小 str_replace 补丁一次性修复全部错误,不能整页重写。所有写入的深色 HTML 会由 html_postprocess 自动注入一次性、幂等的 Bootstrap 深色对比度兼容层(data-df-dark-contrast-fix):它为卡片、表格单元格、弹窗和常见表单控件设置语义浅色前景,并补齐 Bootstrap 文本 token。validate 同时检查未注入/未显式配置的 .card、.data-table 和 .modal 前景色,避免深色页面再次出现黑字。
HTML 流程图节点采用浅色语义圆角卡片、同色系描边和深色高对比文字,节点文字居中并带类型前缀。运行时严格尊重页面声明的 LR / TB 方向和容器高度,不再自行改方向或把容器撑穿后续区块;单图超过 8 个节点时,页面生成规则要求拆图,或使用至少 600px 高的 TB 布局。画布保留拖动和缩放能力。
普通 HTML 默认不读取模板库、不选择模板,而是直接用 write_file 在 /mnt/user-data/outputs/ 从空白文件生成;写入开始即可自动打开右侧沙箱。只有用户明确要求使用模板时才调用 catalog/prepare,此时可选 light-document / dark-document 响应式文档模板,且 working_copy 与 final_output 是同一路径。普通页面自然纵向滚动、主体限制最大宽度、移动端降为单列;只有 page_brief 明确包含大屏、驾驶舱、单屏或固定画布意图时才允许屏幕式布局。文档模式校验会拒绝 html/body overflow:hidden、1920×1080 整页缩放、过矮流程图和过长横向流程。
Workflow Studio runtime
- Candidate workflow graphs remain editable after they are loaded into the Coze-compatible canvas; the compatibility response explicitly marks the current owner as editable so node dragging is not mistaken for a read-only preview.
- OpenAI-compatible model responses accept both
reasoning_contentandreasoning; both are normalized into the existing reasoning display path. Models markedsupports_thinking: truealso receiveenable_thinking: truewhen thinking mode is active unless an explicitwhen_thinking_enabledconfiguration overrides it. Workflow-agent SSE forwards provider reasoning as an inline<think>…</think>trace for the UI while keeping the executable node output limited to the visible answer text. Every native LangGraph[AIMessage|ToolMessage, metadata]frame is additionally persisted and sent asnode.message.data.messageswithout projection or length cropping, so the Studio can replay DeerFlow's tool-result, search-result andask_clarificationcards exactly after reconnecting.
AgentScope report collaboration (in progress)
The independent /api/report-collaboration workbench supports durable run-time intervention commands and immutable formal report versions. Its production worker now runs a real AgentScope 2.0.5 Agent per TaskLedger assignment; AgentScope owns the member ReAct/tool/structured-output loop, while TaskLedger alone owns DAG readiness, retries and completion. Ready nodes in one wave run concurrently up to max_parallel_tasks; cancellation and member exceptions close agent runs as terminal states, and lease recovery remains independent of in-memory AgentScope objects. Retrieval uses the deployment's configured DeerFlow web_search/web_fetch tools through the frozen role permission snapshot. Every visible AgentScope reply, TeamSay handoff, tool call and tool result is persisted immediately into the existing SSE/message protocol; hidden reasoning and system prompts are not exposed. A constrained intent agent classifies explicit answers, angle additions, re-search, re-analysis, node reruns, section rewrites, polishing, report questions, replanning, and cancellation. Server-side impact analysis validates node/agent ownership and computes the minimum downstream lineage; low-confidence, high-cost, or multi-branch changes require confirmation. Commands are CAS-claimed and applied at TaskLedger safe points, where old attempts and artifacts become superseded so late results cannot contaminate the revised report. Run creation freezes the selected /api/report-structures row and all server-enforced budgets. Per-user active-run limits and per-run model-call, retrieval, source-byte, token, cost, node-time, and total-time limits fail closed. User-visible event payloads are centrally redacted before durable persistence without size truncation; external source content is explicitly untrusted, and hidden reasoning/system prompts are never exposed. Append-only audits cover agent selection, tools, sources, user commands, confirmations, report versions, redactions, and budget failures; owner-scoped reads are available at GET /runs/{id}/audits. Temporary Markdown is emitted as report.delta; only a template/citation/coverage-clean report with an independent passing review is atomically persisted and emitted as report.version.created before the run becomes completed. Section rewrites are replacement candidates; apply and restore append a new head version and never destroy history. Configure runtime_mode, concurrency and limits under report_collaboration; both the feature and worker remain disabled by default. Install the report-collaboration project extra when enabling the worker.
Middleware Chain
Middlewares execute in strict order, each handling a specific concern:
| # | Middleware | Purpose |
|---|---|---|
| 1 | ThreadDataMiddleware | Creates per-thread isolated directories (workspace, uploads, outputs) |
| 2 | UploadsMiddleware | Injects newly uploaded files into conversation context |
| 3 | SandboxMiddleware | Acquires sandbox environment for code execution |
| 4 | SummarizationMiddleware | Reduces context near token limits and folds the hidden summary into one leading system message for strict model adapters (optional) |
| 5 | TodoListMiddleware | Tracks multi-step tasks in plan mode (optional) |
| 6 | TitleMiddleware | Auto-generates conversation titles after first exchange |
| 7 | MemoryMiddleware | Queues conversations for async memory extraction |
| 8 | ViewImageMiddleware | Injects image data for vision-capable models (conditional) |
| 9 | ClarificationMiddleware | Intercepts clarification requests and interrupts execution (must be last) |
Sandbox System
Per-thread isolated execution with virtual path translation:
-
Abstract interface:
execute_command,read_file,write_file,list_dir -
Providers:
LocalSandboxProvider(filesystem) andAioSandboxProvider(Docker, in community/) -
Virtual paths:
/mnt/user-data/{workspace,uploads,outputs}→ thread-specific physical directories -
Skills path:
/mnt/skills→ an online-only view underDEER_FLOW_HOME; the canonicaldeer-flow/skills/tree remains administrative storage -
Portable configured mounts: a relative
sandbox.mounts[*].host_pathis resolved against the directory containing the loadedconfig.yaml, not the process working directory. Providers still receive an absolute host path, soknowledge/knowledge_basecan be committed and the deployment can be cloned or moved without editing a machine-specific path; already-absolute mount paths are preserved. -
Skills loading: Recursively discovers nested
SKILL.mdfiles underskills/{public,custom}and preserves nested container paths. Administrators can take a skill offline without deleting it: the persistedenabled: falsestate keeps the files and existing agent references intact while removing the skill from model-facing discovery, prompt injection, new skill selection, and direct/mnt/skillsfile/command access. Bringing it online again restores those references automatically; launcher-managed runtime configuration lives in a writable persisted directory. -
Minimal skill example: The “安装技能” dialog shows the smallest accepted
SKILL.mdfrontmatter. Onlynameanddescriptionare required for the metadata example:--- name: information-summary description: 汇总指定主题的信息并生成简明摘要 --- -
Built-in skill seeds: Source-controlled defaults live beside their Gateway seeders under
app/gateway/routers/_*_seed_assets/skills/and are copied only when the corresponding runtime directory is absent. Startup must never overwrite administrator edits.task-report-libis an internal support package (noSKILL.md) declared through.deerflow-skill-support.json; it is mounted only for enabled dependent report skills. -
Conditional WeKnora LLMWiki: a non-empty
llmwiki.weknora.api_base_urlenables the DeerFlow-native WeKnora BFF and UI; an empty address preserves the existing LLMWiki page. WeKnora remains independently deployed and owns its database, Redis, storage, parsers and models. DeerFlow keeps only the API/admin addresses, user/publication mappings, agent knowledge-base bindings and per-conversation selections; it stores and sends no WeKnora credential, so the endpoint must be restricted to a trusted network and allow DeerFlow traffic. Owners publish directly to the public LLMWiki without an approval queue for ordinary bases; the system-owned conversation-deposit base is always published, shared by all users, excluded from My Knowledge, immutable through user/admin detail operations, and recognized by both its reserved name andsystemowner. An existing same-name remote mapping is adopted in place as the system deposit so its mapping id and document links stay valid. Its user-facing knowledge-base description and generated document metadata use thecmzsbrand; list/detail reads synchronize stale remote descriptions, while conversation-deposit search results, source previews and embedded-detail JSON rewrite legacy brand text tocmzs. Deposit requests must reference a thread accessible to the current account. Personal knowledge-base lists are scoped to the current DeerFlow account even for admins, while frontend LLMWiki query caches also include the active account id so switching accounts cannot reuse another account's list/detail data. Admins remain the only role that sees the WeKnora connection-config tab. Knowledge cards open a dedicated WeKnora-style detail route: Wiki-enabled bases default to Wiki, then documents and graph; standard bases hide Wiki and default to documents. Writable ordinary knowledge-base details expose a native multi-file upload action that submits files sequentially and reports aggregate success/failure; read-only public bases and the system deposit base do not. It supports file/URL/manual-Markdown imports, Wiki page browsing, document preview, real parsed chunks and Wiki graph nodes/edges. New chats use a compact WeKnora-style searchable/grouped multi-select inside the composer, while agent create/edit pages bind spaces through an inline panel. A single proxy service identity means DeerFlow users are isolated logically while their WeKnora entities share that identity's workspace. The read-onlyllmwiki_searchtool reauthorizes the current DeerFlow user on every call and renders credential-free source previews; source previews prefer cleaned Wiki/LLMWiki text when a retrieved chunk maps to a Wiki page, with raw chunks as the fallback. The detail page first creates a signed iframe session through the normal DeerFlow API auth path, then points the iframe directly at the proxied WeKnora/platform/...route using the signeddeerflow_weknora_embedcookie; global auth/CSRF bypass is limited to those signed iframe proxy requests, and HTTPS cookie attributes honorX-Forwarded-Protobehind reverse proxies. The iframe proxy keeps WeKnora's root-relative/platform,/assets,/locales, and/api/v1/*paths, but is registered last throughllmwiki.proxy_routerso it cannot intercept first-party Gateway APIs such asPOST /api/v1/auth/login/username. See docs/LLMWIKI_WEKNORA_INTEGRATION_ZH.md. -
Enterprise Research workbench (temporarily disabled): the independent
/api/enterprise-researchrouter, its report executor, and its ORM table registration are intentionally inactive. The implementation remains in the repository, but startup does not create or alterenterprise_research_tasksorenterprise_research_report_jobs; this keeps the active Deep Research workflow independent of the paused workbench and avoids MySQL schema errors. -
Embedded WeKnora detail chrome: the proxy hides WeKnora's application sidebar (
.main > .aside_box) and expands the detail outlet to the full iframe width, while preserving the Wiki page's own index/navigation panel. The host does not sandbox this trusted, permission-filtered detail frame because browser PDF viewers are disabled inside sandboxed ancestor frames. Preview and knowledge-base file redirects are followed by the Gateway so PDF bytes and protected Wiki images remain inside the authorized proxy instead of leaking to an unreachable object-storage URL. -
Embedded WeKnora knowledge-base switching: the original breadcrumb picker works inside the iframe, but lists only the signed-in user's own and published knowledge bases. Every switch is reauthorized through DeerFlow and receives a fresh short-lived iframe session, so it never exposes the upstream service-admin knowledge-base list.
-
Prefixed WeKnora deployment: the frontend may be published under a separate prefix such as
/magentweb, while embedded WeKnora detail traffic is served by the Gateway under/deerflow. The Gateway rewrites HTML root assets, WeKnora API URLs, redirects, and Vite dynamic dependency maps so page assets stay under/deerflow; the prefixed proxy routes must be registered before the unprefixed fallback. The signed embed cookie covers/deerflow, and auth/CSRF recognition includes all supported static directories. -
File-write safety:
str_replaceserializes read-modify-write per(sandbox.id, path)so isolated sandboxes keep concurrency even when virtual paths match -
Tools:
bash,ls,read_file,write_file,str_replace(bashis disabled by default when usingLocalSandboxProvider; useAioSandboxProviderfor isolated shell access)
Subagent System
Async task delegation with concurrent execution:
- Built-in agents:
general-purpose(full toolset) andbash(command specialist, exposed only when shell access is available) - Concurrency: Max 3 subagents per turn, 15-minute timeout
- Execution: Background thread pools with status tracking and SSE events
- Flow: Agent calls
task()tool → executor runs subagent in background → polls for completion → returns result
Memory System
LLM-powered persistent context retention across conversations:
- Automatic extraction: Analyzes conversations for user context, facts, and preferences
- Structured storage: User context (work, personal, top-of-mind), history, and confidence-scored facts
- Debounced updates: Batches updates to minimize LLM calls (configurable wait time)
- System prompt injection: Top facts + context injected into agent prompts
- Storage: JSON file with mtime-based cache invalidation
Tool Ecosystem
| Category | Tools |
|---|---|
| Sandbox | bash, ls, read_file, write_file, str_replace |
| Built-in | present_files, ask_clarification, view_image, task (subagent) |
| Community | Tavily (web search), Jina AI (web fetch), Firecrawl (scraping), DuckDuckGo (image search) |
| MCP | Any Model Context Protocol server (stdio, SSE, HTTP transports) |
| Skills | Domain-specific workflows injected via system prompt |
Configurable HTTP web search
The config-defined web_search tool can call a deployment-specific JSON API
instead of a public search provider. Set its endpoint, static payload, SSL
verification, timeout, result limit, and result_url_template in config.yaml.
At runtime only payload.query is overwritten with the model's search text.
The API response must use {"results": [{"recUuid": "", "content": "", "title": "", "url": ""}]}; missing or null result fields are normalized to empty strings. When
recUuid is present, {recUuid} in result_url_template replaces the original
result URL; otherwise the original URL is retained. Set enabled: false to keep
the tool completely out of the agent's registered tool list. verify_ssl: false
supports trusted internal HTTPS services with self-signed certificates.
Gateway API
FastAPI application providing REST endpoints for frontend integration:
| Route | Purpose |
|---|---|
GET /api/models |
List available LLM models |
GET/PUT /api/mcp/config |
Manage MCP server configurations |
GET /api/skills, PUT /api/skills/{name} |
List skills; administrators can toggle any skill and ordinary users can toggle their own custom skills without deleting them |
POST /api/skills/install |
Install skill from .skill archive |
POST /api/skills/validate, POST /api/skills/install-upload |
Validate and upload .zip/.skill packages with owner-aware conflicts: owners may explicitly overwrite their own copy; other users' copies report the uploader and cannot be overwritten |
GET/POST /api/agents |
List visible agents or create a user-owned agent |
GET/PUT/DELETE /api/agents/{id} |
Read, update, or delete an id-addressed agent |
GET/POST/PUT/DELETE /api/roundtable-chains |
Manage user-owned roundtable business chains; each seat may optionally carry position_id for the separate position-collaboration workspace, plus human_validation to mark a Step 2 client-side pause checkpoint after that seat/stage completes (the original roundtable has no extra validation card) |
GET/PUT /api/business-mapping |
Read global business mappings (seeded 3Q/6BF/7BF/8BF/AFX plus independent slots 3q-0…3q-6, 6-1…6-4, 7-1…7-4). Updates to 3Q/6BF/7BF and the independent slots require an administrator; the 8BF public business-chain mapping is maintained exclusively by the trusted lqq account (derived from the authenticated email prefix). |
GET/POST/PUT /api/position-roundtable/sessions |
Standalone position-collaboration sessions: freeze task/intent/chain snapshots, index one direct-agent node per seat, build upstream handoff context (including bounded UTF-8 excerpts of text artifacts), and make archived sessions read-only without invoking the original roundtable coordinator or jobs. Built-in summary/action-plan completion mirrors visible report text into a Markdown artifact when a model omits its final write_file call. POST /sessions/{id}/nodes/{nodeKey}/reject accepts an optional reason, marks the rejected delivery for rework, and invalidates affected downstream deliveries so they are regenerated in order. The same router is also mounted at /api/multi-agent/position-roundtable/* for intranet gateways that only forward the established multi-agent namespace; the compatibility mount is intentionally omitted from OpenAPI. |
GET /api/memory |
Retrieve memory data |
POST /api/memory/reload |
Force memory reload |
GET /api/memory/config |
Memory configuration |
GET /api/memory/status |
Combined config + data |
GET/PUT /api/upload-policy, POST /api/upload-policy/reset |
Read the database-backed global upload policy; administrators atomically replace it or restore the persisted defaults. Defaults: 20 files, 100 MiB total, documents/images 10 MiB each, spreadsheets 5 MiB, large-file output 12,000 characters with a non-disableable 32,000-character hard ceiling. |
POST /api/threads/{id}/uploads |
Upload a staged all-or-nothing batch under the current database policy. The Gateway enforces category extension/size limits, validates text/image/PDF/Office signatures, and optionally converts PDF/PPT/Excel/Word to a collision-free <original-name>.<ext>.md companion. |
GET /api/threads/{id}/uploads/list |
List uploaded files |
DELETE /api/threads/{id} |
Delete DeerFlow-managed local thread data after LangGraph thread deletion; unexpected failures are logged server-side and return a generic 500 detail |
GET /api/task-deeplink-threads?task_id=&go_path= |
List the dedicated single-agent task deep-link history for 3qfx / rwfx / xdfx. The (task_id, go_path) lookup comes from task_deeplink_threads and joins canonical thread metadata by primary key, avoiding a global chat search. New marked conversations are indexed automatically; migration 20261019_01 creates the table without backfilling legacy rows. |
GET /api/artifact-library |
List the current user's generated documents across normal threads and completed strategy-selection project snapshots, with name/source search, file-kind/source filtering, pagination, and an optional cache-bypassing refresh. Thread outputs/ directories remain authoritative for conversation files; strategy syntheses are projected from the owner-scoped parallel_agent_tasks snapshot. Strategy items carry source_type=strategy, strategy_project_id, strategy_name, strategy_stale, an explicit strategy_source_kind (strategy_selection or analysis_project), and download_url when they use the dedicated virtual-document route. Uploads and image/audio/video files are excluded. |
GET /api/strategy-project-artifacts/{id}/download |
Download the current user's completed strategy-selection synthesis as Markdown. Ownership and the versioned strategy snapshot are revalidated on every request; stale-but-completed historical syntheses remain downloadable. |
GET /api/threads/{id}/artifacts/{path} |
Stream generated artifacts without materialising the whole file in Gateway memory. |
GET /api/threads/{id}/artifact-preview |
Return a bounded text preview; large files use streaming full-file statistics and deterministic head/middle/tail samples. |
GET/POST /api/deep-research/* |
Durable Deep Research sessions and jobs. quick/basic run the compact report flow; detailed plans subtopics and writes independent sections; deep recursively investigates bounded follow-up branches. All modes reuse DeerFlow's configured material provider, LLM factory, hidden-thread artifacts, and SSE event log. Deep Research session/job/event/source/message writes serialise their rows in the writer transaction, rather than doing a post-commit ORM refresh or readback; this prevents MySQL read/write-splitting replica lag from failing “start writing”, dispatcher claim, report persistence, or follow-up history. Whole-document artifact/report rewrites likewise create their reversible version snapshots from writer-local ORM data and never refresh after commit, so replica lag cannot restore an otherwise successful rewrite. A report is written to the session and confirmed before its Job becomes completed; an unconfirmed write is retried and never exposed as a false completion. Retrying an already-created queued job re-nudges the dispatcher, recovering an interrupted HTTP response without creating a second report. If progress-event persistence, durable replay, or live fan-out is briefly unavailable, the worker/stream logs the degradation and continues or reconnects rather than failing the whole job. Whole-report rewrite accepts an optional reportOutline Markdown template; the durable job persists it and applies it as the chapter structure constraint. Optional report illustrations use a separately configured OpenAI-compatible image endpoint and are served only through the owning session's authenticated artifact route. |
/api/enterprise-research/* |
Temporarily disabled. Its router and persistence initialization are not registered, so these endpoints return 404 and no enterprise-research tables are created or migrated during startup. |
POST /api/ai-writing/sample/extract |
Extract text from Word/PDF/Markdown/TXT samples for AI-writing imitation mode |
POST /api/writing/export/docx |
Generate the shared formal Word download from Markdown. Uses the standard-library OOXML builder in app/gateway/word_export.py, removes manual/compound heading prefixes and Markdown horizontal rules, applies the prescribed A4/margin/heading/body/table/footer profile, and embeds the server-bundled 方正小标宋简体.ttf; the client computer does not need that font installed. |
POST /cop/saveSuperiorTask |
TaskCOP compatibility API (DeerFlow Bearer/session authentication required): create a situation-overview task with a sequential four-digit id (0001, 0002, ...) and return the legacy {state, msg, data} envelope |
POST /api/taskcop/import/tasks |
TaskCOP compatibility API (authenticated): import tasks from a JSON array or tasks/records envelope; every row's supplied id/taskId is authoritative and a duplicate id completely replaces the stored task |
DELETE /cop/tasks/{task_id} |
TaskCOP compatibility API (authenticated): delete one task and its task-scoped situation-report document |
GET /taskAnalyseSearch/cop-task-three-list |
TaskCOP compatibility API (authenticated): paginated task list for the situation-overview sentiment page (content, taskStatus, taskDirection, startTime, endTime) |
GET/POST/PUT /api/task-reports/by-task/{task_id} |
Shared task-scoped situation-report detail. Query, first save, and full update all use the legacy-compatible {state, msg, data:[{id, taskId, createTime, updateTime, sessionId, contentJson, categoryType}]} shape; contentJson is preserved as a JSON string. Saving a non-empty report automatically moves the matching TaskCOP task to status 25 (the legacy list's “已完成” state). |
POST /api/task-reports/import/by-task/{task_id} |
TaskCOP detail import (authenticated): accept a sq-report-mock.json-compatible data/records array and atomically replace the selected task's full report. The path task id overrides task ids inside the file. The agentfx built-in agent fills that file with the task-report-build convert skill in one call (search JSON + fixed task-context.json → 14 dashboard-compatible categories; task-report-{enemy,our,env,judge} re-run a single page) then imports via task-report-import. Converter inputs/outputs are confined to the current thread's user-data, and partial reruns reject a report belonging to another task. |
POST /api/sentiment-agent/stream |
Authenticated BFF for the frontend sentiment-analysis virtual agent. Forwards the AG-UI JSON body to the apiUrl supplied from runtime-config.js (Basic Auth from the same payload). TLS certificate verification is off. Upstream SSE is piped through unchanged; connection/HTTP failures return the complete error in detail. |
The separately deployed TaskCOP Vue app uses POST /api/v1/auth/login/username directly (its username, userId, or yUserId URL parameter identifies the user), so this compatibility flow does not depend on the retired Consumer login service. It calls the Gateway address configured as b1ConsumerUrl directly; it does not use a Vite /api reverse proxy. An empty TaskCOP store remains empty; task data is introduced through normal creation or the JSON import API. The Gateway CORS middleware always merges local Vite origins http://localhost|127.0.0.1:{5173,5174,3000,8080}, including when GATEWAY_CORS_ORIGINS is set for another frontend. This keeps a Vite server that falls back from 5173 to 5174 (or the reverse) working after a backend restart. For a deployment, set GATEWAY_CORS_ORIGINS to the exact additional frontend origins that are allowed to call the Gateway.
Workflow Studio interface resources
The Workflow Studio data-source API also registers reusable HTTP interface resources. Create them through POST /api/workflows/data-sources with kind set to http; provide a non-secret baseUrl, an allowedMethods allowlist, and optional request headers. The service encrypts the headers immediately and never returns them through list, detail, resource-catalog, run-event, or canvas APIs. The canvas resource catalog exposes only the name, description, HTTP address, and allowed methods. HTTP execution continues to enforce the workflow target security policy, including the default SSRF restrictions.
The same catalog's agent and skill entries are display projections, not a second ownership store:
/api/workflows/resources/agents returns agentId, cached available skills, and createdBy; the
skills endpoint returns skillId, direct-call capability, and createdBy. Creator names are resolved
from the immutable owner id at read time (falling back to the id if an account was removed), while
built-in resources explicitly return 系统内置.
Conversation-driven workflow planning
The Workflow Studio chat composer does not silently start a template. A normal user
message first creates a durable planning session with
POST /api/workflows/{workflow_id}/planning-sessions/stream (SSE; the legacy
non-streaming POST /api/workflows/{workflow_id}/planning-sessions remains for
integrations). The stream relays safe DeerFlow controller milestones—catalog read,
controller parsing, role selection, graph assembly and validation—before returning
the final session, so the UI never has to fabricate a progress bar. The constrained planner builds
two or three validated candidate graphs from the current draft and the visible business
agents the user can use. It is a dedicated built-in 工作流总控 (workflow-planner),
not the multi-agent roundtable coordinator: it only reads the user requirement and
catalog metadata, selects permitted agent ids, and ranks the supported strategies. It
does not dispatch a roundtable, use research tools, or execute a formal run. The server
constructs the executable graph from those selected, visible resources; a controller
failure returns an explicit planning error rather than a keyword/template fallback. The
controller call has no callable tools and disables model thinking, so it adds one bounded
planning inference without starting research or worker-agent costs. A
browser selects one candidate, automatically applies the editor's DAG layout when
it loads, then may explicitly save an edited graph through
PATCH /api/workflows/planning-sessions/{session_id}/proposals/{proposal_id}/graph,
then creates the one formal run through the existing
POST /api/workflows/{workflow_id}/runs endpoint with planningSessionId and
proposalId. The gateway rejects unselected or invalid candidates and makes a repeated
confirmation return the already-created run, so a double click cannot execute a second
workflow. Planning sessions are persisted separately from runs (migration
20260831_01); their parent row is flushed before candidate rows so the foreign-key
transaction works consistently across SQLite, MySQL, and PostgreSQL. A candidate is
not consuming execution resources until it is confirmed.
The development Studio proxy must not gzip /api responses: compressed SSE can hold
small planning and run-event frames until the response ends. The backend already marks
both streams no-transform and X-Accel-Buffering: no; apps/coze-studio also sets
its Rsbuild development server compress: false. Agent/skill nodes use the bounded
workflows.agent_recursion_limit (default 250), rather than a chat-sized 60-step
limit, because a model-to-tool research turn consumes about two LangGraph super-steps.
The composer may also send a configured modelName: the server validates it against
the safe model catalog, uses it for the workflow controller, and writes it into the
generated candidate agent nodes. For a direct single-agent task it overrides only that
projected node; confirmation still executes the immutable selected proposal snapshot.
The controller additionally creates a per-selected-agent task contract (mission,
deliverable, scope, handoff). The graph builder accepts contracts only for
catalog-visible, selected agent ids and injects each one into that worker's prompt, so
parallel branches receive complementary research assignments and sequential branches
have an explicit review/handoff boundary rather than all workers attempting the whole
user task. Missing controller fields receive a deterministic role-aware contract.
When an agent emits DeerFlow's native ask_clarification ToolMessage, the workflow
enters awaiting_input rather than completing or failing. The pause event references
the originating nodeRunId and toolCallId; the browser renders that original card
inside the agent message and POST /runs/{run_id}/resume supplies the user's reply as
the next turn of the same agent thread.
The current planner is deliberately constrained: it can rank supported strategies and
focus resources, but the server builds the graph from the already configured agent
nodes. It cannot invent a node type, external target, or credential from chat text.
For a parallel-research candidate, the configured research agents first flow into the
deterministic evidence_normalizer node. It emits a bounded Evidence Pack with the
originating node, role label, explicit gaps, and no invented verification claim before
the synthesis agent sees it. The candidate then runs deep_research_write: its
app-layer adapter converts exactly that Evidence Pack into selected, provenance-tagged
Deep Research source rows, starts or reattaches to the existing durable Deep Research
job, forwards report deltas as workflow node.output.delta events, and exposes the
final Markdown as a run artifact. Cancellation is forwarded to the same research job;
the node never calls the Deep Research HTTP API or launches a second writer. The
session key includes the topic, evidence snapshot and writing configuration, so a retry
reuses the same job while a changed upstream pack cannot return stale report prose. multi_agent Deep Research is
intentionally not nested yet because its separate plan-review pause would conflict
with workflow human_input. A completed agent or skill node in a terminal,
acyclic run can now receive targeted feedback through
POST /api/workflows/runs/{run_id}/feedback: the original remains immutable, a
revision run reuses completed nodes outside the target's downstream closure, and
only the target prompt receives the bounded feedback block before it and its
downstream nodes stream again. The public revision run retains the affected and
reused node ids in its feedback summary for the conversation/history UI. Running and looped workflows deliberately continue
to use human_input or a new full run; they are not modified in place. See
docs/WORKFLOW_CONVERSATIONAL_MULTI_AGENT_ORCHESTRATION_ZH.md for the roadmap.
Deep Research modes
Deep Research is a separate Gateway feature rather than part of AI Writing. Its
detailed runner adapts GPT Researcher's detailed-report control flow (plan
subtopics, collect materials concurrently, write sections concurrently, then
assemble the report). Its deep runner adapts the recursive DeepResearchSkill
flow (branch, extract learnings, investigate follow-ups) with a deterministic
query budget derived from deep_breadth and deep_depth. The runners live in
packages/harness/deerflow/agents/deep_research/runners/ and only use injected
DeerFlow adapters. multi_agent is a LangGraph 1.x editor/researcher/writer/
reviewer graph: it persists a plan-review pause, releases the worker lease, and
resumes from the approved or revised plan through the existing durable-job API.
Report prose uses the configured LangChain model's astream() path. Each
native model delta is fanned out as an in-process report_delta SSE frame so
the right-side report.md sandbox can write smoothly, while bounded,
persisted report_chunk events remain reconnect checkpoints. The durable log
is still authoritative: a browser reconnect, slow consumer, or another worker
falls back to ?after=<seq> replay rather than starting a second model call.
Reasoning exposed by providers either through structured reasoning fields or
inline <think>...</think> text is separated from report deltas, checkpoints,
and the persisted Markdown projection. It may be forwarded as an ephemeral
live report_thinking frame for the message-list thought trace, but is never
persisted or written to the sandbox; only answer Markdown reaches the sandbox.
The short non-streaming control-plane calls used to plan queries, sections, and
reviews are independently capped at 45 seconds (maximum 120 seconds). If query
or section planning times out, the runner emits a visible recoverable warning
and searches the original research topic instead of leaving the job at the
planning step indefinitely. Report-prose streaming is intentionally not capped
by this control-plane limit.
Planning also persists queries_planned before each basic, detailed, or
multi-agent parallel search fan-out; the completed session stores its terminal
lastJobId in the usage snapshot so
the conversation workbench can replay the full planning/search/source/curation
trace after a page refresh. These are observable execution facts only, never
hidden model reasoning.
Detailed reports use stable introduction/section/conclusion targets; a
multi-agent review revision emits report_reset before the replacement draft.
Every session may also provide a bounded custom_outline. Markdown headings or
numbered entries become the deterministic section plan for detailed and
multi_agent; basic and deep receive the same outline as a guarded
report-writing constraint. An empty outline (the workbench default) is planned after chat collection
via POST /sessions/{id}/intent-outline and filled into the write-confirmation
form; basic/quick still generate a topic-specific skeleton at write time
if the form is still empty. The outline is frozen in the session snapshot and
never treated as a system instruction.
All owner-scoped Deep Research routes resolve the platform's asynchronous
get_current_user identity before querying or writing persistence, so session
and job records are always bound to a concrete user ID.
Report artifacts are server-named and written through the canonical sandbox
virtual path (/mnt/user-data/outputs/<name>), which the path layer maps to
the host filesystem. This keeps native Windows deployments compatible without
relaxing the sandbox traversal guard.
Deep Research reuses the DeerFlow Q&A retrieval stack instead of a separate
scraper. It first runs enabled research skills (deep-search, web-research,
and enabled knowledge-base skills) through the same SubagentExecutor runtime
used by Q&A, then uses a configured web_search provider when present. If this
offline deployment still has the shipped placeholder/disabled web_search
configuration, it falls back to DeerFlow's existing keyless DuckDuckGo provider
and, when that provider returns only irrelevant engine noise, a second real-web
search source. It persists only real returned sources. A missing intranet
endpoint therefore cannot by itself produce an empty Deep Research report. The capabilities API
reports skill only when its required Q&A tool is available, and reports
web_search when configured search or the online fallback can run.
Cancelling a queued research job finalizes its job row and immediately marks
the owning session cancelled with no active job, so it cannot retain a
per-user concurrency slot.
Saved draft sessions do not consume a concurrency slot; the two-job limit
only applies to sessions with a running or awaiting_input durable job.
Whole-report rewrite builds its requirement plan locally from the selected
style and instruction, publishes that plan immediately, and then opens only
one model stream for the actual Markdown writer. This avoids making users wait
for a separate model-thinking/planning pass before sandbox output begins.
POST /api/deep-research/sessions/{id}/report-variants is the structural
regeneration boundary: it copies the completed report and selected evidence
into a new session (including both saved [来源:id] and legacy streamed
[[source:id]] citation markers remapped to the cloned ids). The following
durable writer is explicitly queued with operation=generate: it does not send
the inherited report to the model as text to edit, and instead writes a fresh
report from the complete selected evidence set plus the newly confirmed
outline. Model reasoning remains a live SSE event before the first Markdown
token. New output uses display-ready [来源:id] citations; validation repairs
only unique near-complete drs_src_* ids (for example a provider-truncated
suffix) and still rejects genuinely unknown evidence ids.
The report-only model stream requests an 8192-token completion budget so a
normal multi-section report and its reference list do not stop at the common
4096-token provider default; malformed trailing citation syntax is rejected
rather than committed as a partial document.
The original report therefore remains independently available in history even
if the new report is cancelled or fails.
Optional report illustrations
DeerFlow's stock configuration includes image recognition but no image-generation
provider. Deep Research therefore keeps illustrations disabled until an operator
fills config.yaml -> deep_research.image_generation with an
image endpoint, API key, and model. provider: openai_compatible uses the
standard /images/generations protocol and requires b64_json; provider: dashscope_native supports Qwen Image 3.0 and Wan 2.7 on DashScope / Token Plan
with the native multimodal-generation protocol. Native provider image URLs are
downloaded immediately, so both providers produce protected local artifacts.
When configured, the capabilities endpoint enables the page's “生成报告配图” switch.
A selected job writes at most four research-image-*.png|jpg|webp artifacts,
injects deep-research:// links into the Markdown report, and emits image SSE
events. A failed/unconfigured image provider only emits a recoverable warning;
the text report still completes.
Completed reports also expose GET /api/deep-research/sessions/{id}/messages
and POST /api/deep-research/sessions/{id}/chat. Follow-up answers are grounded
only in that session's report, selected sources, and recent follow-up history;
the assistant returns persisted source ids for every accepted citation. Passing
allow_new_research=true is rejected rather than silently triggering another
search job. Owners can change the follow-up evidence set with
PATCH /api/deep-research/sessions/{id}/sources/{source_id} after a research
job stops. That selection is applied immediately to later follow-up prompts;
it never rewrites the completed report, and edits are rejected while the job is
running so they cannot race the automatic curation projection.
The Deep Research page is a conversation workspace: the original research
question, observable retrieval steps, completion summary, and report-grounded
follow-ups appear in chronological order. The report itself is written in the
adjacent sandbox as /mnt/user-data/outputs/report.md, with Word, Markdown,
and HTML export actions. Word export calls the shared Gateway
POST /api/writing/export/docx generator, embeds the licensed title font, and
includes the collected reference list without adding a backend Python package.
In chat-collection sessions the report configuration card
shows the unique material count inferred from the collector's visible tool
results before writing begins. The write lifecycle itself is rendered as
ordinary workspace message-list tool steps (not a second timeline component),
and remains in the transcript after completion; the generated digest is a
normal assistant message below those steps. As soon as the runner enters its
summary phase, that same assistant message is inserted in a streaming state
and receives each SSE summary_delta; completion only changes it in place to
the final report-complete wording rather than appending a delayed second
summary card.
Each completed chat-collection report also contributes a normal file card to
the message history, named from the report Markdown title rather than the
internal report.md artifact path. Historical sessions keep the sandbox closed
on replay, and opening that card selects the session's virtual report artifact
in the sandbox; its download action uses the existing Markdown/Word export
dialog. The per-user active-job limit and the default in-job retrieval/write
concurrency are both three. The Deep Research composer uses the installed
TDesign Select, populated from /api/models, for an optional per-research
model override. It is disabled during active collection/writing, is sent as
model_name to collector turns, and is applied to the fast_model,
smart_model, and strategic_model roles when a session or report job starts.
Completed-report Q&A uses POST /api/deep-research/sessions/{id}/chat/stream
for native SSE deltas; the earlier /chat JSON endpoint remains for API
compatibility. An explicit scoped edit such as “修改第一段”、
“重新生成一下第一段” or “重写第二节” uses the separate
POST /api/deep-research/sessions/{id}/rewrite/stream report-section writing
pipeline. That route requires a deterministic Markdown range and returns 422
instead of falling back to Q&A when no target is supplied. Ordinary follow-up
questions remain report/source-grounded and never mutate the report. The streamed
replacement candidate is shown in the message list while the sandbox keeps the
current report unchanged. The completed candidate creates a confirmation card;
only POST /sessions/{id}/messages/{messageId}/rewrite-proposal with
{"action":"apply"} substitutes the matching range and saves the assembled
Markdown. The proposal carries the source report hash, so a stale candidate is
rejected instead of overwriting a later edit; dismissing/cancelling leaves the
persisted report unchanged. This does not initiate new web research. Legacy
unmarked candidates from before this route split are inferred only when their
immediately preceding user message contains the same explicit scoped edit.
Position-roundtable history supports two modes. A non-empty external_task_id from the frontend route's taskId creates a row with task_scoped = true, so all sessions, nodes and persisted conversation snapshots for that task are shared across users; user_id is then creator audit metadata only. When the field is absent or empty, the original per-user personal-session behavior remains unchanged. Alembic revision 20260808_01 adds task_scoped with the safe default false for historical rows plus the task/update-time index.
Roundtable concurrency guarantees
Position-roundtable node completion, rejection, thread binding and activation are database transactions with row locking and retry-stable command ids. This keeps simultaneous seat completion from leaving a downstream stage locked. Task and intent snapshots are frozen when the chain becomes active, so a late request from another user cannot alter the confirmed input. Background roundtable jobs use a database unique active-job key and renewable worker leases; cancelled jobs are finalized by the lease holder or recovered after an expired lease, allowing users to start a new job after a worker failure.
The ordinary unit tests use SQLite. To verify deployed database semantics with
real PostgreSQL row locks and independent connections, set
DEERFLOW_POSTGRES_CONCURRENCY_TEST_URL and run:
PYTHONPATH=. uv run --no-sync pytest tests/test_roundtable_postgres_concurrency.py -q
AI writing supports a sample-imitation mode: the frontend can upload or paste a sample article, the Gateway extracts text for document files, and the ai_writing graph runs a sample_analyzer node before normal intent/research/outline/draft stages. The resulting style profile is injected into outline and draft prompts with explicit guardrails against copying source sentences, facts, names, or data.
Custom Agents
Custom agents are indexed in the agents database table. The id column is the stable runtime identifier and filesystem directory name (.deer-flow/agents/{id}/SOUL.md); name is display-only, may be Chinese, and may be duplicated. Rows with user_id = NULL are built-in agents, user rows are private unless published = true, and users can only update or delete agents they created.
The built-in forced-research-responder (「强制检索输出助手」) is seeded at Gateway startup. Unlike an ordinary skill-enabled custom agent, it has a runtime-enforced per-turn collection gate: the model cannot finish a response until a retrieval/knowledge/MCP tool or a skill Python script returns usable information. It then answers directly in the user's requested chat format rather than generating an artifact. The agent may create or edit only temporary .py files below /mnt/user-data/workspace/ and may execute .py skill scripts through bash; writes to outputs, Markdown/document creation, present_files, general shell commands, and skill mutation are denied. Administrators can edit its model and skill allowlist from agent management; the runtime safety policy remains fixed. Regression coverage: tests/test_forced_research_agent.py.
The strategy-selection workbench starts with nine folder-backed functional agents: one intent agent, six independent angle agents (governance, diplomacy, economy/supply-chain, security risk, public opinion, and law/policy), one judge, and one final solution editor. Defaults ship in app/gateway/routers/_strategy_selection_seed_assets/ and are copied only when an agent directory is absent, before _sync_legacy_agents; existing administrator edits are never overwritten. A global strategy_agent_roles registry owns classification, lifecycle state, and ordering independently of projects. Intent, judge, and solution are fixed singleton roles: administrators may update their model, SOUL, and skills but cannot add, disable, reorder, or delete them. Angles form one ordered catalog shared by every project; administrators may add, update, disable, restore, and reorder them. GET /api/strategy-agents is the public enabled catalog and omits model/SOUL/skills; include_disabled=true or include_config=true requires an administrator. Admin writes use POST /api/strategy-agents/angles, PUT /api/strategy-agents/{id}, and DELETE /api/strategy-agents/{id}; deletion is a soft disable so folder configuration and historical project references remain intact. Dynamic angles use the reserved strategy-selection-angle- namespace, receive web + file:read, and get a safe default SOUL if the submitted SOUL is blank. Model names and selected skills are validated, skills must be active and globally visible, generic agent CRUD cannot create or hard-delete reserved strategy agents, and parallel execution rejects disabled roles. The shipped angle agents use policy-knowledge, intent uses intent-clarification-policy, judging is input-only, and the solution editor has file-write access for /mnt/user-data/outputs/综合策略方案.md. Every default SOUL requires neutral evidence handling, de-escalation and civilian protection, and prohibits operational combat guidance or targeted political manipulation.
POST /api/parallel-agents/run/stream keeps reasoning and the deliverable in separate per-agent frames. Provider structured reasoning and inline <think>...</think> content are emitted only as reasoning_delta; visible answer text is emitted as delta, and agent_done.content contains only the visible answer. Consumers must not append reasoning_delta to strategy output or the final document. A final is_llm_error_message fallback, empty/thinking-only response, or punctuation-only provider degeneration is emitted as agent_error, never as a successful agent_done. While a seat is waiting inside a long-running skill or retrieval call, the stream emits agent_progress every five seconds with its current user-facing step and elapsed time; 180 seconds without any upstream frame ends only that seat with agent_error instead of leaving the whole workbench pending. The strategy frontend keeps the intent Q&A visible, translates tool calls into readable execution steps, and shows the heartbeat on each running card. Strategy-mode project conversations also open on the real message/tool stream by default, with the structured board still available as a secondary view. Regression coverage: tests/test_strategy_selection_seed.py, tests/test_strategy_agent_management.py, and tests/test_roundtable_model_fallback.py.
AI Writing Intranet Retrieval
AI 写作的「知识库检索」会直连 ai_writing.intranet_search_url 内网 ES/知识库接口,并默认跳过代理环境变量和 HTTPS 证书校验,适配自签名或内部 CA 证书的内网服务。需要强校验时可在 config.yaml 里设置 ai_writing.intranet_verify_ssl: true。通用检索里的非内置技能在 ai_writing.researcher_skill_agent: true 时会用真实 agent 运行时执行技能:以 ai-writing-researcher 身份读取该技能的 SKILL.md,并按技能要求通过 bash 运行脚本,弱模型也会在本轮任务里收到明确的读文件和脚本执行指令。
Installable Sentiment Analysis Skill
Cognitive Situation Map Regions
cognitive-situation-map ships its interactive runtime through
/api/public/html-runtime/cognitive-situation-map. Its bundled 2×2 base-map
atlases now cover local Taiwan Strait and South China Sea views, the
Philippines-centred surrounding region (including the southern Sulu/Celebes
approaches), the First Island Chain, and the U.S.--Pacific connection (U.S.
West Coast, Hawaii, and the wider Pacific), in addition to the existing
regions. Each region provides terrain, maritime, tactical, and briefing
treatments. The skill package and
the harness HTML-runtime copy must be updated together so generated artifacts
and Gateway previews expose the same region catalog.
For a routine render, place only scenario.json in a fresh artifact directory
and pass that directory as --output; the renderer accepts its direct input
file as the sole pre-existing entry. Do not use --force as a normal retry
mechanism—choose a new output directory when other artifacts are present.
skill-packages/sentiment-analysis.skill is a separately installable skill package. It keeps the external AG-UI gateway address in config/sentiment-agent.json, resolves Basic Auth from deployment environment variables, and normalizes the upstream SSE stream into NDJSON (thinking_delta / answer_delta / done) for callers that need incremental rendering. It does not alter the existing frontend sentiment-analysis page. See skill-packages/sentiment-analysis/docs/调用与安装说明.md for installation and invocation.
IM Channels
The IM bridge supports Feishu, Slack, and Telegram. Slack and Telegram still use the final runs.wait() response path, while Feishu now streams through runs.stream(["messages-tuple", "values"]) and updates a single in-thread card in place.
For Feishu card updates, DeerFlow stores the running card's message_id per inbound message and patches that same card until the run finishes, preserving the existing OK / DONE reaction flow.
Quick Start
Prerequisites
- Python 3.12+
- uv package manager
- API keys for your chosen LLM provider
Installation
cd deer-flow
# Copy configuration files
cp config.example.yaml config.yaml
# Install backend dependencies
cd backend
make install
Configuration
Edit config.yaml in the project root:
models:
- name: gpt-4o
display_name: GPT-4o
use: langchain_openai:ChatOpenAI
model: gpt-4o
api_key: $OPENAI_API_KEY
# Complete input request ceiling; independent from the output max_tokens.
max_input_tokens: 128000
# 默认 native;仅对会缓存 write_file 长参数的兼容网关启用 buffered_gateway。
write_file_transport: native
supports_thinking: false
supports_vision: true
- name: gpt-5-responses
display_name: GPT-5 (Responses API)
use: langchain_openai:ChatOpenAI
model: gpt-5
api_key: $OPENAI_API_KEY
use_responses_api: true
output_version: responses/v1
supports_vision: true
Set your API keys:
export OPENAI_API_KEY="your-api-key-here"
For a LiteLLM OpenAI-compatible proxy, keep supports_thinking: false unless
the configured model has an explicit, proxy-supported thinking control. This
prevents DeerFlow's connection check from sending vendor-specific thinking
fields that a generic OpenAI-compatible gateway may reject.
OpenAI-compatible gateways that return vendor errors inside an HTTP 200
response (for example, a non-zero base_resp.status_code with empty/null
choices) are treated as upstream failures in both streaming and
non-streaming calls. DeerFlow retries transient upstream responses instead of
accepting an empty assistant turn, while genuine quota/balance failures are
classified as non-retriable and surfaced immediately instead of being retried.
OpenAI-compatible tool streams are normalized before LangChain assembles them.
The adapter accepts tool calls placed in a streamed message, at the choice
root, or inside an otherwise empty delta; object/list function.arguments
are converted to compact JSON; missing tool indexes are assigned consistently;
and confirmed cumulative argument snapshots are rewritten to suffix deltas.
Standard OpenAI delta chunks remain unchanged. Per-call state is isolated, so
overlapping requests cannot concatenate one run's write_file JSON into
another. Gemini-style thought_signature variants are retained and echoed on
the next turn.
For an OpenAI-compatible gateway that streams ordinary answer text but buffers
long write_file arguments, an administrator can enable File streaming
compatibility in the Settings dialog. The runtime switch applies
buffered_gateway to every compatible configured model without a Gateway
restart. The provider still sees the original write_file name, but its
selection request carries only path/description/append.
DeerFlow then obtains the body through a second, tool-free ordinary-text stream
inside the same logical model call and projects it byte-for-byte into canonical
incremental write_file argument chunks. LangGraph, ToolNode, the frontend and
the sandbox therefore see one standard write_file call; no internal prepare
tool is published or executed. Provider reasoning and a leading inline
<think>...</think> stay in the normal reasoning channel and never become file
content. Empty bodies, truncation, unsafe finish reasons and unexpected tool
calls abort before sandbox I/O. Ordinary answers do not start the second request.
Turning the switch off restores each model's configured/native transport for the
next question. The older per-model field remains backward compatible, and the
artifact_renderer internal-handshake mode remains only for compatible existing
deployments.
Production diagnostics use LLM_CONNECT_*, LLM_REQUEST_*,
LLM_CALL_RETRY/LLM_CALL_FAILED, and OPENAI_TOOL_STREAM_SUMMARY. They
include the model, run id, connection/first-tool-argument/total latency,
argument-shape counters, retry classification, status, and error type. They do
not include prompts, generated text, file contents, tool arguments, credentials,
URL paths/queries, provider response bodies, or raw exception messages. A
caller cancellation is logged at INFO as status=cancelled, not as a model
failure.
Run-creation SSE responses advertise both the canonical Content-Location and
the resumable Location stream endpoint. The browser records the last consumed
SSE event id and rejoins from that cursor after a drop, avoiding replayed tool
arguments. If a live run produces no business frame for 90 seconds, the UI
checks the authoritative run status and recycles only the local reader; the
backend run continues and concurrent recovery triggers share one reconciliation.
The live RunManager and default MemoryStreamBridge are process-local, so
Gateway SSE is currently a single-worker runtime. A Location, /join, or
Last-Event-ID recovery is a new HTTP request; with multiple uvicorn workers it
can land in a process that has neither the live run nor its buffered events and
will terminate the recovery stream early. Both production offline launchers
therefore default to and validate GATEWAY_WORKERS=1. Do not enable multiple
workers until both the stream bridge and live-run control plane have a shared
implementation; a persistent checkpointer or RunStore alone is insufficient.
For models with a finite context window, set max_input_tokens to a safe value
below the advertised window (for example 160000 for a 200k-token deployment).
Immediately before every lead-agent or subagent model call, DeerFlow applies
this ceiling to the complete payload and transiently removes/truncates the
oldest context if needed. max_tokens remains the maximum generated output;
it does not cap the input. See Context Summarization.
Running
Full Application (from project root):
make dev # Starts LangGraph + Gateway + Frontend + Nginx
Access at: http://localhost:2026
Backend Only (from backend directory):
# Terminal 1: LangGraph server
make dev
# Terminal 2: Gateway API
make gateway
Direct access: LangGraph at http://localhost:2024, Gateway at http://localhost:8001
Project Structure
backend/
├── src/
│ ├── agents/ # Agent system
│ │ ├── lead_agent/ # Main agent (factory, prompts)
│ │ ├── middlewares/ # 9 middleware components
│ │ ├── memory/ # Memory extraction & storage
│ │ └── thread_state.py # ThreadState schema
│ ├── gateway/ # FastAPI Gateway API
│ │ ├── app.py # Application setup
│ │ └── routers/ # 6 route modules
│ ├── sandbox/ # Sandbox execution
│ │ ├── local/ # Local filesystem provider
│ │ ├── sandbox.py # Abstract interface
│ │ ├── tools.py # bash, ls, read/write/str_replace
│ │ └── middleware.py # Sandbox lifecycle
│ ├── subagents/ # Subagent delegation
│ │ ├── builtins/ # general-purpose, bash agents
│ │ ├── executor.py # Background execution engine
│ │ └── registry.py # Agent registry
│ ├── tools/builtins/ # Built-in tools
│ ├── mcp/ # MCP protocol integration
│ ├── models/ # Model factory
│ ├── skills/ # Skill discovery & loading
│ ├── config/ # Configuration system
│ ├── community/ # Community tools & providers
│ ├── reflection/ # Dynamic module loading
│ └── utils/ # Utilities
├── docs/ # Documentation
├── tests/ # Test suite
├── langgraph.json # LangGraph server configuration
├── pyproject.toml # Python dependencies
├── Makefile # Development commands
└── Dockerfile # Container build
Configuration
Main Configuration (config.yaml)
Place in project root. Config values starting with $ resolve as environment variables.
Key sections:
models- LLM configurations with class paths, API keys, thinking/vision flagstools- Tool definitions with module paths and groupstool_groups- Logical tool groupingssandbox- Execution environment providerskills- Skills directory paths plus optional prompt routing;skills.es_query_routingcan inject an editable system-prompt rule that sends flexible Elasticsearch Query DSL requests to thees_queryskill, and can be disabled without changing code. Each ordinary Q&A agent build logsES query routing prompt registrationwithregistered, eligibility, character count, and a short SHA-256 fingerprint so operators can verify the exact configured block reached the final model system prompt without logging its contentstitle- Auto-title generation settingssummarization- Context summarization settingssubagents- Subagent system (enabled/disabled)memory- Memory system settings (enabled, storage, debounce, facts limits)
Provider note:
models[*].usereferences provider classes by module path (for examplelangchain_openai:ChatOpenAI).- If a provider module is missing, DeerFlow now returns an actionable error with install guidance (for example
uv add langchain-google-genai).
Browser CORS
The Gateway permits the local Vite development origins on ports 5173, 5174,
3000, and 8080 for both localhost and 127.0.0.1 with credentials, even
when GATEWAY_CORS_ORIGINS is set. For a separate deployed frontend, set
GATEWAY_CORS_ORIGINS to its exact origin (including scheme and port), for
example http://47.88.25.99:7010.
Extensions Configuration (extensions_config.json)
MCP servers and skill states in a single file:
{
"mcpServers": {
"github": {
"enabled": true,
"type": "stdio",
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"],
"env": {"GITHUB_TOKEN": "$GITHUB_TOKEN"}
},
"secure-http": {
"enabled": true,
"type": "http",
"url": "https://api.example.com/mcp",
"oauth": {
"enabled": true,
"token_url": "https://auth.example.com/oauth/token",
"grant_type": "client_credentials",
"client_id": "$MCP_OAUTH_CLIENT_ID",
"client_secret": "$MCP_OAUTH_CLIENT_SECRET"
}
}
},
"skills": {
"pdf-processing": {"enabled": true}
}
}
Environment Variables
DEER_FLOW_CONFIG_PATH- Override config.yaml locationDEER_FLOW_EXTENSIONS_CONFIG_PATH- Override extensions_config.json location- Model API keys:
OPENAI_API_KEY,ANTHROPIC_API_KEY,DEEPSEEK_API_KEY, etc. - Tool API keys:
TAVILY_API_KEY,GITHUB_TOKEN, etc.
LangSmith Tracing
DeerFlow has built-in LangSmith integration for observability. When enabled, all LLM calls, agent runs, tool executions, and middleware processing are traced and visible in the LangSmith dashboard.
Setup:
- Sign up at smith.langchain.com and create a project.
- Add the following to your
.envfile in the project root:
LANGSMITH_TRACING=true
LANGSMITH_ENDPOINT=https://api.smith.langchain.com
LANGSMITH_API_KEY=lsv2_pt_xxxxxxxxxxxxxxxx
LANGSMITH_PROJECT=xxx
Legacy variables: The LANGCHAIN_TRACING_V2, LANGCHAIN_API_KEY, LANGCHAIN_PROJECT, and LANGCHAIN_ENDPOINT variables are also supported for backward compatibility. LANGSMITH_* variables take precedence when both are set.
Langfuse Tracing
DeerFlow also supports Langfuse observability for LangChain-compatible runs.
Add the following to your .env file:
LANGFUSE_TRACING=true
LANGFUSE_PUBLIC_KEY=pk-lf-xxxxxxxxxxxxxxxx
LANGFUSE_SECRET_KEY=sk-lf-xxxxxxxxxxxxxxxx
LANGFUSE_BASE_URL=https://cloud.langfuse.com
If you are using a self-hosted Langfuse deployment, set LANGFUSE_BASE_URL to your Langfuse host.
Dual Provider Behavior
If both LangSmith and Langfuse are enabled, DeerFlow initializes and attaches both callbacks so the same run data is reported to both systems.
If a provider is explicitly enabled but required credentials are missing, or the provider callback cannot be initialized, DeerFlow raises an error when tracing is initialized during model creation instead of silently disabling tracing.
Docker: In docker-compose.yaml, tracing is disabled by default (LANGSMITH_TRACING=false). Set LANGSMITH_TRACING=true and/or LANGFUSE_TRACING=true in your .env, together with the required credentials, to enable tracing in containerized deployments.
Development
Commands
make install # Install dependencies
make dev # Run LangGraph server (port 2024)
make gateway # Run Gateway API (port 8001)
make lint # Run linter (ruff)
make format # Format code (ruff)
Code Style
- Linter/Formatter:
ruff - Line length: 240 characters
- Python: 3.12+ with type hints
- Quotes: Double quotes
- Indentation: 4 spaces
Testing
uv run pytest
Runtime appearance themes
The admin appearance settings are persisted in .deer-flow/system_settings.json.
Besides the built-in themes, an administrator can create up to 12 runtime
themes through PUT /api/system-settings/appearance. Each theme stores a
safe six-digit primary colour, an optional primary-button text_color,
light/dark mode, name, enabled flag and sort order. The frontend derives
the rest of the visual tokens; the optional overrides object only exposes
ten interaction colours (button_hover, navigation_gradient_start,
navigation_gradient_end, selected_background, border, muted,
muted_text, destructive, brand_title, primary_foreground) for
exceptional brand requirements. The two navigation values override the
automatically derived top-bar gradient. A custom theme id
must use the custom-theme- prefix. Disabled or removed custom themes cannot
remain the system default and automatically fall back to light.
Compact top-bar shortcuts use the same appearance endpoint. In addition to
login and theme query parameters, each shortcut may include ordered keyless
path parameters. The frontend resolves each selected login field and appends
it as an encoded path segment (including inside a #/… hash route); password
remains an ordinary login query parameter with the key password.
Citation display settings
Reference-mode entry points are controlled by citation_display in
.deer-flow/system_settings.json. reference_mode_enabled defaults to false;
when disabled, the frontend hides the "参考文献" composer button in both the
normal workspace and iframe chat, and hides the per-user RAG citation toggle
from appearance settings. Conversations with selected LLMWiki/assistant
knowledge spaces still show their sources even when the manual entry is hidden,
but they use a separate Wiki reader rather than the generic reference-card
panel: a flat Wiki table of contents selects an inline Markdown article, and
sources without one of the currently selected server-authored knowledge-space
ids are omitted. GET /api/system-settings/citation-display is
readable by logged-in users so the UI can decide whether to expose the entry;
PUT /api/system-settings/citation-display is admin-only and supports partial
updates for both reference_mode_enabled and cleanup_words.
Ordinary Q&A Markdown format switch
Administrators can opt into the formal long-answer Markdown prompt from the
admin "问答提示词配置" panel. The value is persisted as
prompt_prefix.ordinary_qa_markdown_format_enabled in
.deer-flow/system_settings.json and defaults to false. When enabled, only
ordinary Q&A receives the # 主标题 / ## 一、标题 / ### (一) 标题 /
#### 1. 标题 rules; writing mode, notebook mode and scheduled runs remain
unaffected. The change takes effect when the next prompt is built.
Technology Stack
- LangGraph (1.0.6+) - Agent framework and multi-agent orchestration
- LangChain (1.2.3+) - LLM abstractions and tool system
- FastAPI (0.115.0+) - Gateway REST API
- langchain-mcp-adapters - Model Context Protocol support
- agent-sandbox - Sandboxed code execution
- markitdown - Multi-format document conversion
- tavily-python / firecrawl-py - Web search and scraping
Documentation
- Configuration Guide
- Architecture Details
- API Reference
- Offline Docker wheelhouse deployment (ZH)
- File Upload
- Path Examples
- Context Summarization
- Plan Mode
- Backend Run/Stop Guide (ZH)
- Setup Guide
License
See the LICENSE file in the project root.
Contributing
See CONTRIBUTING.md for contribution guidelines.
Position roundtable durable history
The position-roundtable workflow stores database-owned recovery snapshots for
Step-1 intent chat, every position-node chat, the summary/action-plan chats,
unsent composer drafts, and generated text deliverables. LangGraph checkpoints
remain the execution source, but SQL history continues to render after a
checkpoint or sandbox-output volume is replaced. Apply Alembic revision
20260723_05 (or simply upgrade to head) before deploying this version.
Its live intent conversation still uses the shared /api/intent/init and
/api/intent/stream endpoints used by the original multi-agent roundtable.
Position roundtable identifies itself with intent_agent_id: "position-roundtable-intent";
this is a separately seeded built-in agent, not an alias of roundtable-intent.
It preserves the same thread, SSE streaming, and file-reading lifecycle as the
generic Step-1 agent, but has its own at-most-five user-assistance-round
brake. A round is one AI turn plus the user reply, not one tool call: the
dedicated agent may emit several independent ask_clarification calls in the
same turn, which the frontend renders as several separate selection cards and
submits together. A topic-only request is not considered resolved: the agent
asks for every independent missing decision (such as scope, decision use, or
task constraints) as separate cards. For the dedicated agent, a fresh
ask_clarification emitted in a stream is a strict user-input pause and wins over any same-run premature
[INTENT_READY]; a historical clarification from the prior turn does not
block the user answer from completing. Its final
[INTENT_READY] JSON contains four string arrays: coreGoals, riskWarnings,
keyPoints, and strategicSignificance; it must contain completed conclusions,
not “待确认” or other unresolved placeholders. The multi-agent endpoint remains
backwards compatible when this optional field is omitted and continues to emit
objective, constraints, and assumptions.
/api/intent/init creates its empty thread directly through the in-process
thread store/checkpointer, matching multi-agent initialization and avoiding a
deployment-sensitive 127.0.0.1:<gateway-port> loopback. If initial checkpoint
creation fails or is cancelled, bootstrap removes both the partial checkpoint and
its thread metadata row. Thread metadata creation issues no post-commit ORM
read-back on any code path: all returned fields are assigned before commit, and
skipping the extra read keeps a MySQL read/write-splitting endpoint from sending
it to a lagging replica (which fails the request with
Could not refresh instance). Only the
recovery snapshot layer is position-specific. Deployments must expose
either /api/position-roundtable/* or the compatibility mirror
/api/multi-agent/position-roundtable/*; updated clients probe both and only
retry when the first response is the framework/proxy-level 404 Not Found.
Position-roundtable role directory
The position-roundtable work views are persisted separately from organisation
positions/RBAC in position_roles. Gateway startup idempotently inserts the
four built-in ids (testing analysis, organisation test, solution design and
comprehensive test), but only when an id is missing: administrator changes to
the name, order, enabled state or role type are never overwritten.
GET /api/position-roles is consumed by the workbench and PUT /api/position-roles is administrator-only. Exactly one enabled intent role
is required. It is the initial intent-identification workspace and owns the
summary/action-plan closing agents. Built-in rows retain stable ids so
historical chain nodes remain readable; they can be edited or disabled but not
deleted. Custom roles may be freely added or removed.
Roundtable artifact submission status
The task-scoped roundtable workbench exposes a lightweight submission-status API for generated deliverables:
GET /api/roundtable/tasks/{taskId}/artifacts?submitted=truereturns the submitted artifact references for a task (oritems: []).PUT /api/roundtable/tasks/{taskId}/artifacts/submissionrecordssubmitted: trueor cancels it withsubmitted: false.
Both routes use the normal Gateway Authorization: Bearer <token> middleware.
The record is shared by taskId; the authenticated user is retained only in
audit columns. Its server-owned idempotency key is
taskId + sourceKey + path + revision. Repeating the same submitted payload
returns the existing record; cancellation updates its state and timestamp
without deleting the audit row. DeerFlow stores only the artifact reference
(threadId + sandbox path, plus the optional session/node/name/MIME fields),
never the Markdown body and never a task-system delivery. Continue to preview
the file through the existing thread-artifact endpoint.
普通知识库管理
在「普通知识库」的「我的知识库」或「公共 LLMWiki」中,知识库所有者和管理员可以点击卡片上的「编辑」,修改名称和描述(描述可清空)。保存后同步更新知识服务和本地记录,并刷新列表;系统「对话沉淀」库不提供编辑入口。系统生成的默认测试问题不会因为只修改名称而被保存为自定义内容,并始终按远端当前名称重新生成;旧版本误保存的精确四问默认模板也按自动问题处理,因此改名后不再继续显示旧名称。只有用户实际编辑问题文本时才固定为自定义问题。
远端已经删除的知识库会在下次列表刷新时自动隐藏,包括个人、公共列表和知识库选择器;页面每 15 秒自动刷新,也可以手动点击「刷新」。远端服务请求失败时展示错误,不把连接故障当成删除,不自动删除本地映射或已有引用。
新建知识库时会通过 GET /api/llmwiki/embedding-models 读取 WeKnora 中可用的
Embedding 模型,并把所选 embedding_model_id 明确传给创建接口。该模型只负责
WeKnora 中上传原文的分块向量化;它和下文 DeerFlow 自己的 Wiki 镜像编码器是两套
独立配置,因此选择 OpenAI/OpenAI-compatible 原文编码器时不要求同时启用本地
BGE-M3。模型列表响应只暴露名称、provider、状态等白名单字段,不返回密钥或连接
参数。模型绑定只对新建库生效;需要跨知识库检索时,各库应使用相同或兼容的向量模型。
选择「Wiki 知识库」时,新建弹窗还可填写「Wiki 内容生成要求」和「Wiki 提取重点」;
已有 Wiki 库可从知识库卡片的「编辑」入口修改。两项配置分别写入 WeKnora
wiki_config.content_instructions 与 wiki_config.extraction_instructions:前者控制自动生成页面的
语言、结构与表达侧重,后者控制候选主题、实体和概念的抽取重点。配置会用于之后上传或
重新解析的文档;只修改配置不会自动重写已有 Wiki 页面。
DeerFlow-local LLMWiki Wiki vector index
LLMWiki can mirror processed WeKnora Wiki pages into DeerFlow and search only
processed Wiki content. Retrieval now runs local vector and local lexical Wiki
recall in parallel and merges independently ranked channels with RRF. A failed
or cancelled incremental sync keeps serving the last coherent vector snapshot;
only rebuild_required, a fingerprint mismatch, or an actually incomplete
snapshot takes it offline. Before a first complete snapshot, internal search
uses local Wiki keyword recall plus WeKnora's Wiki-page search/list API. It
never falls back to raw documents or chunks. Configure
llmwiki.local_wiki_index.embedding, then enable the local index in
config.yaml. auto_sync defaults to true: the scheduler performs one
immediate incremental scan at startup and then refreshes the WeKnora
knowledge-base directory every configured interval (30 seconds by default).
Only new, failed, remotely updated, or rebuild-required libraries are scanned;
a 10-minute low-frequency full scan is the safety net for older providers that
do not update directory timestamps. Wiki page summaries with a complete body
are normalized directly. Older summary shapes use version / updated_at to
skip unchanged detail requests and fall back to the page-detail API when those
markers are absent. Unchanged pages reuse their vectors, while directory,
parent, path, source/chunk-reference, version, and timestamp-only changes update
the mirror without re-embedding the body.
database_batch_size caps each vector insert statement (default 50) while
the page replacement remains atomic, preventing a large Wiki from flooding
the SQL connection in one statement.
The optional embedding.dimensions value is used only to validate the returned
vector size and build the index fingerprint; it is deliberately not sent in
the embedding request because fixed-dimension BGE/OpenAI-compatible endpoints
may reject that extra field with HTTP 400. Omit it to discover the dimension
from the first successful response.
Embedding failures log the upstream HTTP status and a bounded response body.
The client never sends a dimensions request parameter.
The deployment default uses SiliconFlow's OpenAI-compatible endpoint with
BAAI/bge-m3 (1024 dimensions); set SILICONFLOW_API_KEY in .env.local or
the process environment. Provider/model/base URL/dimensions are part of the
fingerprint, so switching providers intentionally schedules a rebuild while
keyword/Wiki fallback remains available.
The migration creates llmwiki_wiki_pages, llmwiki_wiki_vectors, and
llmwiki_wiki_sync_states. Wiki Markdown and normalized float32 vectors are
stored in DeerFlow's configured SQL database. Multi-worker sync is protected by
database leases; searches use revision-checked per-knowledge-base LRU snapshots
after the full-library readiness gate succeeds. Knowledge-base owners can click
同步更新 in Wiki management (POST /api/llmwiki/knowledge-bases/{id}/sync; the former /vectorize route remains a
compatible alias) and follow its status at
GET /api/llmwiki/knowledge-bases/{id}/wiki-index-status. The response includes
aggregate progress plus a pages list with every Wiki page's index state,
vector-section count, timestamps, and last error. Its last_sync object reports
the last run's added, modified, skipped, deleted, and failed counts, timestamps,
mode, and sync id. It also includes configured/indexed fingerprints,
stale-serving state, and the effective retrieval mode. The management page renders
the percentage and labels each article as 已向量化 / 待向量化 / 失败, so it is
clear exactly what is searchable. Existing chunk citations remain readable
while all new retrieval citations open Wiki pages.
The frontend labels these results simply as Wiki references; provider-brand
wording is intentionally hidden from user-facing labels and status text. In
knowledge-space Q&A, the right side is a dedicated Wiki table-of-contents and
inline Markdown reader rather than the generic reference-card layout. Its
完整 Wiki action opens one right-side drawer (rather than a second detail
dialog), renders the complete page as Markdown, and reads the remote
WeKnora Wiki page directly when the local index is disabled. Even if a Wiki
citation carries a legacy document id, the drawer does not mix in the uploaded
original-file preview. A single page can be downloaded as Markdown or Word from
the drawer. Open in Wiki navigates to the exact DeerFlow knowledge-base
mapping and Wiki slug. Wiki search-result cards use the same drawer interaction.
Exact Wiki links now open a lightweight native reader at
knowledge/{mapping_id}/wiki/view?wikiSlug=...; it fetches only the selected
page and never creates a WeKnora iframe session. The former
knowledge/{mapping_id}?tab=wiki&wikiSlug=... links redirect to the reader
before waiting for the LLMWiki runtime check. Drawer navigation stays in the
same SPA and passes the already-loaded page as route state for immediate paint.
The native reader also keeps a searchable, collapsible Wiki directory on the
left. It reconstructs hierarchy from parent_slug, wiki_path,
category_path, and the slug fallback, highlights and expands the current
page, and switches directory entries through the same lightweight reader route
without mounting the WeKnora iframe.
All generated SPA deep links preserve the deployment document path (for
example /web/) before the # route, including Q&A references and Wiki index
links. Protected Wiki images no longer render from temporary browser blob:
URLs. Public images use the published file proxy directly; private images first
obtain a 24-hour signed capability and then load through
GET /api/public/llmwiki/files/{token} with a real HTTP URL and inline
filename, allowing zoom, new-tab viewing, and browser Save As without exposing
the user's bearer token. The embedded WeKnora detail page follows the same
rule: late img[data-protected-src] placeholders keep their scoped HTTP proxy
URL on the DOM node, so WeKnora's click-to-enlarge viewer never receives an
already-revoked blob: URL.
Leaderboard query and schema startup safety
The leaderboard excludes scheduled runs with indexed correlated NOT EXISTS
checks. Tool metrics no longer outer-join the full runs table merely to
recognize legacy scheduler messages. New databases receive dimension-aware
analytics indexes from ORM metadata; existing large MySQL deployments should
run scripts/sql/leaderboard_indexes_mysql.sql during a low-traffic window so
index creation never delays application startup.
Startup Alembic migrations now have one unambiguous head. The historical
duplicate 20260821_01 identifier is repaired by unique fixed-question
revisions and the 20260825_01 merge revision, which also fills the
report-structure column for databases stamped by either old variant. The later
duplicate 20260916_01 / 20260920_01 Wiki revisions are normalized into the
20260916_03 merge and final 20260920_02 reconciliation migration. That final
step inspects the real schema and idempotently restores Wiki edges and retrieval/
incremental-sync columns, so databases stamped by either historical duplicate
variant converge without guessing which file ran.
「问题配置」中的固定回答流式速度完全在本地实现。固定回答命中后使用内置的
文字分块器控制 1–1000 token/s 的近似速度,不加载模型分词器,也不会在首次命中时
联网下载 cl100k_base 等词表;因此清空缓存的离线部署同样可以直接返回固定回答。
Published knowledge bases also have a login-free, chrome-free reader at
/#/embed/knowledge/{mapping_id} for direct third-party iframe use. With no
wikiSlug, it selects the Wiki 索引 page first (recognized by title, page
type, or index/home/readme slug) and pins that page to the top of the
public directory. If WeKnora has pages but no explicit index page, the reader
creates a virtual __index__ landing page from the public directory; empty
dynamic-index groups also fall back to those page summaries;
?wikiSlug=... deep-links one page. The reader supports
theme=light|dark, bg, and text embed appearance parameters. Index-article
catalogue sections (page-type counts, nested categories, page summaries) are
read from the published-only /wiki/index proxy because WeKnora renders that
part dynamically rather than storing it in the index page body; group and item
ordering therefore stays identical to WeKnora. Links generated as
knowledge-base-shell placeholders are matched
against the public page title, slug, or alias and rewritten to exact embedded
wikiSlug links. They retain WeKnora's brand-color medium text and dashed
underline (solid on hover), and switch articles inside the current reader
without a full-page reload. Data comes
only from the read-only /api/public/llmwiki/knowledge-bases/{id}/... namespace,
which rechecks publication_status=published on every request and denies the
system conversation-deposit knowledge base. Private or unpublished bases are
never exposed by this route. Anonymous consumers can discover the available
public Wiki libraries through GET /api/public/llmwiki/knowledge-bases. The
endpoint validates each published mapping against WeKnora, omits stale remote
mappings, and returns safe comprehensive metadata: description/type,
document/chunk/processing/share counts, non-secret configuration and
timestamps, Wiki page/type/status counts plus explicit/generated index mode and
default embed route, and aggregate local-vector state/counts/timestamps. It
never returns the WeKnora id, tenant/creator identity, storage credentials, API
credentials, raw vectors, or per-page indexing errors.
Protected resource:// and supported object-storage images are fetched through
the current mapping instead of being exposed as upstream URLs. Authenticated
readers use GET /api/llmwiki/knowledge-bases/{id}/files?file_path=...; public
readers use the corresponding /api/public/llmwiki/... route, which requires a
published mapping. The embedded WeKnora guard applies the same mapping-scoped
proxy to document previews and Markdown images, including images inserted after
the drawer/page first renders.
Wiki management also provides 导出 Excel. GET /api/llmwiki/knowledge-bases/{id}/wiki/export.xlsx exports every processed Wiki
page, including its full directory path, current directory, parent-directory
path, one column per directory level, metadata, and complete Markdown content
(split across continuation columns when Excel's per-cell limit is reached).
Authenticated operations are available below /api/llmwiki/wiki-index and
POST /api/llmwiki/wiki-vector-search. The service-to-service endpoints below
/api/external/llmwiki/wiki require X-API-Key, are rate-limited, and only
expose mappings that are both internally published and explicitly marked
external_search_enabled; the system 对话沉淀 mapping is always denied.
A separate browser-friendly endpoint, POST /api/knowledge/vector-search, is
fully anonymous: it requires neither a DeerFlow token nor X-API-Key, has no
rate limit, and responds with Access-Control-Allow-Origin: *. It searches the
local vectors of every published knowledge base regardless of
external_search_enabled, while still excluding the system 对话沉淀 base.
The minimal request is {"query":"如何部署"}; optional fields are top_k
(default 10, maximum 100) and include_page_content (default false). Each
matched vector section is returned as its own ranked result, including the
matched text, score, DeerFlow and WeKnora knowledge/Wiki ids, page metadata,
raw source/chunk references, best-effort document/chunk ids, and a directly
usable wiki_url. Set llmwiki.local_wiki_index.frontend_base_url to the
deployed frontend origin/base path; its development default is
http://127.0.0.1:5174.
See docs/LLMWIKI_DEERFLOW_LOCAL_VECTOR_INDEX_ZH.md for configuration,
security boundaries, rollout, and API contracts.
Document rewrite fallback
Whole-document rewrite commits the artifact/report before it attempts to store
the optional reversible before-image. If that version-snapshot write fails, the
rewritten content remains available and Gateway logs the exception; it never
restores the original solely because undo history is unavailable. The durable
result marks versionSnapshotFailed, allowing the sandbox to enable its normal
manual-save action without showing the user an error notification.
The optional rewrite-history hydration endpoints scope results to the authenticated job owner. They deliberately return an empty list rather than performing a second thread-owner check, so a legacy guest/SSO display alias cannot make an otherwise usable sandbox report fail with a history-only 404.
Artifact and rewrite path resolution uses the persisted thread creator's
directory when metadata exists, after the route's normal ownership check. This
prevents a missing request context from incorrectly looking under
users/default and returning 404 for an existing sandbox file.
The final rewrite commit uses a deliberately short sibling temporary filename,
which keeps long Windows artifact paths below the path-length limit. It also
recreates a missing outputs parent before retrying, so transient
sandbox-directory recreation cannot discard a completed rewrite.
Offline local-sandbox Office runtime
The pure DeerFlow offline image treats the backend container as the default
LocalSandboxProvider runtime. It bundles MarkItDown, python-docx, openpyxl,
python-pptx, LibreOffice Headless, Pandoc, Poppler, and CJK fonts. Modern OOXML
uploads are parsed directly; legacy DOC/XLS/PPT and ODT/ODS/ODP files are
normalized through an isolated LibreOffice profile before Markdown extraction.
Run ./OFFLINE_INSTALL.sh --office-check from the offline bundle to generate
and re-parse Word, Excel, and PowerPoint samples without requiring a database.
Skill knowledge distillation and assistant knowledge
The Gateway now exposes an administrator-only skill distillation pipeline at
/api/skill-knowledge. Each request can send up to 200 skills to one writable
WeKnora Wiki knowledge base; multi-base distillation is done by repeated
operations so the local binding remains skill_name + target_id. The scanner
never reads or uploads code/script files, strips Markdown code fences, blocks
credential-like files and content, safely inspects ZIP members, and converts
supported Office/PDF/spreadsheet files off the event loop. Jobs use database
claims and renewable leases, stage snapshots before activation, retain the
prior active version on failure, support per-item retry, and keep per-binding
contribution records for safe detach/delete. Direct assistant-base targets,
non-Wiki/FAQ/conversation/readonly targets, and direct Neo4j writes are rejected
for this product version.
/api/assistant-knowledge manages DeerFlow-native assistant knowledge bases.
There is one global full-library base named 知识梳理总库, plus admin-created
custom assistant bases. Reads and search are available to authenticated users;
global initialization, custom-base creation, WeKnora export/import, source
reimport, and source deletion are admin-only. Assistant bases only accept
WeKnora export packages or imports triggered from a WeKnora mapping; direct
skill import and arbitrary local-file import return 410. Importing into a custom
assistant base also mirrors the same package into the global base, where
canonical entities and relations are aligned across all imported sources.
The database schema is introduced by Alembic revision 20260901_01.
Wiki 流转与本地小批量编码
所有登录用户都可在「技能 → 归纳到知识库」中多选自己可见的技能,并归纳到一个 自己可写的 WeKnora Wiki 知识库;用户只能看到和维护自己发起的归纳记录,管理员 接口仍可查看全量记录。技能归纳上传清理后的知识正文,等待 WeKnora 完成 Wiki 分析,再建立本地 Wiki 向量并通过落盘导出包自动沉淀到「知识梳理」。内置技能没有数据库所有权记录 也可以归纳。若解析结束却没有 Wiki,会提示检查生成模型和资料;文档解析完成 不等于 Wiki 生成成功,例如生成服务余额不足仍可能表现为文档已完成。
管理员可以在普通知识库直接生成向量数据包,每次操作有独立后台记录和下载入口。 助手知识库支持上传包、异步导入和导出;新建空的普通 Wiki 库可上传助手导出包。管理员可编辑助手知识库名称与描述,也可删除自建助手知识库及其 Wiki、向量、实体、关系、导入记录和落盘数据包;承担全库沉淀的「知识梳理总库」允许编辑但禁止删除。 包保留正文、别名/标签、目录分类、双向链接、实体关系及 float32 向量。 回流时通过 WeKnora API 重建目录/页面,向量进入 DeerFlow 本地 Wiki 索引, 不直接写 WeKnora 的底层向量或图数据库。普通与助手 Wiki 使用同一个阅读组件。 助手知识库入口只读取库统计,不再因服务重启同步扫描全库;进入具体库时先加载不含 正文/搜索文本的轻量 Wiki 目录,当前文章正文再按 slug 单页读取,因此目录条目很多时 也能先打开页面,且不会在浏览器和 Gateway 中同时保留全库的多份长文本。首批目录 返回后即可阅读默认索引,后续目录分页在后台补齐;直接链接的文章会与目录并行加载。 新建且尚未导入文件的助手知识库仍可正常进入 Wiki 管理页:空目录是合法状态,页面 明确提供上传文件或导入普通知识库数据包的入口,不以“已有来源/Wiki 页面”作为路由 前置条件。普通知识库的空 Wiki 阅读页同样可直接进入 Wiki 管理并手工创建首篇文章。
小批量 CPU 编码可配置如下,无需额外部署 HTTP 编码服务:
llmwiki:
local_wiki_index:
enabled: true
auto_sync: true
sync_interval_seconds: 30
embedding:
provider: local
model: bge-embedding-m3
dimensions: 1024
batch_size: 2
local_threads: 2
local_process_isolation: true
# 离线部署时可指定已下载的 BAAI/bge-m3 模型目录:
# local_model_path: /opt/models/bge-m3
本地编码依赖随标准后端安装。将完整 BGE-M3 模型目录提前放到离线服务器,并通过
local_model_path 或 DEERFLOW_BGE_M3_MODEL_PATH 指向它。管理员也可在
「设置和更多 → 设置 → Wiki 镜像编码器」中选择服务器目录,或从浏览器上传完整模型目录;
页面配置持久化到运行目录的 system_settings.json,上传文件落到
.deer-flow/models/bge-m3-revisions/<revision> 的不可变版本目录,均不进入源码仓库。系统强制 local_files_only,
不会访问互联网或下载模型。管理员可在同一设置页点击「一键启动编码器」异步加载;该操作不挂在应用启动流程,加载失败不会
中断 DeerFlow 主服务。默认由独立低优先级子进程承载模型内存,正常停止 DeerFlow
时会一并回收该子进程;编码进程异常退出只会将当前向量任务标记失败。模型目录应包含
onnx/model.onnx(官方权重约 2.3 GB)及对应 tokenizer/config 文件。权重不进入业务源码仓库。
运行时编码器模式持久化为 configured 或 local。默认 configured 严格使用
config.yaml 中的 OpenAI、OpenAI-compatible 或 local provider;保存服务器目录或
上传模型会显式切到 local 并热重建 embedding/search/sync/retrieval 服务,不需要
重启 Gateway。POST /api/llmwiki/local-encoder/use-configured 可立即恢复配置文件中的
provider。GET /api/llmwiki/local-encoder/status 在远程 provider 下也正常返回当前
provider/mode,不再把“不是 local”当成状态接口错误。每次激活本地模型都会生成新的
模型修订指纹,避免同一路径替换权重后误复用旧向量。切换后的旧编码器会给在途任务
保留短暂宽限期,随后等待当前编码调用结束并自动回收模型进程。
浏览器目录上传采用磁盘暂存和 4 MB 分块复制,页面显示上传进度;经 Nginx 等反向 代理部署时仍需把请求体上限和读写超时配置到可承载完整模型目录。 相同模型指纹、相同内容的页面复用现有向量;短正文/摘要至少保留一个非空分段。 数据包导入不重新编码,模型指纹或维度不一致时明确拒绝,不能只按维度混用模型。 问答编码查询本身仍需可用的同一模型。WeKnora 的原始文档向量不能当作生成后 Wiki 正文的向量使用,两种文本需要分别建立各自的首次索引。
普通知识库检索会将“此知识库都讲了什么”“这个知识空间有哪些内容”等库级问法识别为概览请求:
完整本地索引直接返回若干代表性 正文,不让宽泛提问被 min_similarity 过滤成零结果;尚未完成
索引的库则通过 Wiki 页面目录取得同类正文。目录、索引和摘要页不作为概览或回退候选,也不会以页面摘要
冒充正文命中。
知识空间列表的热路径也做了收敛:助手知识库的来源/Wiki/实体/关系数量由每库 4 次串行 统计改为整批 1 条聚合查询,单纯改名不再扫描内容表;普通知识库复用 WeKnora 登录态, 并用 15 秒短缓存合并多个页面/权限范围同时发起的目录请求,增删改库及文档变化后立即 失效。前端关闭知识空间选择器时不再无条件拉取全量列表,也不再固定每 15 秒轮询空闲库。
助手向量检索先校验编码指纹和维度,再按 256 条矩阵化计算;全量扫描阶段只读取向量与 最小页面标识,最终候选才回查正文。中文无空格问句的关键词降级会拆出有效短语,因此 模型不兼容或暂时不可用时也不会把整句当成唯一关键词而轻易返回 0。
Wiki 页面新增、编辑、删除及文档上传/重解析(包括嵌入式 WeKnora 页面内完成的写操作) 都会按知识库去抖,并按 1/8/30/90/300 秒的有上限退避执行增量重试;远端仍在处理时只读 知识库状态,不枚举整库页面,也不会耗掉最终确认机会。处理结束后至少做一次短间隔静默确认, 首轮早于 WeKnora 异步抽取完成时后续轮次仍会接住新页面。周期目录预检与 10 分钟补扫继续 作为兜底,但任何补扫都不会在远端处理期退休缺页或覆盖完整图快照。每次成功 向量化后自动把该普通 知识库的最新完整快照同步到「知识梳理总库」,包括服务重启前已有库和新建库;来源 页面被删除时也会在总库中退役,不保留失效 Wiki。同一 Gateway 内会按 mapping 合并同步触发;跨 Gateway worker 的导入事务统一先锁定目标助手库基表行,再进行图对齐和来源快照替换,避免不同 worker 以不同顺序持有从属表锁。数据库报告瞬态锁等待超时、死锁或序列化冲突时,会在完整回滚后按有界退避重放整个导入事务,而不会把已经完成的 Wiki 向量化误报为永久的知识梳理失败。
「知识梳理总库」不是按来源简单堆叠:导入时会规范化全/半角、空白、标点及常见实体 类型,并利用实体别名对齐同一人物、组织、产品或概念;不同来源中同标题的实体 Wiki 只保留一个规范页面,正文按去重后的知识块合并,来源与实体/关系贡献仍分别留痕。 重复上传同一个 WeKnora 导出包时,以包内稳定知识库 ID 识别同一来源并执行快照替换, 不会再产生一批新的片段副本。已有历史重复数据会在总库首次读取、检索、导出或下次 同步时自动归并。助手知识检索排除目录、索引和摘要页;关键词只查标题与知识正文, 向量检索返回实际命中的正文片段,问答上下文和检索卡片均不再用摘要代替知识。
聊天中一旦选择普通或助手知识空间,本轮进入严格知识空间模式:Gateway 与 Lead Agent
双层关闭 Web、浏览器、检索型 MCP、外部检索技能、ACP 与子智能体,不允许模型用常识
补齐库内没有的事实。llmwiki_search 兼容名现在同时支持普通与助手知识空间,并且只能
读取线程上下文中的已选 ID。普通 Wiki 同时执行向量与标题/摘要/正文关键词召回并以 RRF
融合;低于向量阈值时返回关键词结果以及 raw top score、阈值过滤数和 fallback_reason。
新对话选择空间后,输入框会根据库名展示 4 个可直接提问的问题;知识空间建议存在时不再
混排全局通用推荐问题。
知识空间新增管理员专用的「检索调试」页面(/page/workspace/knowledge/retrieval-debug):可同时选择普通
Wiki 库和助手知识库,输入问题后调用 POST /api/llmwiki/retrieval-debug。全局调试接口与单库
POST /api/llmwiki/knowledge-bases/{id}/retrieval-debug 均在检索启动前校验管理员身份,普通用户
即使直接请求也会收到 403。该接口复用真实问答
的回答前检索函数,不是单独的向量测试接口;响应会给出所选库、实际模式(向量/关键词/
Wiki API/原文分块/混合)、候选数、阈值和降级原因,以及最终送给模型的限长正文。普通问答的首次 SSE
metadata 事件同时带 knowledge_retrieval_trace;回答结束后也可在
GET /api/threads/{thread_id}/reference-batches 的 retrieval_traces 中复查,包括零命中轮次。
因此浏览器 F12 可以区分“没有选库”“索引未就绪”“向量低于阈值”“关键词未命中”和检索服务
不可用,而不再把所有情况都表现为没有参考资料。旧版或嵌入式客户端若后续轮次省略选库字段,
Gateway 会沿用线程已持久化的选择;显式传空列表仍表示本轮不检索知识空间。
普通知识库现在并行检索两层证据:生成后的 Wiki 正文页/分段,以及 WeKnora 原始上传文档 chunk;两层都命中时用 RRF 做跨通道融合,最终仍最多向回答上下文提供 8 条、每条 1400 字。 原文通道只使用通过 DeerFlow 权限校验后映射出的远端库 ID,并再次丢弃上游返回的未知库结果。 完整本地 Wiki 索引使用向量与关键词并行的混合召回;索引未就绪时走本地关键词和 Wiki 页面 API。助手知识库也会并行召回向量与关键词/原文 chunk,再以加权 RRF 融合;精确设备码、事件码 等字面标识优先,因此即使已有 Wiki 向量,普通库导入的原文 chunk 也不会被隐藏。两类知识库都 不能宣称每轮都是纯向量检索。
标题优先排序:普通 Wiki、原文切片和助手知识库的检索融合都会提取用户问题中的
主题短语,并把标题/别名命中作为有界的高权重信号;因此查询「海湖庄园」时,标题为
「海湖庄园」的文档通常会优先于只在正文提到该词的「特朗普」文档,同时不会让弱标题
子串无条件压过高度相关正文。普通 Wiki 的本地
向量索引可在 llmwiki.local_wiki_index 中通过 title_boost_enabled 和
title_boost_weight 调整(默认 true / 0.25)。实体扩展产生的关联词只用于
召回,不会冒充原始问题的标题命中。
点击知识库引用的「查看详情」后,抽屉顶部先展示该次检索实际命中的切片,再展示摘要和
完整文档/Wiki 正文。对话底部的后续问题只在消息列表处于最下方时展示;用户上滑查看
历史消息时自动隐藏,回到最下方后恢复。
同步器会把 Wiki 入链、出链、正文 [[wikilink]] 和 provider graph 归并成库内有向边;回答前以
混合召回页面为种子做有界图扩展,默认 1 跳,问题明确要求多跳或首轮向量低置信时最多 2 跳。
助手知识库也会沿已导入的实体关系扩展:关系两端通过 entity 的 wiki_page_id 回到原始 Wiki
正文页,只读取所选助手库内仍为 active 的关系、实体和页面,不跨库追边。助手库图扩展使用同样
的跳数、候选和去重边界;直接召回但未入 seed 的中间页仍可作为下一跳,却不会重复占用上下文。
图通道最多检查 24 个候选,种子、每库接受数和最终上下文图槽都有硬上限,并以衰减传播分排序;
扩展页必须有真实正文和可打开引用,未知库或越权节点会被再次过滤。调试 trace 会给出种子、候选、
接受数、实际跳数、路径和独立耗时;存储接口可用时 capabilities.wiki_graph_expansion=true,图通道
失败只标记 partial,不会丢弃 Wiki、原文 chunk 或助手库已经命中的证据。
大包采用流式 JSONL 落盘、同盘临时 SQLite 分阶段读取及异步导入,避免一次性将 整个包加载进内存。部署需预留包、暂存索引和业务数据库空间,并配置代理上传大小 与超时。当前不宣称断点续传或 GB 级压力验收已完成。
数据库建表失败时的启动降级
Gateway 启动时会尽力执行应用 ORM、LangGraph Checkpointer 和 Store 的自动建表, 但任意一张表因权限、数据库版本或 DDL 兼容性创建失败时,不再终止整个后端进程。 应用 ORM 会保留现有数据库连接和已有表继续提供服务;缺失表对应的接口可能暂时不可用, 错误会完整写入后端日志。Checkpointer 或 Store 初始化失败时会自动降级为进程内存实现, 因此智能问答仍可临时运行,但降级期间的新会话状态在服务重启后不会保留。默认岗位和 上传策略的启动填充也采用非阻塞方式,上传策略失败时使用代码内默认值。
该机制是可用性兜底,不替代数据库修复。部署后仍应根据日志中的
Automatic ORM schema creation failed、in-memory fallback 或启动填充失败信息修复
数据库权限和表结构,并重启服务恢复持久化能力。
openGauss / GaussDB application database
The application ORM can use a PG-compatible openGauss/GaussDB database through
the official opengauss-sqlalchemy async dialect. Install the optional driver
set before enabling it:
uv sync --extra gauss
Configure the business database independently from the LangGraph checkpointer:
database:
backend: gauss
gauss_url: $GAUSS_DATABASE_URL
checkpointer:
type: sqlite
connection_string: .deer-flow/data/deerflow.db
GAUSS_DATABASE_URL should normally be
opengauss+asyncpg://user:password@host:26000/deerflow. Plain
opengauss://, gaussdb://, and PostgreSQL-style URLs are accepted and
normalized to the openGauss dialect. Do not configure the LangGraph
PostgreSQL checkpointer against GaussDB: its migrations are PostgreSQL-specific.
For a production image, build with --build-arg UV_EXTRAS=gauss.
问答记忆开关
问答记忆默认关闭。关闭时,普通问答不会加载记忆中间件、不会注入历史记忆,也不会在回答结束后调用模型提取或写入记忆。管理员可在「设置 → 问答记忆」中显式开启;开关保存在运行目录的 system_settings.json,新部署或没有该字段时始终按关闭处理。
2026-09-14 我的项目与知识库验收修复
- 简洁模式的「我的项目」列表、策略工作台和普通问答项目恢复工作区功能导航;窄屏提供展开菜单按钮。
- 普通问答先创建具名项目,再在项目内创建多个对话。列表读取
/api/analysis-projects的qa项目,不再读取、标记或自动归入全局普通聊天。 - 项目设置更新保留既有知识库绑定。原「项目空间」与官方入口继续可用;我的项目下问答路由为
qa/projects/groups/*。 - 策略生成卡片统一先显示思考/执行过程,再显示正文;评判、方案及展开视图沿用相同顺序。
- 完成评判但尚未勾选策略的项目可以从方案列表进入,在工作台选择后生成;已保存方案在任务修改后仍可打开。旧列表请求不再覆盖新请求状态。
- 策略优选终稿不依赖临时系统 thread 写文件;
parallel_agent_tasks.messages内版本化 snapshot 的synthesisStatus=done且synthesis非空时,资料库将其投影为一份可搜索、可下载的 Markdown 文档。workflowStale=true只标记为历史方案,不删除或禁止下载。普通分析项目 thread 的analysis_project_mode=strategy文件也以策略来源展示。 - 知识库管理页读取上游最新目录,避免远端删除后命中旧缓存;检索调用仍共享短期目录缓存。
- 管理员,或管理员已切换到
v2后的普通用户,访问尚未初始化的助手知识库时显示明确空态及刷新按钮。
验证:后端相关回归 273 项、前端策略/项目回归 31 项通过;真实 BAAI/bge-m3 编码、指定库向量检索与草稿隔离通过;本地隔离服务的问答项目创建/分组、真实问答、方案整理、助手库创建/编辑/文件入库/向量检索/详情均通过。完整验收记录见仓库 docs/ji-knowledge-projects-acceptance.md。不在源码或文档中保存接口密钥。
2026-09-14 日常使用复查:补修项目历史入口与移动侧栏、默认智能体同步、项目加载重试、助手知识检索过期响应隔离及无结果指引;更新 ScrollArea 的 React 19 兼容修复。后端再次 273 项通过,前端 33 项通过。范围及限制见仓库 docs/daily-usage-audit-20260914.md。
2026-09-20 知识库扩展、统计检索与检索测试
普通 WeKnora Wiki 知识库支持按库维护说明、默认测试问题和检索设置。旧库自动提供四个测试问题;所有者可在知识库卡片的「编辑」中逐行调整,保存后用户选择该库时直接展示。问“这个知识库讲了什么”时,问答工具同时返回库说明与实际 Wiki 正文引用,不以说明代替正文证据。
llmwiki_search 在原混合检索上增加一跳 Wiki 关系扩展:先识别问题中的关注对象,再从命中页的别名、[[Wiki 链接]] 和 Markdown 链接生成有限扩展词,独立检索后使用 RRF 融合,原问题通道权重最高。日期/数量问题由后端解析和计数;支持 YYYY-MM-DD、YYYY年M月D日、M月D号到M月D号 及当月「几号到几号」。存在带稳定标识的 source_refs 时按新闻/来源去重计数,否则明确回退为 Wiki 页面数;日期缺失的记录不会悄悄进入日期区间统计,而会单列 undated_excluded_count。
知识库详情顶部的「检索测试」进入 /page/workspace/knowledge/{id}/retrieval-debug。页面调用 POST /api/llmwiki/knowledge-bases/{id}/retrieval-debug,返回 request_id、识别对象、日期范围、扩展词、实际子查询、确定性统计、具体明细、Wiki 参考文献、通道诊断和警告。浏览器 F12 的 Network 面板可直接检查完整响应,用于定位“问答说未查到数据”究竟是索引、阈值、日期元数据还是引用缺失。
回答评价与用户反馈分
每条持久化的智能体回答可通过
PUT /api/threads/{thread_id}/messages/{message_id}/feedback 提交优秀/不佳评价;
不佳评价支持“不正确或不完整、与期望不符、速度慢或存在问题、风格或语气、
安全或法律疑虑、其他”六类稳定原因及可选详情。服务端从消息事件解析 run 和智能体,
不接受客户端把评价归到另一个智能体;历史页可用批量
POST /api/threads/{thread_id}/feedback-contexts 一次读取。
仅 system_role=admin 的管理员可调用
GET /api/agents/{agent_id}/feedback-score;接口返回 0–100 的用户反馈回答质量分、
原始好评率、样本量/置信度、五项任务质量维度和问题原因分布。总分使用
Beta(8,2) 先验抑制小样本极值;零样本返回 score: null 和
insufficient_data,不会把先验当成真实成绩。该指标不调用额外模型,也不替代对
规划、推理轨迹、技能/工具使用、延迟、可靠性或安全性的专项评测。完整契约与公式见
docs/AGENT_ANSWER_FEEDBACK_SCORING_ZH.md。
用户菜单与管理员默认菜单
全量模式和简洁模式菜单都支持用户个性化。管理员在「默认菜单管理」中只维护共享默认值;
普通用户在「我的菜单」中保存自己的完整菜单快照,不会修改其他账号或管理员默认值。
用户未保存个人配置时直接使用管理员默认;保存个人配置后,快照内已有菜单继续使用用户的
名称、位置、顺序和启停状态,而后续新增、尚未进入快照的菜单自动继承管理员配置。
「恢复默认」只删除当前用户对应的个人快照。简洁模式仍按部署配置的
VITE_COMPACT_MENU_TYPE 隔离 A/B 两套菜单。
技能读取、模型日志与线程池并发优化
实际加载的 config.yaml(DEER_FLOW_CONFIG_PATH 指定文件优先)支持以下四个独立开关。
skill_view_reuse_enabled 和 skill_view_cache_enabled 在配置里不写时默认开启;
要恢复每次工具调用都全量刷新技能视图,把对应项写成 false 后重启。另外两项仍默认关闭:
runtime_performance:
skill_view_reuse_enabled: true
skill_view_cache_enabled: true
model_timing_nonblocking_enabled: true
asyncio_thread_pool_enabled: true
asyncio_thread_pool_size: 100
skill_view_reuse_enabled:一次沙箱工具调用共用一份已验证技能视图,路径检查、 本地/AIO 沙箱初始化、命令转换和输出脱敏不再反复刷新。下一次工具调用重新验证, 不跨问答永久沿用启停状态。skill_view_cache_enabled:工具热路径不扫描整棵技能树。命令没有点名技能时跳过检查; 点名了技能时只核对该技能。技能管理里的保存、删除和上下线会立刻同步这一份。 副本还不存在,或启停配置对不上时,仍做一次完整重建。显式调用的完整刷新仍会核对已知文件的元数据版本。model_timing_nonblocking_enabled:轻量计时回调不再进入工具共享线程池;模型日志 交给独立线程的有界队列输出,不把日志 I/O 搬到事件循环。队列满时丢弃诊断日志, 日志端恢复后汇总LLM_TIMING_LOG_DROPPED,不会为了记录日志阻塞模型输出。asyncio_thread_pool_enabled:开启后,在 Gateway 启动、LangGraph 运行时初始化前配置 asyncio 默认线程池。上限默认取asyncio_thread_pool_size: 100(整数 1..1024),也可由.env的DEER_FLOW_ASYNCIO_THREAD_POOL_SIZE=100覆盖;兼容别名DEERFLOW_IO_THREADS=100。非空值优先级为正式变量 > 别名 > YAML;只需配置一个环境变量。 开关关闭时完全保留 Python 默认线程池,环境变量不会偷偷启用此功能。 开启时选中变量的无效值使启动失败。线程按需创建,不会在启动时立即创建 100 个线程。
修改后重启后台以确保全部运行实例使用一致配置;回退时将相应项设为 false 并重启,
无需删除技能或缓存目录。启动/配置加载日志 RUNTIME_PERFORMANCE 会打印四项开关及 YAML 中的上限。
GATEWAY_ASYNCIO_EXECUTOR 才是线程池最终生效值(含环境变量优先级)及来源;线程池改动必须重启生效。
技能日志 SKILL_VIEW_CACHE 含 status、lock_wait_ms、refresh_ms、total_ms、
源/目标条目数及启用技能数;正常命中为 DEBUG,重建/合并等待为 INFO,配合
LLM_CONNECT_START/OK、LLM_REQUEST_DONE/FAILED 定位耗时。不记录技能正文或密钥。
这些开关不调整 Gateway 进程数、AnyIO 同步 HTTP 线程池、子智能体独立线程池或上游模型配额,也不保证
消除其他同步回调的等待。已有服务器若使用持久化或外挂的 config.yaml,仍需手工同步开关并重启;
仓库中的运行配置不会覆盖部署环境已持久化的配置。
扩大到 100 只提高当前 Gateway 事件循环的同步执行槽位,不代表已验证 100 路问答或 20 人模型并发容量。
流式文件预览与 React 错误保护(2026-09-27 补充)
模型生成 write_file 参数时,前端可预览尚未闭合的 JSON 中的 Markdown;真正的文件写入仍在
工具参数完整并通过校验后执行。流式预览不等于最终文件已保存,也不把半截参数提前写入最终文件。
兼容网关若重复发送完整工具名,适配器现在按调用/工具索引去重,避免把 write_file 拼成
write_filewrite_file;标准的分段工具名保持原语义。既有 OPENAI_TOOL_STREAM_SUMMARY 中的
repeated_tool_names 可确认是否触发了这项兼容处理;日志不输出工具参数正文。
若 OpenAI-compatible 模型只能流式返回普通文本、但网关会缓存较长的 write_file 工具参数,管理员可在
「设置 → 文件流式兼容」打开全局运行时开关。无需重启后台,下一次发起的问答起,所有兼容模型统一使用
buffered_gateway。模型仍调用原名 write_file:第一段请求
只选择路径、说明和追加方式,后端随即在同一逻辑模型调用内发起无工具的普通文本正文流,并把正文
实时投影成与原生模型相同的标准 write_file 参数增量。LangGraph、ToolNode、前端和沙箱最终只看到
一次真实 write_file,不会出现或执行内部准备工具,也不要求模型再遵守 JSON 外壳或自定义分隔符。
普通问答不会触发第二段请求;provider reasoning 或前导 <think>...</think> 进入消息思考区,不会
进入文件。空正文、截断、异常结束或意外工具调用会在沙箱 I/O 前中止。关闭开关后,下一次问答恢复
各模型原有配置(未单独配置时为 native),请求和流式行为保持原样。旧的模型级字段继续兼容,
artifact_renderer 仅为已部署的内部握手模式保留。
Markdown 覆盖生成只有在内部代码围栏配对完整时才移除整篇 md/markdown 包装,避免误删多个
独立代码块的首尾围栏;追加模式保留围栏原文。本地沙箱的文本覆盖写入(包括原生 write_file)
先在目标目录写临时文件并 flush/fsync,再原子替换目标;编码、写盘或替换失败时保留旧文件。
这项落盘保护不覆盖追加写入,也不改变远程沙箱的写入实现。
前端直接流入口改用异步下一任务批处理。安装版本 SDK 的数字 throttle: 16 实际是尾部防抖,
连续快速输出可能一直延迟刷新。React #185 另由流错误分类与专用渲染边界拦截,显示友好提示、
限制自动恢复次数,保留不含问答正文的诊断;边界不会自动重复挂载崩溃页面。
这些保护不代表已经确认线上具体组件的循环更新根因。
会话创建耗时诊断
POST /api/threads 先写应用 threads_meta,再写空检查点。新增 Server-Timing 响应头及
GATEWAY_OPERATION_TIMING 日志区分 metadata_lookup、metadata_create、checkpoint_create;
POST /api/threads/search 记录 metadata_search。计时不包含处理函数启动前的鉴权/代理/事件循环等待。
同步图构建另有 AGENT_BUILD_TIMING(500ms 以上 WARNING,否则 DEBUG)。这些是诊断,不宣称消除排队。
检查点使用哪种数据库由实际配置决定:显式 checkpointer 段优先;type: postgres 但
use_postgres: false 且无环境覆盖时实际使用 SQLite,不能通过归档 PostgreSQL 改善当前写入。
数据库归档 SQL 单独交付给部署方手工执行,不进入源码、应用迁移或启动流程。
必须先确认真实存储后端,并在备份恢复的隔离库演练;归档不能当作接口变慢的必然解决办法。
Large uploaded-file safety
The singleton upload_policy database row is the source of truth for every
chat attachment picker and Gateway upload. The administrator-facing
「设置 → 上传设置」 page edits file count, total bytes, per-category formats and
sizes, plus the large-file threshold and output budgets; every save increments
the policy revision, and reset writes the built-in defaults back to the row.
Model-facing file reads are bounded. Large text and converted documents are
scanned as streams and returned as whole-file statistics plus deterministic
head/middle/tail samples; use read_file line ranges or grep for follow-up
inspection. The active database upload policy controls the sampling threshold
and output budgets. Image analysis validates decoded dimensions and uses a
bounded derivative, so original image bytes are not injected unboundedly.
Each model request also receives a bounded content sample for uploaded text,
Word and PDF files. This manifest is request-only rather than chat history, so
it remains available after provider retries, tool calls and follow-up questions
without accumulating hidden attachment text in the conversation checkpoint.
问答工具等待和结束恢复(2026-09-28)
修复了命令超时后子进程仍持有输出、技能刷新锁无限等待,以及图已结束但元数据写入阻止 SSE
结束的问题。LocalSandbox 支持可选 sandbox.command_timeout_seconds(有限正数,默认仍为 600 秒),
超时会请求终止进程树;技能视图/AIO 操作锁等待上限为 30 秒。
前端普通 SDK 请求也有 30 秒上限,终态恢复会关闭旧 reader,SSE 长任务不受该总时长限制。
上线需要同时更新前后端;配置兼容旧文件。原因、限制和回归入口见
问答卡住排查与修复。
技能缓存首次加载或文件更新时,同一进程内的并发请求现在共享一次刷新和发布校验, 避免每个等待者再次串行遍历整个技能目录。等待仍有 30 秒上限,技能上下线与访问校验不变。 这项优化降低并发重复工作,不会消除数千个小文件在慢磁盘上的单次扫描成本。
问答中的内部待办提醒会继续推动模型完成任务,但不再显示成用户问题;历史会话中的这类提醒 同样从消息列表和问题导航中过滤,用户正常引用提醒文本不受影响。
真实问答、资料库、大技能、20 用户短时并发及最新菲律宾地图技能的实测数据和容量限制见 本地问答与技能并发验收。
力量态势 consumer 技能请求头(2026-09-30)
fs-collect、任务详情、重要时间节点、态势研判、当前判断、重要目标和衔接任务这 7 个
consumer 接口技能,把真实接口地址固定写在脚本第一行,把鉴权固定写在第二行:
# AUTHORIZATION=Bearer admin。运行时会真正读取第二行并发送
Authorization: Bearer admin;命令行显式传入的 --authorization 仍可临时覆盖。
内网 HTTPS 调用继续关闭证书校验并绕过系统代理。fs-collect 与 fs-detail-submit
同时打印脱敏后的 Gateway 请求、入参、HTTP 状态和响应,便于确认业务链是否已经走到详情入库。