开源Agent平台Dify源码剖析系列(三)核心模块core/agent之BaseAgentRunner

每一篇文章都短小精悍,不啰嗦。

笔者寄语

本期介绍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平台Dify源码剖析系列(三)核心模块core/agent之BaseAgentRunner_第1张图片

接下来我们可以从「功能定位」→「核心架构」→「关键流程」→「技术细节」四个层面逐步剖析,结合实际场景理解其设计逻辑。

一、功能定位:代理(Agent)运行的「中枢协调器」

这段代码定义了 BaseAgentRunner 类,是代理聊天应用的核心运行器。它的核心职责是:协调代理(Agent)在处理用户请求时的全流程操作,包括:

  • 整合历史对话与上下文

  • 管理可用工具(如数据集检索工具、第三方 API 工具)

  • 与大语言模型(LLM)交互,生成思考或工具调用指令

  • 记录代理的「思考过程」(如调用了哪些工具、观察到什么结果)

  • 处理模型特性(如流式工具调用、视觉能力)

简单说:当用户向 AI 代理提问时,BaseAgentRunner 就是背后「指挥代理思考、调用工具、生成回答」的总指挥。

二、核心架构:5 大模块的协同设计

类的结构可拆解为 5 个核心模块,彼此配合完成代理的运行逻辑:

模块

作用

关键属性 / 方法

初始化模块

接收外部参数,初始化运行环境

__init__

 方法

工具管理模块

将工具转换为模型可识别的格式,管理工具实例

_convert_tool_to_prompt_message_tool

_init_prompt_tools

历史消息模块

整理历史对话与代理思考记录,构建模型输入的上下文

organize_agent_history

organize_agent_user_prompt

思考记录模块

创建和保存代理的思考过程(如调用工具的输入输出、LLM 使用量)

create_agent_thought

save_agent_thought

模型交互模块

适配模型特性(如流式调用、视觉能力),准备模型输入

模型特性检查(stream_tool_callfiles

三、关键流程:用户提问后,代理如何「思考并行动」?

假设用户提问:「帮我查下本季度产品销量,并总结趋势」,我们结合流程理解代码逻辑:

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_dateend_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),作为上下文输入。

四、技术细节:值得关注的设计亮点

  1. 多租户隔离

    通过 tenant_id 区分不同租户的资源(如工具、数据集),符合企业级应用的隔离需求。

  2. 兼容性设计
    • 对模型不支持的特性(如不支持流式调用)做降级处理;

    • 工具输入输出的序列化容错(try-except 捕获 JSON 序列化错误)。

  3. 可扩展性

    通过抽象方法(如 _repack_app_generate_entity)预留扩展点,方便子类重写以适配不同场景。

  4. 数据库操作优化

    使用 db.session.close() 及时释放连接,避免资源泄漏。

总结:从「用户视角」看整体流程

当用户提问 “本季度产品销量趋势” 时:

  1. BaseAgentRunner

     初始化,加载历史对话和可用工具(如 dataset_search);

  2. 整理上下文(历史对话 + 工具列表),发送给 LLM;

  3. LLM 生成思考:“需要调用 dataset_search 工具,参数为 Q3 的起止日期”;

  4. create_agent_thought

     创建思考记录,调用工具并获取销量数据;

  5. save_agent_thought

     更新记录,包含工具返回的销量数据和 LLM 使用量;

  6. LLM 基于工具结果生成自然语言回答,返回给用户。

整个过程中,BaseAgentRunner 像「中枢神经」一样,协调了上下文、工具、模型、存储四大模块,让代理的行为可追溯、可管理、可扩展。

你可能感兴趣的:(机器智能,人工智能,大模型,Agent,Dify)