开源Agent平台Dify源码剖析系列(三)核心模块core/agent之BaseAgentRunner
本文深入解析了Dify框架core/agent模块的设计与实现。该模块作为Agent系统的中枢协调器,包含BaseAgentRunner基类及多种AgentRunner实现(如CoT、FunctionCalling等)。核心架构分为初始化、工具管理、历史消息、思考记录和模型交互五大模块,协同完成代理运行全流程。文章详细剖析了从用户提问到生成回答的完整过程,包括工具转换、上下文构建、思考记录等关键环
每一篇文章都短小精悍,不啰嗦。
笔者寄语
本期介绍Dify框架的核心模块core/agent。接下来我们一起深入剖析core/agent目录下的所有代码,并以通俗易懂的方式解释。我们需要先了解这个目录的完整结构,然后逐个分析关键文件,最后总结整个Agent框架的设计和工作原理。
首先,让我查看core/agent目录的完整结构:
dify/api/core/agent.├── base_agent_runner.py # Agent框架的基础实现├── cot_agent_runner.py # Chain of Thought (CoT) Agent Runner的实现├── cot_chat_agent_runner.py # CoT Chat Agent Runner的实现├── cot_completion_agent_runner.py # CoT Completion Agent Runner的实现├── entities.py # 定义了Agent框架中的核心实体和数据结构├── fc_agent_runner.py # CoT Completion Agent Runner的实现├── __init__.py├── output_parser│ └── cot_output_parser.py # Chain of Thought输出解析器的实现└── prompt└── template.py # Agent提示模板的实现
我们已经对Dify的Agent系统架构有了初步了解。系统主要包含以下几种Agent Runner:
1. BaseAgentRunner:所有Agent Runner的基类,提供了基础功能如:
-
工具初始化和转换
-
Agent思考过程的创建和保存
-
历史消息的组织和处理
-
文件处理
2. CoT Agent Runner:基于Chain of Thought的Agent实现
-
继承自BaseAgentRunner
-
实现了反应式思考过程
-
支持多轮迭代工具调用
-
使用CotAgentOutputParser解析模型输出
3. Function Calling Agent Runner:基于函数调用的Agent实现
-
也继承自BaseAgentRunner
-
专门处理支持函数调用的模型
4. CoT Chat/Completion Agent Runner:针对不同场景的CoT实现
-
分别处理聊天和完成任务两种场景

接下来我们可以从「功能定位」→「核心架构」→「关键流程」→「技术细节」四个层面逐步剖析,结合实际场景理解其设计逻辑。
一、功能定位:代理(Agent)运行的「中枢协调器」
这段代码定义了 BaseAgentRunner 类,是代理聊天应用的核心运行器。它的核心职责是:协调代理(Agent)在处理用户请求时的全流程操作,包括:
-
整合历史对话与上下文
-
管理可用工具(如数据集检索工具、第三方 API 工具)
-
与大语言模型(LLM)交互,生成思考或工具调用指令
-
记录代理的「思考过程」(如调用了哪些工具、观察到什么结果)
-
处理模型特性(如流式工具调用、视觉能力)
简单说:当用户向 AI 代理提问时,BaseAgentRunner 就是背后「指挥代理思考、调用工具、生成回答」的总指挥。
二、核心架构:5 大模块的协同设计
类的结构可拆解为 5 个核心模块,彼此配合完成代理的运行逻辑:
|
模块 |
作用 |
关键属性 / 方法 |
|---|---|---|
| 初始化模块 |
接收外部参数,初始化运行环境 |
__init__
方法 |
| 工具管理模块 |
将工具转换为模型可识别的格式,管理工具实例 |
_convert_tool_to_prompt_message_tool
、 |
| 历史消息模块 |
整理历史对话与代理思考记录,构建模型输入的上下文 |
organize_agent_history
、 |
| 思考记录模块 |
创建和保存代理的思考过程(如调用工具的输入输出、LLM 使用量) |
create_agent_thought
、 |
| 模型交互模块 |
适配模型特性(如流式调用、视觉能力),准备模型输入 |
模型特性检查( |
三、关键流程:用户提问后,代理如何「思考并行动」?
假设用户提问:「帮我查下本季度产品销量,并总结趋势」,我们结合流程理解代码逻辑:
1. 初始化:接收参数,搭建运行环境(__init__ 方法)
当用户提问触发代理运行时,__init__ 会先完成初始化,核心操作包括:
- 接收核心参数
租户 ID(多租户隔离)、对话实例(
conversation)、模型配置(model_config)、用户消息(message)等。 - 初始化工具与回调
-
加载数据集检索工具(
dataset_tools),用于查询「产品销量数据」; -
初始化回调处理器(如
DifyAgentCallbackHandler),用于实时反馈代理状态(如 “正在检索数据”)。
-
- 检查模型能力
-
通过
model_schema判断模型是否支持「流式工具调用」(stream_tool_call)—— 若支持,可边调用工具边返回结果; -
若模型支持「视觉能力」(
ModelFeature.VISION),则接收用户上传的图片(files)。
-
- 记录思考计数
查询当前消息已有的代理思考次数(
agent_thought_count),用于后续排序。
2. 工具准备:让模型「知道如何调用工具」(工具管理模块)
代理要查销量,需调用「数据集检索工具」。但模型无法直接理解工具的参数格式,因此需要先将工具「翻译」为模型可识别的格式:
-
工具转模型格式:
_convert_tool_to_prompt_message_tool方法将工具(如AgentToolEntity)转换为PromptMessageTool格式,包含:例:数据集检索工具可能被转换为:
PromptMessageTool(
name="dataset_search", description="查询指定时间范围的产品销量数据", parameters={"properties":{"start_date":{"type":"string","description":"开始日期,格式YYYY-MM-DD"},"end_date":{"type":"string","description":"结束日期,格式YYYY-MM-DD"}},"required":["start_date","end_date"]})-
工具名称(
name)、描述(description)—— 告诉模型 “这个工具能做什么”; -
参数定义(
parameters)—— 告诉模型 “调用时需要传哪些参数”(如查询的时间范围start_date、end_date)。
-
-
初始化工具集:
_init_prompt_tools方法整合所有可用工具(应用配置的工具 + 数据集工具),生成tool_instances(工具实例,用于实际调用)和prompt_messages_tools(模型可识别的工具列表)。
3. 上下文构建:让模型「了解历史」(历史消息模块)
模型需要结合历史对话生成回答,organize_agent_history 方法会整理两类历史信息:
- 用户与代理的对话记录
:从数据库查询当前对话的历史消息(
messages),排除当前消息后,按时间顺序整理为用户消息(UserPromptMessage)和代理消息(AssistantPromptMessage)。 - 代理的历史思考过程
:若历史消息中包含代理的思考记录(
MessageAgentThought),则将其拆分为「思考内容」+「工具调用记录」+「工具返回结果」,例如:-
思考内容:
agent_thought.thought(如 “我需要调用销量查询工具”) -
工具调用:
tool_calls(如调用dataset_search时的参数) -
工具结果:
tool_call_response(如工具返回的销量数据)
-
4. 思考与行动:代理如何「调用工具并记录过程」(思考记录模块)
当模型决定调用工具(如 dataset_search)时,需要记录这一过程,以便后续追溯或计费:
- 创建思考记录
:
create_agent_thought方法在数据库中创建一条MessageAgentThought记录,包含工具名称、输入参数、位置序号(position)等初始信息。 - 更新思考记录
:
save_agent_thought方法在工具调用完成后,更新记录:-
补充工具返回结果(
observation); -
记录 LLM 的 token 使用量(
llm_usage),用于计算成本; -
序列化工具输入输出(如将字典转为 JSON 字符串),确保存储格式统一。
-
5. 模型交互:适配模型特性,准备最终输入
最后,BaseAgentRunner 会根据模型特性,调整输入格式:
-
若模型支持「流式工具调用」(
stream_tool_call=True),则采用流式方式返回工具调用结果,提升用户体验; -
若模型支持「视觉能力」,则将用户上传的图片(
files)转换为模型可识别的格式(如ImagePromptMessageContent),作为上下文输入。
四、技术细节:值得关注的设计亮点
- 多租户隔离
通过
tenant_id区分不同租户的资源(如工具、数据集),符合企业级应用的隔离需求。 - 兼容性设计
-
对模型不支持的特性(如不支持流式调用)做降级处理;
-
工具输入输出的序列化容错(
try-except捕获 JSON 序列化错误)。
-
- 可扩展性
通过抽象方法(如
_repack_app_generate_entity)预留扩展点,方便子类重写以适配不同场景。 - 数据库操作优化
使用
db.session.close()及时释放连接,避免资源泄漏。
总结:从「用户视角」看整体流程
当用户提问 “本季度产品销量趋势” 时:
BaseAgentRunner初始化,加载历史对话和可用工具(如
dataset_search);-
整理上下文(历史对话 + 工具列表),发送给 LLM;
-
LLM 生成思考:“需要调用
dataset_search工具,参数为 Q3 的起止日期”; create_agent_thought创建思考记录,调用工具并获取销量数据;
save_agent_thought更新记录,包含工具返回的销量数据和 LLM 使用量;
-
LLM 基于工具结果生成自然语言回答,返回给用户。
整个过程中,BaseAgentRunner 像「中枢神经」一样,协调了上下文、工具、模型、存储四大模块,让代理的行为可追溯、可管理、可扩展。
更多推荐

所有评论(0)