震惊!多mcp智能体交互可以这样搞!

201 阅读7分钟

上次讲解了多模型交互的例子:juejin.cn/post/751876… 本次我们拆分各个的prompt,来看一下各prompt做了哪些事,并结合代码进行梳理。 本次代码全部来自于github.com/yincongcyin…

整体流程概述:任务的智能拆解与执行

该自动化流程的核心在于将一个“主要任务”分解为若干可管理的“子任务”,并为每个子任务匹配最合适的执行者。这个执行者可能是特定的多模态计算平台(MCP)服务,也可能是**大模型(LLM)**本身。整个过程呈现为一个循环迭代的模式,直至所有子任务完成并最终汇总结果。

其具体步骤如下:

  1. Prompt拆分任务: 流程伊始,系统会接收一个主任务,并利用一个专业的**“深度研究员”角色**的Prompt,将主任务拆解为一系列自动化子任务。这个“深度研究员”的职责是分析主任务,确定“输出专家”生成最终可交付成果所需的所有数据或信息,并设计详细的子任务执行过程。它还会忽略最终输出形式,只专注于数据的收集与信息的提供。
  2. 子任务分配: 拆分出的每个子任务会根据其需求和MCP服务的不同描述进行智能分配。如果存在适合的MCP服务,则直接分配给该服务;若无匹配,则任务会直接分配给大模型(llm_tool)本身进行处理。
  3. 大模型功能配置: 针对已分配的子任务,系统会为大模型配置不同的function call(函数调用),以确保大模型能够针对性地处理该子任务,获取所需的数据或信息。
  4. 循环询问与判断: 子任务完成后,系统会对大模型进行下一轮的询问,以判断是否还有未完成的子任务。这是一个关键的反馈循环机制,确保任务的持续推进。
  5. 迭代执行: 如果判断有剩余子任务,流程将回到步骤2至4,继续进行子任务的分配、处理和询问。
  6. 结果总结: 当所有子任务都完成后,流程将进入总结阶段,返回与主要任务相关的最终结果。

image.png

核心prompt展示

拆分任务prompt: 角色:\n* 您是一名专业的深度研究员。您的职责是利用一支由专业智能代理组成的团队来规划任务,为“输出专家”收集充分且必要的信息。\n* 输出专家是一名强大的代理,能够生成诸如文档、电子表格、图像、音频等可交付成果。\n\n职责:\n1. 分析主要任务,并确定输出专家生成最终可交付成果所需的所有数据或信息。\n2. 设计一系列自动化子任务,每个子任务都由一个合适的“工作代理”执行。仔细考虑每个步骤的主要目标,并创建一份规划大纲。然后,定义每个子任务的详细执行过程。\n3. 忽略主要任务所需的最终可交付成果:子任务只专注于提供数据或信息,而非生成输出。\n4. 基于主要任务和已完成的子任务,生成或更新您的任务计划。\n5. 判断是否已为输出专家收集到所有必需的信息或数据。\n6. 跟踪任务进度。如果计划需要更新,请避免重复已完成的子任务——只生成剩余的必要子任务。\n7. 如果任务简单且可以直接处理(例如,编写代码、创意写作、基本数据分析或预测),请立即使用 llm_tool,无需进一步规划。\n\n可用工作代理:\n{{range i,i, tool := .assign_param}}- 代理名称:{{tool.tool_name}}\n 代理描述:{{tool.tool_desc}}\n{{end}}\n\n主要任务:\n{{.user_task}}\n\n输出格式(JSON):\n\njson\n{\n "plan": [\n {\n "name": "第一个任务所需的代理名称",\n "description": "执行步骤1的详细说明"\n },\n {\n "name": "第二个任务所需的代理名称",\n "description": "执行步骤2的详细说明"\n },\n ...\n ]\n}\n",

他的返回结果

image.png

循环任务prompt: 主要任务: {{.user_task}}\n\n已完成的子任务:\n{{range task,task, res := .complete_tasks}}\n\t- 子任务:{{$task}}\n{{end}}\n\n当前任务计划:\n{{.last_plan}}\n\n请根据以上信息创建或更新任务计划。如果任务已完成,请返回一个空的计划列表。\n\n注意:\n- 仔细分析上次完成的子任务的完成状态,以确定下一个任务计划。\n- 适当且合理地补充细节,以确保工作代理或工具拥有足够的执行任务的信息。\n- 扩展后的描述不得偏离子任务的主要目标。

可以通过日志看出调用哪些mcp image.png

总结任务prompt: 根据问题,用纯文本格式总结搜索结果和其他参考信息中的要点。\n\n主要任务:\n{{.user_task}}"

deepseek的返回展示:

image.png


为何要根据 MCP 服务区分 Function Call?

根据您提供的信息,之所以要根据 MCP(多模态计算平台)服务的不同来区分 Function Call,主要有两个核心原因:

  1. 避免大模型上下文超限: 大模型(LLM)的请求存在严格的上下文(Context)字数限制。如果将所有的 MCP Function 都直接塞入大模型的请求上下文中,很可能导致上下文长度超出限制,从而无法正常进行处理。
  2. 优化 Token 使用效率: 将大量 MCP Function 塞入上下文会显著增加 Token 的使用量。Token 是衡量大模型计算成本和效率的关键单位,Token 数量的增加意味着更高的成本和更长的处理时间。通过区分 Function Call,系统可以只将与当前子任务最相关的 Function Call 提供给大模型,从而大幅降低 Token 的消耗,提升整体效率。

简而言之,这种区分 Function Call 的策略是为了在保证大模型处理能力的同时,优化资源使用效率,避免不必要的上下文膨胀和Token浪费。


telegram-deepseek-bot 核心方法详解

ExecuteTask() 方法

func (d *DeepseekTaskReq) ExecuteTask() {
    // 设置15分钟超时上下文
    ctx, cancel := context.WithTimeout(context.Background(), 15*time.Minute)
    defer cancel()
    
    // 准备任务参数
    taskParam := make(map[string]interface{})
    taskParam["assign_param"] = make([]map[string]string, 0)
    taskParam["user_task"] = d.Content
    
    // 添加可用工具信息
    for name, tool := range conf.TaskTools {
        taskParam["assign_param"] = append(taskParam["assign_param"].([]map[string]string), map[string]string{
            "tool_name": name,
            "tool_desc": tool.Description,
        })
    }
    
    // 创建LLM客户端
    llm := NewLLM(WithBot(d.Bot), WithUpdate(d.Update),
        WithMessageChan(d.MessageChan))
    
    // 获取并发送任务分配提示
    prompt := i18n.GetMessage(*conf.Lang, "assign_task_prompt", taskParam)
    llm.LLMClient.GetUserMessage(prompt)
    llm.Content = prompt
    
    // 同步发送请求
    c, err := llm.LLMClient.SyncSend(ctx, llm)
    if err != nil {
        logger.Error("get message fail", "err", err)
        return
    }
    
    // 解析AI返回的JSON任务计划
    matches := jsonRe.FindAllString(c, -1)
    plans := new(TaskInfo)
    for _, match := range matches {
        err = json.Unmarshal([]byte(match), &plans)
        if err != nil {
            logger.Error("json umarshal fail", "err", err)
        }
    }
    
    // 如果没有计划,直接请求总结
    if len(plans.Plan) == 0 {
        finalLLM := NewLLM(WithBot(d.Bot), WithUpdate(d.Update),
            WithMessageChan(d.MessageChan), WithContent(d.Content))
        finalLLM.LLMClient.GetUserMessage(c)
        err = finalLLM.LLMClient.Send(ctx, finalLLM)
        return
    }
    
    // 执行任务循环
    llm.LLMClient.GetAssistantMessage(c)
    d.loopTask(ctx, plans, c, llm)
    
    // 最终总结
    summaryParam := make(map[string]interface{})
    summaryParam["user_task"] = d.Content
    llm.LLMClient.GetUserMessage(i18n.GetMessage(*conf.Lang, "summary_task_prompt", summaryParam))
    err = llm.LLMClient.Send(ctx, llm)
}

loopTask() 方法

func (d *DeepseekTaskReq) loopTask(ctx context.Context, plans *TaskInfo, lastPlan string, llm *LLM) {
    // 记录已完成任务
    completeTasks := map[string]bool{}
    
    // 为任务创建专门的LLM实例
    taskLLM := NewLLM(WithBot(d.Bot), WithUpdate(d.Update),
        WithMessageChan(d.MessageChan))
    defer func() {
        llm.LLMClient.AppendMessages(taskLLM.LLMClient)
    }()
    
    // 执行每个子任务
    for _, plan := range plans.Plan {
        // 配置任务工具
        o := WithTaskTools(conf.TaskTools[plan.Name])
        o(taskLLM)
        
        // 发送任务描述
        taskLLM.LLMClient.GetUserMessage(plan.Description)
        taskLLM.Content = plan.Description
        
        // 执行任务
        d.requestTask(ctx, taskLLM, plan)
        completeTasks[plan.Description] = true
    }
    
    // 准备循环任务参数
    taskParam := map[string]interface{}{
        "user_task":      d.Content,
        "complete_tasks": completeTasks,
        "last_plan":      lastPlan,
    }
    
    // 请求AI评估是否需要更多任务
    llm.LLMClient.GetUserMessage(i18n.GetMessage(*conf.Lang, "loop_task_prompt", taskParam))
    c, err := llm.LLMClient.SyncSend(ctx, llm)
    
    // 解析新任务计划
    matches := jsonRe.FindAllString(c, -1)
    plans = new(TaskInfo)
    for _, match := range matches {
        err := json.Unmarshal([]byte(match), &plans)
    }
    
    // 如果有新任务,递归调用
    if len(plans.Plan) > 0 {
        d.loopTask(ctx, plans, c, llm)
    }
}

requestTask() 方法

func (d *DeepseekTaskReq) requestTask(ctx context.Context, llm *LLM, plan *Task) {
    // 同步发送任务请求
    c, err := llm.LLMClient.SyncSend(ctx, llm)
    if err != nil {
        logger.Error("ChatCompletionStream error", "err", err)
        return
    }
    
    // 处理空响应
    if c == "" {
        c = plan.Name + " is completed"
    }
    
    // 保存AI响应
    llm.LLMClient.GetAssistantMessage(c)
}