MaxKB 技术解析文档

1. 项目定位

MaxKB 是一个企业级智能体平台。它把以下能力组合成一个平台:

  • 知识库 RAG:文档上传、解析、切分、向量化、关键词索引、混合检索、重排、引用展示。
  • 应用发布:简单问答应用和工作流应用都可以发布成在线聊天/API/MCP 工具。
  • 工作流编排:前端用 LogicFlow 画节点图,后端用 WorkflowManage 执行 DAG。
  • Agent 工具调用:AI Chat 节点可以接外部 MCP、自定义工具、工作流工具、已发布应用、Skill 包。
  • 多端接入:在线聊天、API 调用、嵌入组件、企业微信/钉钉/飞书/Slack/触发器等。

一个准确的心智模型是:

text 复制代码
MaxKB = Django 平台后端 + Vue 工作台 + PostgreSQL/pgvector 知识库 + Workflow 图执行器 + LangChain/DeepAgents 工具调用层

它的 "Agent" 主要体现在两个层面:

  • 工作流节点级 Agent:每个节点是一个能力单元,节点之间通过上下文传递数据。
  • 工具调用型 Agent:AI Chat 节点可以挂 MCP/Tool/Skill/Application,模型在对话中决定是否调用工具。

2. 核心技术栈

2.1 后端

源码入口:

  • main.py
  • apps/maxkb/settings/
  • apps/maxkb/urls/
  • apps/chat/
  • apps/application/
  • apps/knowledge/
  • apps/tools/

主要依赖来自 pyproject.toml

  • Web 框架:Django 5.2Django REST Framework
  • 数据库:PostgreSQLpsycopgpgvector
  • 缓存:django-redis
  • 异步任务:Celerycelery-oncedjango-celery-beat
  • 调度:APScheduler
  • LLM 编排:langchainlanggraphdeepagents
  • MCP:langchain-mcp-adapters
  • Embedding:sentence-transformers、各模型供应商适配
  • 文档解析:pypdfpython-docxopenpyxlxlrdBeautifulSoup

2.2 前端

源码入口:

  • ui/src/components/ai-chat/index.vue
  • ui/src/api/chat/chat.ts
  • ui/src/request/chat/index.ts
  • ui/src/views/application/ApplicationSetting.vue
  • ui/src/workflow/

主要依赖来自 ui/package.json

  • 框架:Vue 3ViteTypeScript
  • UI:Element Plus
  • 状态:Pinia
  • 工作流画布:@logicflow/core@logicflow/extension
  • Markdown/公式/图表:markedmd-editor-v3katexmermaid
  • 代码编辑:CodeMirror

2.3 存储与基础设施

  • 主业务数据库:PostgreSQL
  • 向量存储:PostgreSQL 的 vector 类型,配合 pgvector 的余弦距离检索
  • 全文检索:PostgreSQL SearchVectorField + websearch_to_tsquery
  • 缓存:Redis,主要用于 ChatInfo、调试变量、任务锁
  • 异步任务:Celery,用于文档向量化、知识库批量处理、长期记忆抽取等

3. 启动与模块结构

main.py 是后端进程入口,主要做三件事:

  1. 设置 DJANGO_SETTINGS_MODULE=maxkb.settings
  2. collectstatic 收集前端静态资源。
  3. 执行数据库迁移后启动 webtask 服务。

核心业务目录:

text 复制代码
apps/
  application/       应用、工作流、ChatInfo、长期记忆
  chat/              对外聊天 API、MCP server endpoint
  common/            通用工具、文件解析、chunk、锁、响应转换
  knowledge/         知识库、文档、段落、向量、RAG 检索
  models_provider/   模型供应商适配
  tools/             工具、Skill、工具工作流
  ops/               Celery、服务管理
ui/
  src/components/ai-chat/      聊天组件
  src/workflow/                工作流画布、节点配置、校验
  src/views/application/       应用设置、MCP/Tool/Skill 配置

4. RAG 是怎么搭建的

MaxKB 的 RAG 分成两条主链路:

  • 入库链路:文件上传 -> 文档解析 -> 段落/问题生成 -> chunk -> embedding -> pgvector/full-text 索引。
  • 问答链路:用户问题 -> 可选问题改写 -> 检索知识库 -> 可选 rerank -> 组装 prompt -> LLM 生成。

4.1 RAG 数据模型

核心文件:

  • apps/knowledge/models/knowledge.py

关键模型:

  • Knowledge:知识库。
  • Document:文档,属于某个知识库。
  • Paragraph:段落,保存原始内容和 chunks
  • Problem:相关问题/问法。
  • ProblemParagraphMapping:问题与段落的关联。
  • Embedding:向量索引表。
  • Termbase:术语表,用于全文检索分词增强。
  • File:原始文件和图片等附件。

重要字段:

text 复制代码
Paragraph.content      段落文本
Paragraph.chunks       该段落拆出来的 chunk 列表
Embedding.embedding    pgvector 向量字段
Embedding.search_vector PostgreSQL 全文检索字段
Document.status        文档任务状态
Paragraph.status       段落任务状态

Embedding 同时存了两套检索索引:

  • embedding:用于语义相似度检索。
  • search_vector:用于关键词全文检索。

这就是 MaxKB 支持 embeddingkeywordsblend 三种检索模式的基础。

4.2 文档上传与解析

入口文件:

  • apps/knowledge/views/document.py
  • apps/knowledge/serializers/document.py

核心类:

  • DocumentSerializers.Split
  • DocumentSerializers.Create
  • DocumentSerializers.Operate.refresh

文档解析的核心流程:

text 复制代码
用户上传文件
  -> DocumentView.Split / BatchCreate
  -> DocumentSerializers.Split.parse()
  -> file_to_paragraph()
  -> 遍历 split_handles
  -> 命中文件类型对应解析器
  -> 返回段落结构
  -> 创建 Document / Paragraph / Problem / ProblemParagraphMapping
  -> refresh()
  -> 触发 embedding_by_document Celery 任务

file_to_paragraph() 会先把原始文件保存成 File,再通过 split_handles 判断使用哪个解析器。

支持的解析器主要在:

text 复制代码
apps/common/handle/impl/text/
  csv_split_handle.py
  doc_split_handle.py
  html_split_handle.py
  pdf_split_handle.py
  text_split_handle.py
  xls_split_handle.py
  xlsx_split_handle.py
  zip_split_handle.py

apps/common/handle/impl/qa/
  csv_parse_qa_handle.py
  md_parse_qa_handle.py
  xls_parse_qa_handle.py
  xlsx_parse_qa_handle.py
  zip_parse_qa_handle.py

apps/common/handle/impl/table/
  csv_parse_table_handle.py
  xls_parse_table_handle.py
  xlsx_parse_table_handle.py

也就是说,MaxKB 不是只支持普通 txt/pdf,而是区分了普通文档、QA 导入、表格导入、zip 批量导入等多种入库形态。

4.3 切分 paragraph 和 chunk

相关文件:

  • apps/common/utils/split_model.py
  • apps/common/chunk/__init__.py
  • apps/common/chunk/impl/mark_chunk_handle.py

MaxKB 里有两个层次的切分:

  • 文档解析阶段:把文件切成 Paragraph
  • 向量化阶段:把 Paragraph 再切成更小的 chunks

Paragraph.chunks 是数组字段,保存该段落内部的 chunk。默认 chunk 函数是:

text 复制代码
text_to_chunk(text, chunk_size=256)

MarkChunkHandle 使用标点和长度规则切分:

text 复制代码
.{1,chunk_size}[。| |\.|!|;|;|!|\n]

如果文本超出规则无法自然切开,会按最大长度兜底切分。

学习重点:

  • split_model.py 更偏文档结构级切分,比如标题、层级、过滤等。
  • common/chunk 更偏 embedding 前的短文本切块。
  • Paragraph.content 是可展示、可引用的段落。
  • Paragraph.chunks 是为了提升 embedding 检索粒度。

4.4 向量化任务

相关文件:

  • apps/knowledge/task/embedding.py
  • apps/common/event/listener_manage.py
  • apps/knowledge/vector/base_vector.py
  • apps/knowledge/vector/pg_vector.py
  • apps/knowledge/serializers/common.py

触发入口:

text 复制代码
DocumentSerializers.Operate.refresh()
  -> embedding_by_document.delay(document_id, embedding_model_id)

Celery 任务:

text 复制代码
embedding_by_document
embedding_by_document_list
embedding_by_knowledge
delete_embedding_by_document
delete_embedding_by_knowledge

ListenerManagement.embedding_by_document() 做了几件重要的事:

  1. RedisLock 锁住 embedding:<document_id>,避免同一文档重复向量化。
  2. 更新 Document.statusSTARTED
  3. 分页读取 Paragraph,每 5 条一批。
  4. 调用 VectorStore.get_embedding_vector().batch_save()
  5. 每批处理时更新段落状态。
  6. 最后调用 create_knowledge_index() 创建或刷新知识库索引。

BaseVectorStore.batch_save() 会先执行:

text 复制代码
chunk_data_list()
  -> 如果 source_type 是 PARAGRAPH
  -> 使用 Paragraph.chunks
  -> 如果没有 chunks,就 text_to_chunk(text)

然后 PGVector._batch_save()

text 复制代码
texts = normalize_for_embedding(...)
embeddings = embedding.embed_documents(texts)
Embedding.bulk_create(...)

每个 chunk 会变成一条 Embedding 记录,同时写入:

  • embedding=[float(...)]
  • search_vector=SearchVector(Value(to_ts_vector(...)))

4.5 向量数据库与索引

MaxKB 的向量库不是外部 Milvus/Chroma,而是 PostgreSQL + pgvector。

关键文件:

  • apps/knowledge/models/knowledge.py
  • apps/knowledge/vector/pg_vector.py
  • apps/knowledge/serializers/common.py

VectorField.db_type() 返回:

text 复制代码
vector

说明它直接使用 pgvector 类型。

create_knowledge_index() 会为知识库创建索引。代码里有一个重要约束:

text 复制代码
超过 2000 维度不创建索引,因为 pgvector hnsw 索引不支持超过 2000 维度

检索时还有一个细节优化:

text 复制代码
如果同时查多个知识库,不直接 knowledge_id__in
而是按 knowledge_id 逐个 query,再合并排序

原因是每个知识库可能有自己的 partial HNSW index,单独查可以更容易命中索引。

4.6 检索模式

SQL 文件:

  • apps/knowledge/sql/embedding_search.sql
  • apps/knowledge/sql/keywords_search.sql
  • apps/knowledge/sql/blend_search.sql

三种检索:

text 复制代码
embedding: pgvector cosine distance
keywords: PostgreSQL full-text ts_rank_cd
blend: vector score + keyword score

embedding_search.sql 核心:

sql 复制代码
(embedding::vector(%s) <=> %s) AS distance
1 - distance AS comprehensive_score

keywords_search.sql 核心:

sql 复制代码
ts_rank_cd(embedding.search_vector, websearch_to_tsquery('simple', %s), 32)

blend_search.sql 核心:

sql 复制代码
1 - vector_distance + ts_rank_cd(...)

问答时的检索节点:

  • apps/application/flow/step_node/search_knowledge_node/impl/base_search_knowledge_node.py

执行流程:

text 复制代码
BaseSearchKnowledgeNode.execute()
  -> 获取知识库列表
  -> 权限过滤 filter_authorized_ids
  -> 校验多个知识库 embedding_model_id 是否一致
  -> embedding_model.embed_query(question)
  -> VectorStore.query(...)
  -> 查 Paragraph 明细
  -> 计算 directly_return
  -> 返回 NodeResult

返回给后续节点的字段:

text 复制代码
paragraph_list
is_hit_handling_method_list
data
directly_return
question

其中 data 是拼进 prompt 的知识上下文,受 max_paragraph_char_number 限制。

4.7 简单应用的 RAG 问答链路

入口:

  • apps/chat/serializers/chat.py

简单应用走 pipeline:

text 复制代码
ChatSerializers.chat()
  -> chat_simple()
  -> PipelineManage.builder()
  -> 可选 BaseResetProblemStep
  -> BaseSearchDatasetStep
  -> BaseGenerateHumanMessageStep
  -> BaseChatStep

含义:

  • BaseResetProblemStep:问题优化/改写。
  • BaseSearchDatasetStep:查知识库。
  • BaseGenerateHumanMessageStep:组装上下文和用户消息。
  • BaseChatStep:调用模型生成答案,也可以处理 MCP/Tool/Skill。

4.8 工作流应用的 RAG 问答链路

工作流应用不固定走一条 pipeline,而是由用户在画布里组合节点,例如:

text 复制代码
start-node
  -> search-knowledge-node
  -> reranker-node
  -> ai-chat-node

相关节点:

  • search_knowledge_node
  • reranker_node
  • ai_chat_step_node
  • direct_reply_node
  • condition_node
  • parameter_extraction_node

这条链路更灵活,用户可以在检索前加参数抽取,在检索后加重排,也可以命中高相似段落后直接回复。

5. 智能体/工作流是怎么通信的

MaxKB 的工作流不是多个独立进程或服务互相发消息,而是一个 DAG 图执行器在同一后端进程里调度节点。

核心文件:

  • apps/application/flow/workflow_manage.py
  • apps/application/flow/i_step_node.py
  • apps/application/flow/common.py
  • apps/application/flow/step_node/

5.1 前端如何定义工作流

前端文件:

  • ui/src/workflow/index.vue
  • ui/src/workflow/common/data.ts
  • ui/src/workflow/common/validate.ts
  • ui/src/workflow/nodes/

前端用 LogicFlow 创建画布,最终保存的是:

text 复制代码
nodes: 节点列表
edges: 连线列表

每个节点有:

text 复制代码
id
type
properties.stepName
properties.node_data
properties.config.fields
properties.config.globalFields
properties.config.chatFields

data.ts 定义每类节点暴露哪些字段。后端 WorkflowManage.init_fields() 会读取这些字段,用于后续 prompt 变量替换。

5.2 后端如何执行工作流

入口:

  • ChatSerializers.chat_work_flow()

流程:

text 复制代码
ChatSerializers.chat()
  -> 判断 application.type
  -> WORK_FLOW
  -> WorkflowManage(Workflow.new_instance(work_flow), params, ...)
  -> work_flow_manage.run()

WorkflowManage.run()

text 复制代码
if stream:
    run_stream()
else:
    run_block()

流式执行:

text 复制代码
run_stream()
  -> run_chain_async()
  -> await_result()
  -> StreamingHttpResponse

节点执行:

text 复制代码
run_chain_manage(current_node)
  -> run_chain(current_node)
  -> current_node.run()
  -> NodeResult.write_context()
  -> get_next_node_list()
  -> 递归执行后续节点

如果一个节点后面有多个分支:

text 复制代码
ThreadPoolExecutor(max_workers=200)

会并发执行后续分支。

5.3 节点之间如何传数据

核心类:

  • NodeResult
  • INode
  • WorkflowManage.context
  • WorkflowManage.chat_context
  • node.context

NodeResult 有两个变量字典:

python 复制代码
NodeResult(node_variable, workflow_variable)

含义:

  • node_variable:写入当前节点的 node.context
  • workflow_variable:写入全局 workflow.context

默认写入函数在 i_step_node.py

text 复制代码
write_context(step_variable, global_variable, node, workflow)
  -> node.context[key] = step_variable[key]
  -> workflow.context[key] = global_variable[key]

所以节点通信本质是:

text 复制代码
上游节点输出
  -> NodeResult
  -> node.context / workflow.context
  -> 下游节点通过 get_reference_field 读取

5.4 变量引用与 Prompt 渲染

关键方法:

  • WorkflowManage.get_reference_field(node_id, fields)
  • WorkflowManage.get_workflow_content()
  • WorkflowManage.reset_prompt(prompt)
  • WorkflowManage.generate_prompt(prompt)

上下文结构:

text 复制代码
{
  "global": workflow.context,
  "chat": workflow.chat_context,
  "<node_id>": node.context
}

Prompt 渲染使用 Jinja2:

text 复制代码
PromptTemplate.from_template(prompt, template_format='jinja2')

这意味着用户在前端节点里配置的变量引用,最终会变成:

text 复制代码
context.get('<node_id>').get('<field>')

再由模板引擎渲染。

5.5 分支、异常和中断

分支判断:

  • 如果 NodeResult.node_variable 里有 branch_id,说明这是条件/异常分支结果。
  • WorkflowManage._has_next_node() 会根据 edge 的 anchor id 判断走哪条边。

异常处理:

  • 节点配置里有 enableException
  • 如果开启异常分支,异常会写入:
text 复制代码
node.context["exception_message"]
node.context["branch_id"] = "exception"

表单中断:

  • form-node 可以中断工作流。
  • 当表单未提交时,NodeResult.is_interrupt_exec() 会让流程暂停。
  • 用户提交后,可以通过 runtime_node_idnode_data 恢复执行。

5.6 流式输出如何从节点回到前端

核心类和方法:

  • NodeChunkManage
  • NodeChunk
  • WorkflowManage.node_chunk_manage
  • WorkflowManage.await_result()
  • base_to_response.to_stream_chunk_response()

执行过程:

text 复制代码
节点产生 chunk
  -> current_node.node_chunk.add_chunk()
  -> NodeChunkManage.pop()
  -> await_result() yield chunk
  -> StreamingHttpResponse
  -> 前端 reader.read()

每个流式 chunk 不只包含文本,还包含节点元数据:

text 复制代码
node_type
runtime_node_id
node_name
view_type
child_node
node_is_end
real_node_id
reasoning_content
node_status

所以前端可以展示:

  • 当前哪个节点在执行。
  • 节点是否结束。
  • 子应用/循环节点的运行状态。
  • 思考内容。
  • 工具调用渲染。

6. MCP 和 Skill 是怎么应用的

MaxKB 里有三类容易混淆的概念:

  • MCP Server:外部工具服务,或 MaxKB 自己暴露出来的应用工具。
  • Tool:平台内的工具记录,可以是自定义 Python 工具、工作流工具、MCP 工具。
  • Skill:平台内的一种工具类型,通常是上传的 zip/file 包,在运行时解压到临时目录供 DeepAgents 使用。

它和当前 Codex 的 SKILL.md 技能不是一套东西。

6.1 独立 MCP 节点

文件:

  • apps/application/flow/step_node/mcp_node/impl/base_mcp_node.py

执行流程:

text 复制代码
BaseMcpNode.execute()
  -> 判断 mcp_source
  -> referencing: 从 Tool 表读取 tool.code
  -> custom: 使用节点配置 mcp_servers
  -> handle_variables() 替换变量
  -> validate_mcp_transport()
  -> MultiServerMCPClient(servers)
  -> client.session(mcp_server)
  -> s.call_tool(mcp_tool, params)
  -> NodeResult(result, tool_params, mcp_tool)

这个节点适合"显式调用某个 MCP tool"。

6.2 AI Chat 节点里的 MCP/Tool/Skill

文件:

  • apps/application/flow/step_node/ai_chat_step_node/impl/base_chat_node.py
  • apps/application/flow/tools.py

AI Chat 节点更强,它不是固定调用一个工具,而是把工具交给模型,让模型在生成过程中决定是否调用。

BaseChatNode.execute() 做完模型、历史消息、prompt 后,会调用:

text 复制代码
_handle_mcp_request(...)

这个方法会合并多类工具来源:

text 复制代码
mcp_servers       自定义 MCP server JSON
mcp_tool_ids      引用的 MCP 工具
tool_ids          自定义工具/工作流工具
application_ids   已发布应用作为工具
skill_tool_ids    Skill 工具

最后统一交给:

text 复制代码
mcp_response_generator(...)

6.3 自定义工具如何转成 MCP

文件:

  • apps/common/utils/tool_code.py

关键方法:

  • ToolExecutor.generate_mcp_server_code()
  • ToolExecutor.get_tool_mcp_config()

流程:

text 复制代码
读取 Tool.code
  -> AST 解析 Python 函数
  -> 给函数加 @mcp.tool
  -> return 包装成 Result(result, tool_id)
  -> 生成 FastMCP server 代码
  -> gzip + base64 压缩
  -> python -c 启动 stdio MCP server

生成的配置类似:

json 复制代码
{
  "command": "python",
  "args": ["-c", "..."],
  "cwd": "...sandbox...",
  "env": {
    "LD_PRELOAD": "...sandbox.so"
  },
  "transport": "stdio"
}

注意:当前代码里 get_tool_mcp_config() 生成的是 transport=stdio,但 validate_mcp_transport() 只允许 ssestreamable_http。从源码看,这可能在部分调用路径存在兼容风险,实际是否触发要看对应路径是否对生成后的工具配置再次执行校验。

6.4 应用如何变成 MCP 工具

文件:

  • apps/common/utils/tool_code.py
  • apps/chat/views/mcp.py
  • apps/chat/mcp/tools.py

ToolExecutor.get_app_mcp_config(api_key) 会生成:

json 复制代码
{
  "url": "http://127.0.0.1:8080/<chat_path>/api/mcp",
  "transport": "streamable_http",
  "headers": {
    "Authorization": "Bearer <api_key>"
  }
}

MaxKB 自己的 MCP endpoint:

text 复制代码
apps/chat/views/mcp.py
  -> initialize
  -> tools/list
  -> tools/call

MCPToolHandler.list_tools() 暴露一个工具:

text 复制代码
agent_<application_id前8位>

MCPToolHandler.call_tool() 内部会:

text 复制代码
新开 chat_id
  -> ChatSerializers.chat(payload)
  -> 读取 streaming_content
  -> 拼接 operate=True 的 content
  -> 去掉 tool_calls_render 标签
  -> 返回 MCP tool result

这就是"应用作为工具"的实现。一个 MaxKB 应用可以被另一个 MaxKB 应用通过 MCP 调用。

6.5 Skill 如何管理和运行

Skill 相关路径:

  • apps/application/flow/step_node/ai_chat_step_node/impl/base_chat_node.py
  • apps/application/flow/tools.py
  • apps/tools/serializers/tool.py
  • apps/tools/models.py
  • apps/common/utils/tool_code.py

AI Chat 节点里,Skill 通过 skill_tool_ids 选择。

_handle_mcp_request() 对 Skill 的处理:

text 复制代码
for tool_id in skill_tool_ids:
  -> 查询 Tool
  -> 读取 tool.code 作为 file_id
  -> 读取 init_params
  -> 组装 skill_file_items
  -> mcp_servers_config["skills"] = skill_file_items

_initialize_skills() 对 Skill 的处理:

text 复制代码
mcp_config = json.loads(mcp_servers)
if "skills" in mcp_config:
  -> 查询 File
  -> file.get_bytes()
  -> zip 解压到 /tmp/<chat_id>/skills
  -> 校验 zip 内路径,禁止 .. 和绝对路径
  -> 把 params 写入每个顶层目录的 .env
  -> chmod
client = MultiServerMCPClient(mcp_config)

随后 _yield_mcp_response()

text 复制代码
create_deep_agent(
  model=chat_model,
  backend=SandboxShellBackend(root_dir=temp_dir, virtual_mode=True),
  skills=["/skills"],
  tools=tools,
  checkpointer=MemorySaver()
)

这里 Skill 的本质是:

text 复制代码
上传的技能包文件
  -> 运行时解压
  -> 作为 deepagents skills 目录
  -> 配合 MCP tools 一起给 Agent 使用

6.6 DeepAgents 执行层

文件:

  • apps/application/flow/tools.py

关键函数:

  • _yield_mcp_response()
  • mcp_response_generator()

流程:

text 复制代码
mcp_response_generator()
  -> 创建 /tmp/<chat_id>/skills
  -> 在全局 asyncio loop 里执行 _yield_mcp_response()
  -> Queue 把 async chunk 转同步 generator
  -> 外层工作流按普通流式响应消费

_yield_mcp_response()

text 复制代码
_initialize_skills()
  -> MultiServerMCPClient.get_tools()
  -> create_deep_agent()
  -> agent.astream(..., stream_mode="messages")
  -> 解析 AIMessageChunk / ToolMessage / tool_call_chunks
  -> yield 内容给前端

这里做了不少兼容处理,例如:

  • Qwen/OpenAI 兼容流式 tool_call_chunks。
  • 有些模型先发 {} 占位,再发真实 JSON fragments。
  • invalid_tool_calls 里也可能有中间 JSON 片段。
  • 需要把 tool call id 归一化,避免 ToolMessage 找不到对应调用。

这是 MaxKB Agent 工具调用链路里非常复杂、也非常有价值的一段代码。

7. 状态管理

MaxKB 的状态管理按生命周期分层,不是一个单一 store。

7.1 工作流运行时状态

文件:

  • apps/application/flow/workflow_manage.py
  • apps/application/flow/i_step_node.py

运行时状态:

text 复制代码
WorkflowManage.context       全局变量
WorkflowManage.chat_context  会话变量
node.context                 节点变量
WorkflowManage.answer        当前回答文本
WorkflowManage.answer_list   分段回答
WorkflowManage.future_list   并发执行 future
NodeChunkManage              流式 chunk 队列

节点输出通过 NodeResult 写入这些上下文。

7.2 聊天会话状态

文件:

  • apps/application/serializers/common.py
  • apps/application/models/application_chat.py

核心对象:

  • ChatInfo
  • Chat
  • ChatRecord

ChatInfo 是运行时聊天快照,会缓存到 Redis:

text 复制代码
ChatInfo.set_cache()
ChatInfo.get_cache(chat_id)
timeout = 30 分钟

它保存:

text 复制代码
chat_id
chat_user_id
chat_user_type
knowledge_id_list
exclude_document_id_list
application_id
chat_record_list
debug

最终持久化:

text 复制代码
application_chat
application_chat_record

ChatRecord.details 是理解工作流执行细节的关键字段,它保存每个节点的运行详情、输入输出、耗时、状态、错误等。

7.3 会话变量状态

ChatInfo.set_chat_variable()

  • 非 debug:写入 Chat.meta
  • debug:写入 Redis cache

这说明 MaxKB 把"长期会话变量"和"调试态临时变量"分开存储。

变量赋值节点相关:

  • apps/application/flow/step_node/variable_assign_node/impl/base_variable_assign_node.py

它可以写:

  • 全局变量
  • chat 变量
  • 输出变量
  • 循环上下文变量

7.4 文档任务状态

文件:

  • apps/knowledge/models/knowledge.py
  • apps/common/event/listener_manage.py

Document.statusParagraph.status 是字符串,但内部用 Status 类按任务类型拆分。

常见任务:

  • EMBEDDING:向量化
  • GENERATE_PROBLEM:生成相关问题
  • SYNC:同步
  • TOKENIZE:分词/索引

ListenerManagement.update_status() 会批量更新状态和统计数据。

任务去重:

text 复制代码
RedisLock("embedding:<document_id>")
celery-once once={"keys": ["document_id"]}

这能避免重复向量化造成资源浪费和脏数据。

7.5 长期记忆状态

文件:

  • apps/application/long_term_memory/__init__.py
  • apps/application/models/application_chat.py

模型:

text 复制代码
ApplicationLongTermMemory
  application
  chat_user_id
  memory

触发位置:

  • WorkFlowPostHandler.handler()

每轮对话结束后:

text 复制代码
extract_long_term_memory.apply_async(..., countdown=1)

长期记忆支持:

  • 按轮数触发
  • daily
  • weekly
  • monthly
  • interval
  • cron

抽取逻辑会读取历史 ChatRecord,结合已有 memory,用模型生成结构化记忆,再写回 ApplicationLongTermMemory

7.6 Agent 工具调用状态

DeepAgents 里使用:

text 复制代码
MemorySaver()
thread_id = chat_id

这让工具调用过程中 Agent 的中间状态能按 chat_id 维持。与此同时,MaxKB 外层仍然把最终结果落到 ChatRecord.detailsanswer_text_list

8. 前端、后端、Agent 如何通信

这是从用户点发送按钮到模型返回答案的完整链路。

8.1 前端打开会话

文件:

  • ui/src/components/ai-chat/index.vue
  • ui/src/api/chat/chat.ts

前端先调用:

text 复制代码
openChatId()
  -> getOpenChatAPI()(application_id)
  -> 返回 chat_id

聊天组件把 chat_id 存到:

text 复制代码
chartOpenId.value

8.2 前端发送消息

chatMessage() 构造请求:

json 复制代码
{
  "message": "用户输入",
  "stream": true,
  "re_chat": false,
  "form_data": {},
  "image_list": [],
  "document_list": [],
  "audio_list": [],
  "video_list": [],
  "other_list": []
}

调用:

text 复制代码
getChatMessageAPI()(chartOpenId.value, obj)

ui/src/api/chat/chat.ts

text 复制代码
postStream(`${prefix}/chat_message/${chat_id}`, data)

ui/src/request/chat/index.ts

text 复制代码
fetch(url, {
  method: "POST",
  body: JSON.stringify(data),
  headers: {
    "Content-Type": "application/json",
    "AUTHORIZATION": "Bearer <token>",
    "Accept-Language": language
  }
})

8.3 后端聊天入口

文件:

  • apps/chat/views/chat.py
  • apps/chat/serializers/chat.py

入口:

text 复制代码
ChatView.post(request, chat_id)
  -> ChatSerializers(...).chat(request.data)

ChatSerializers.chat()

text 复制代码
校验 ChatMessageSerializers
  -> get_chat_info()
  -> get_application()
  -> get_chat_user()
  -> 校验 chat_id / 权限 / 访问次数
  -> SIMPLE: chat_simple()
  -> WORK_FLOW: chat_work_flow()

8.4 简单应用通信链路

text 复制代码
前端 fetch
  -> ChatView.post
  -> ChatSerializers.chat_simple
  -> PipelineManage
  -> BaseSearchDatasetStep
  -> BaseGenerateHumanMessageStep
  -> BaseChatStep
  -> LLM / MCP / Tool / Skill
  -> StreamingHttpResponse
  -> 前端 reader.read()

8.5 工作流应用通信链路

text 复制代码
前端 fetch
  -> ChatView.post
  -> ChatSerializers.chat_work_flow
  -> WorkflowManage.run()
  -> run_stream()
  -> run_chain_async()
  -> 节点执行
  -> NodeResult.write_context()
  -> NodeChunkManage
  -> StreamingHttpResponse
  -> 前端 reader.read()

如果 AI Chat 节点开启工具:

text 复制代码
AI Chat Node
  -> _handle_mcp_request
  -> 合并 MCP/Tool/Application/Skill
  -> mcp_response_generator
  -> create_deep_agent
  -> agent.astream
  -> tool call / tool result / final answer
  -> NodeResult
  -> 工作流流式响应

8.6 前端如何消费流式响应

文件:

  • ui/src/components/ai-chat/index.vue

关键逻辑:

text 复制代码
const reader = response.body.getReader()
const write = getWrite(chat, reader, ...)
return write()

也就是说,MaxKB 不是用 WebSocket,也不是浏览器 EventSource,而是:

text 复制代码
fetch POST + response.body.getReader() + text/event-stream 格式

后端 chunk 格式大致是:

json 复制代码
{
  "chat_id": "...",
  "id": "...record_id...",
  "operate": true,
  "content": "...",
  "is_end": false
}

工作流模式还会带节点元数据,前端据此更新节点执行状态和回答内容。

9. 复杂点与亮点

9.1 RAG 入库不是简单切文本

亮点:

  • 支持普通文档、QA、表格、zip、多格式解析。
  • 保留原始 File,方便溯源和图片处理。
  • 同时维护 Paragraph.contentParagraph.chunks
  • embedding 和 full-text 两套索引并存。
  • 每知识库 HNSW partial index,检索时按知识库拆开查以利用索引。

复杂点:

  • 文件解析器多,格式差异大。
  • 文档/段落任务状态是复合字符串,需要理解 StatusTaskType
  • chunk 粒度、段落粒度、引用展示粒度不是同一个东西。

9.2 工作流是平台核心,而不是附加功能

亮点:

  • 前端节点配置与后端节点执行高度对应。
  • 节点暴露字段可被后续节点引用。
  • 支持条件分支、异常分支、循环、表单中断、子应用调用。
  • 流式响应包含节点级状态,方便前端做可视化执行过程。

复杂点:

  • WorkflowManage 承担调度、上下文、流式输出、恢复执行、并发分支等多重职责。
  • node.contextworkflow.contextchat_context 的生命周期不同,新手容易混。
  • 节点恢复执行依赖 runtime_node_idChatRecord.details 和前端传回的 node_data

9.3 MCP/Tool/Skill 统一抽象很强

亮点:

  • 外部 MCP、自定义 Python 工具、工作流工具、已发布应用、Skill 都能统一进入 Agent 工具集合。
  • 自定义工具可动态包装成 FastMCP server。
  • 应用可作为 MCP 工具被另一个应用调用。
  • Skill zip 解包后交给 deepagents,同时支持 .env 初始化参数。

复杂点:

  • MCP transport 有 ssestreamable_httpstdio 多种路径,源码中存在校验与生成配置不完全一致的风险点。
  • 流式 tool call 兼容大量模型差异,tool_call_chunks 解析逻辑复杂。
  • 工具执行涉及 sandbox、临时目录、权限、压缩代码、环境变量,排障难度高。

9.4 状态分层设计清晰

亮点:

  • Redis 缓存 ChatInfo,提升连续对话性能。
  • DB 持久化 ChatRecord.details,便于审计和重放。
  • 工作流运行时上下文只在执行期间存在,避免污染持久层。
  • 长期记忆异步抽取,不阻塞当前回答。
  • 文档任务用 RedisLock + celery-once 双重防重复。

复杂点:

  • 同一个"状态"可能同时存在运行时、缓存、数据库三个副本。
  • 调试态和发布态的变量存储位置不同。
  • 长期记忆触发策略较多,需要结合应用配置理解。

9.5 前后端通信选择务实

亮点:

  • fetch + ReadableStream 实现 POST 流式响应,比 EventSource 更适合带复杂 body。
  • 后端统一输出 text/event-stream,前端统一解析。
  • 工作流节点状态和最终答案共用同一条流,前端体验连贯。

复杂点:

  • 流式内容既有模型文本,又有节点状态、工具调用渲染、思考内容。
  • 前端需要处理半包、结束标记、错误状态、滚动、重新生成、表单提交等。

10. 源码阅读路线

建议按以下顺序读,不要一上来钻进所有节点实现。

第一阶段:跑通聊天主链路

  1. ui/src/components/ai-chat/index.vue
  2. ui/src/api/chat/chat.ts
  3. ui/src/request/chat/index.ts
  4. apps/chat/views/chat.py
  5. apps/chat/serializers/chat.py

目标:理解一次用户消息如何进后端,又如何流式回前端。

第二阶段:理解简单 RAG

  1. apps/application/chat_pipeline/
  2. apps/application/chat_pipeline/step/search_dataset_step/
  3. apps/application/chat_pipeline/step/chat_step/
  4. apps/knowledge/vector/pg_vector.py
  5. apps/knowledge/sql/*.sql

目标:理解简单应用的固定 RAG pipeline。

第三阶段:理解知识库入库

  1. apps/knowledge/views/document.py
  2. apps/knowledge/serializers/document.py
  3. apps/common/handle/impl/text/
  4. apps/common/utils/split_model.py
  5. apps/common/chunk/
  6. apps/knowledge/task/embedding.py
  7. apps/common/event/listener_manage.py

目标:理解文档如何变成 paragraph、chunk、embedding。

第四阶段:理解工作流执行器

  1. ui/src/workflow/common/data.ts
  2. ui/src/workflow/common/validate.ts
  3. apps/application/flow/workflow_manage.py
  4. apps/application/flow/i_step_node.py
  5. apps/application/flow/step_node/search_knowledge_node/
  6. apps/application/flow/step_node/ai_chat_step_node/

目标:理解节点图如何执行,以及节点之间如何通信。

第五阶段:理解 MCP/Tool/Skill

  1. apps/application/flow/step_node/mcp_node/impl/base_mcp_node.py
  2. apps/application/flow/step_node/ai_chat_step_node/impl/base_chat_node.py
  3. apps/application/flow/tools.py
  4. apps/common/utils/tool_code.py
  5. apps/chat/views/mcp.py
  6. apps/chat/mcp/tools.py
  7. apps/tools/serializers/tool.py

目标:理解 MaxKB 如何把外部工具、自定义工具、应用和 Skill 统一给 Agent 调用。

第六阶段:理解状态与持久化

  1. apps/application/serializers/common.py
  2. apps/application/models/application_chat.py
  3. apps/application/long_term_memory/__init__.py
  4. apps/common/constants/cache_version.py
  5. apps/common/event/listener_manage.py

目标:理解 chat、workflow、document task、memory 的状态分别存在何处。

11. 推荐学习切入点

如果你是为了学习 RAG:

text 复制代码
DocumentSerializers.Split
  -> file_to_paragraph
  -> Paragraph.chunks
  -> embedding_by_document
  -> PGVector._batch_save
  -> PGVector.query
  -> BaseSearchKnowledgeNode.execute

如果你是为了学习 Agent 通信:

text 复制代码
WorkflowManage.run_chain_manage
  -> INode.run
  -> NodeResult.write_context
  -> WorkflowManage.get_reference_field
  -> WorkflowManage.generate_prompt

如果你是为了学习 MCP:

text 复制代码
BaseChatNode._handle_mcp_request
  -> ToolExecutor.get_tool_mcp_config
  -> ToolExecutor.get_app_mcp_config
  -> mcp_response_generator
  -> _initialize_skills
  -> create_deep_agent

如果你是为了学习前后端流式通信:

text 复制代码
chatMessage()
  -> postStream()
  -> ChatView.post()
  -> ChatSerializers.chat()
  -> WorkflowManage.await_result()
  -> response.body.getReader()

12. 一句话总结

MaxKB 的核心价值不只是"有 RAG",而是把 RAG、可视化工作流、工具调用、MCP、Skill、应用发布和多端聊天统一到同一个平台模型里。学习它时不要只看某一个节点,要抓住这条主线:

text 复制代码
数据进入知识库,能力进入工具系统,用户请求进入 Chat/Workflow,所有中间结果进入上下文,最终通过流式响应回到前端并持久化。
相关推荐
湘美书院--湘美谈教育1 小时前
湘美谈教育AI赋能系列经验集锦:学好唐诗宋词的点滴心得体会
大数据·人工智能·深度学习·神经网络·机器学习
迦蓝叶1 小时前
【开源自荐】JAiRouter:一个轻量级 AI 模型服务网关的开源实践
java·人工智能·spring·开源·llm-gateway·mass
Java知识技术分享2 小时前
opencode安装ui-ux-pro-max和frontend-ui-ux技能
人工智能·ui·个人开发·ai编程·ux
苏映视官方账号2 小时前
精品案例丨方寸之间,“微” 毫毕现 —— 圆刀机高精度检测工艺优化实例
人工智能·数码相机·视觉检测·制造
Cloud_Shy6182 小时前
解读《Effective Python 3rd Edition》:从练气到老魔(第六章 Item 40 - 43)
android·开发语言·人工智能·笔记·python·学习方法
Sammyyyyy2 小时前
月之暗面 Kimi Code 0.4.0 发布,终端 AI 编码助手全面采用 TypeScript,实现毫秒级启动
前端·javascript·人工智能·ai·typescript·servbay
装不满的克莱因瓶2 小时前
掌握生成对抗网络(GAN)的优化目标与评估指标——从博弈函数到生成质量衡量体系
人工智能·python·深度学习·算法·机器学习
whyfail2 小时前
小米 MiMo Code 开源:能免费用 2.5 模型的 AI 编程 Agent
人工智能
慕木沐2 小时前
【Spring AI + Google ADK 】流式输出时 outputKey 状态缓存失败的问题
人工智能·spring·缓存