当前位置: 首页 > news >正文

ACP(八):用插件扩展答疑机器人的能力边界

前言

在前面课程中,你已经掌握了通过优化提示词和检索流程提高答疑机器人效果的方法。但目前的答疑机器人依然有一定的局限性,本章节将带你发掘这些不足并引入智能体(Agent)应用以解决这些问题,它以大模型为基础,同时可以拓展大模型的能力,就像给大脑配备了四肢。

机器人的局限性与解决方案

一些同事希望答疑机器人能具备这样一种功能:只需说出“帮我请明天的假”,机器人便能自动提交请假申请单。然而,传统的答疑机器人有其固有局限性:

from fontTools.ttLib.tables.ttProgram import instructionsfrom chatbot import rag
# 上一章节已经建立了索引,因此这里可以直接加载索引。如果需要重建索引,可以增加一行代码:rag.indexing()
index = rag.load_index()
query_engine = rag.create_query_engine(index=index)rag.ask('张三的HR明天想请个假', query_engine=query_engine)
##输出
如果张三的HR想要请假,建议通过邮件或公司内部通讯工具提前通知直接上级和相关部门,说明请假的原因和时间,并确认工作交接事宜,以确保工作的连续性和团队的正常运作。例如,可以发送邮件给直接上级和相关同事,抄送韩杉(hanshan@educompany.com),以便人力资源部门做好相应的安排。

从上面示例你会发现当前机大模型只是文本输入输出的问答系统,无法与外界交互。
image
为了解决这个问题,你可以为答疑机器人引入一种新能力:动态解析用户需求并采取相应行动。例如,为了让回答机器人能够帮助用户请假,你需要让大模型解析用户的需求,并调用相应的API(如请假API)。这下是智能体应用的核心思想——通过任务分解和自动化执行,智能体能够高效地响应并完成复杂的操作。
image

如何构建Agent

通常来说构建一个智能体分为以下步骤,你将如下图所示一步步完成一个智能体的构建。
image

明确目标

“不谋全局者,不足谋一域。”——在任何复杂任务中,明确目标都是迈向成功的第一步。正如绘制一张地图需要先确定目的地,在构建答疑机器人时,你也需要先清晰地定义任务的核心目标。

你想要答疑机器人能够从公司的私有数据库中查询员工信息,并能够帮助用户完成请假申请的同时在数据库中进行记录与更新。

所以你的第一个目标是:将用户所有对公司内人员信息类型的提问转化为数据库查询的工具函数。具体而言,这包括:

  • 将用户输入的自然语言问题转化为对应的SQL查询语句(即NL2SQL,自然语言转为SQL)。
  • 使用生成的SQL查询语句访问数据库,获取对应的查询结果。
  • 将查询结果作为工具函数的输出,最终返回给用户。

定义工具函数

接下来,在配置好环境变量后你就可以开始搭建一个Agent智能体了。

当然,从零来构建一个Agent需要处理复杂的底层实现,这往往需要大量的时间和精力 ,因此你可以使用Assistant API来帮助你更高效地构建Agent.
Assistant API是一种简化智能体应用创建过程的接口。它提供了丰富的功能,包括支持多种基础模型、灵活的工具调用、对话管理和高度可扩展性。
通过Assistant API,你可以专注于智能体的核心功能,而不需要处理繁琐的底层实现。
首先你需要定义一些工具函数,假设你的答疑机器人需要有从数据库查询员工信息的功能。
为了帮助你更多地关注到Agent的内容,你需要模拟一个查询步骤,而不实际去数据库中查询。

假设员工表名为employee,字段包括department(部门)、name(姓名)、HR。

# 导入依赖
from llama_index.llms.dashscope import DashScope
from llama_index.core.base.llms.types import MessageRole, ChatMessage
import re # 导入正则表达式库,用于处理空格def normalize_sql(sql_string):'''对SQL语句进行标准化处理,以方便比较。'''# 1. 转换为小写s = sql_string.lower()# 2. 移除首尾空格s = s.strip()# 3. 移除末尾的分号if s.endswith(';'):s = s[:-1]# 4. (可选但推荐) 将多个空格替换为单个空格s = re.sub(r'\s+', ' ', s)return s# 定义一个员工查询函数
def query_employee_info(query):'''输入用户提问,输出员工信息查询结果'''# 1. 首先根据用户提问,使用NL2SQL生成SQL语句llm = DashScope(model_name="qwen-plus")messages = [ChatMessage(role=MessageRole.SYSTEM, content='''你有一个表叫employees,记录公司的员工信息,这个表有department(某部门)、name(姓名)、HR三个字段。你需要根据用户输入生成sql语句进行查询,只生成sql语句,不需要生成sql语句之外的内容,也不要把```sql```这个标签加上。'''),ChatMessage(role=MessageRole.USER, content=query)]SQL_output_raw = llm.chat(messages).message.content# 对大模型输出的SQL进行标准化SQL_output_normalized = normalize_sql(SQL_output_raw)# 打印出原始和标准化后的SQL语句print(f'原始SQL语句为:{SQL_output_raw}')print(f'标准化后SQL语句为:{SQL_output_normalized}')# 2. 根据标准化后的SQL语句去查询数据库(此处为模拟查询),并返回结果# 注意:我们的比较目标也应该是标准化的字符串if SQL_output_normalized == "select count(*) from employees where department = '教育部门'":return "教育部门共有66名员工。"if SQL_output_normalized == "select hr from employees where name = '张三'":return "张三的HR是李四。"if SQL_output_normalized == "select department from employees where name = '王五'":return "王五的部门是后勤部。"else:return "抱歉,我暂时无法回答您的问题。"# 测试一下这个函数
result = query_employee_info("教育部门有几个人")
print(f"查询结果: {result}")result = query_employee_info("张三的HR是谁")
print(f"查询结果: {result}")

在这里插入图片描述

将工具函数与大模型集成进Agent中

你已经定义好了工具函数,接下来就要将它们与大模型通过Assistant API 集成到Agent中。
通过Assistants.create方法,你可以创建一个新的Agent,并通过model(模型名称)、name(Agent命名)、description(Agent的描述信息)、instructions(Agent非常重要的参数,用于提示Agent所具有的工具函数能力,同时也可以规范输出格式)、tools(工具函数通过参数传入)参数来定义Agent。

其中,tools参数中function.name用于指定工具函数,但需要为字符串格式,因此可以通过一个map方法映射到工具函数上。

# 引入依赖
from dashscope import Assistants, Messages, Runs, Threads
import json# 定义公司小蜜
ChatAssistant = Assistants.create(# 在此指定模型名称model="qwen-plus",# 在此指定Agent名称name='公司小蜜',# 在此指定Agent的描述信息description='一个智能助手,能够查询员工信息,帮助员工发送请假申请,或者查询公司规章制度。',# 用于提示大模型所具有的工具函数能力,也可以规范输出格式instructions='''你是公司小蜜,你的功能有以下三个:1. 查询员工信息。例如:查询员工张三的HR是谁;2. 发送请假申请。例如:当员工提出要请假时,你可以在系统里帮他完成请假申请的发送;3. 查询公司规章制度。例如:我们公司项目管理的工具是什么?请准确判断需要调用哪个工具,并礼貌回答用户的提问。''',# 将工具函数传入tools=[{# 定义工具函数类型,一般设置为function即可'type': 'function','function': {# 定义工具函数名称,通过map方法映射到query_employee_info函数'name': '查询员工信息',# 定义工具函数的描述信息,Agent主要根据description来判断是否需要调用该工具函数'description': '当需要查询员工信息时非常有用,比如查询员工张三的HR是谁,查询教育部门总人数等。',# 定义工具函数的参数'parameters': {'type': 'object','properties': {# 将用户的提问作为输入参数'query': {'type': 'str',# 对输入参数的描述'description': '用户的提问。'},},# 在此声明该工具函数需要哪些必填参数'required': ['query']},}}]
)
print(f'{ChatAssistant.name}创建完成')
# 建立Agent Function name与工具函数的映射关系
function_mapper = {"查询员工信息": query_employee_info
}
print('工具函数与function.name映射关系建立完成')
公司小蜜创建完成
工具函数与function.name映射关系建立完成

同时,你还可以封装一个辅助函数get_agent_response.

这段代码的功能是:当用户向智能体发出请求时,智能体通过get_agent_response()发送请求并获取响应。如果任务需要调用外部工具(如数据库查询),则智能体会根据工具函数的映射执行相应的操作,并将结果返回给用户。这使得智能体能够处理更复杂的任务,而不仅仅是简单的问答。

通过Assistant API获得Agent回复的过程需要涉及到如thread、message、run等概念,如果你对这些概念与细节感兴趣,请参考阿里去Assistant API官方文档。

如果你希望给Agent配备更多的能力,可以添加工具函数,并在function_mapper与tools中建立映射关系。

# 输入message信息,输出为指定Agent的回复
def get_agent_response(assistant, message=''):# 创建一个新的会话线程thread = Threads.create()# 创建一条消息并发送到该会话线程message = Messages.create(thread.id, content=message)# 创建一个运行实例(运行请求),将会话线程与Assistant(智能体)关联起来run = Runs.create(thread.id, assistant_id=assistant.id)# 等待运行完成,检查任务是否完成run_status = Runs.wait(run.id, thread_id=thread.id)# 如果任务运行失败,则输出错误信息if run_status.status == 'failed':print('run failed:')# 如果需要工具来辅助模型进行操作(如查询数据库、发送请求等)if run_status.required_action:# 获取需要调用的工具函数的详细信息f = run_status.required_action.submit_tool_outputs.tool_calls[0].function# 获取工具函数的名称(function name)func_name = f['name']# 获取调用工具函数时需要的输入参数param = json.loads(f['arguments'])# 打印出工具的名称和参数信息print("function is", f)# 根据工具函数的名称,通过一个映射(function_mapper)找到对应的实际工具函数# 这里使用了一个字典映射(function_mapper),它将工具函数名称与具体的函数对应if func_name in function_mapper:# 使用映射找到实际工具函数并传递参数,获取结果output = function_mapper[func_name](**param)else:# 如果找不到对应的函数,输出为空output = ""# 将工具函数的输出(结果)准备提交tool_outputs = [{'output': output}]# 提交工具的输出结果回给运行实例run = Runs.submit_tool_outputs(run.id, thread_id=thread.id, tool_outputs=tool_outputs)# 等待运行完成run_status = Runs.wait(run.id, thread_id=thread.id)# 获取最终的运行结果run_status = Runs.get(run.id, thread_id=thread.id)# 获取消息记录列表msgs = Messages.list(thread.id)# 返回Agent的回复内容return msgs['data'][0]['content'][0]['text']['value']

尝试对话

你已经完成了一个简单的单Agent系统构建,在正式投入使用之前测试是必不可少的一环,你可以尝试与答疑机器人进行对话:

query_stk = ["谁是张三的HR?","教育部门一共有多少员工?","王五在哪个部门?",
]
for query in query_stk:print("提问是:")print(query)print("思考过程与最终输出是:")print(get_agent_response(ChatAssistant,query))print("\n")

在这里插入图片描述
从测试结果可以看出,拓展了功能之后的答疑机器人达到了你预期的效果。

image

在实际实用中,智能体不仅可以与外界交互,还能通过不同的模块化设计来增强其处理复杂任务的能力。智能体的工作原理可以从以下几个核心模块进行理解:

  • 工具模块
    工具模块负责定义和管理智能体能够使用的各种工具。包括工具的描述、参数以及功能特性。这一模块确保智能体能够理解并有效使用这些工具来完成任务。
  • 记忆模块
    记忆模块可以分为长期记忆和短期记忆。
    长期记忆用于存储持久的信息和经验,帮助智能体进行模式学习、知识积累和个性化服务。
    短期记忆则用于临时存储当前任务相关的信息,以支持智能体在任务执行过程中实时调整决策。
  • 计划能力
    计划能力模块负责任务的规划。通过智能体的决策能力,这部分帮助智能体分解复杂任务,制定具体的行动步骤和策略,确保任务顺利完成。
  • 行动能力
    行动能力与工具模块紧密配合,确保智能体能够选择合适的工具,并通过容器执行相应的操作。行动能力是智能体实现任务的核心,确保其能够根据既定计划和决策,有效地实施各项任务。
    通过这些模块 的协作,智能体能够处理复杂任务,提升任务执行的效率和精准度,突破传统方法的局限。(参考阿里云ACA课程)

使用大模型进行意图识别

在上一节中,你已经成功构建了一个具备使用工具能力的初步智能体(Agent),这比单纯的RAG问答机器人又进了一大步。然而,当前的智能体仍然很简单,它只会假定用户的每一个问题都应该由它所拥有的工具(查询员工信息)来处理。如果用户提出了一个完全不同类型的请求,比如,让机器人帮忙检查一句话有没有语病,这时会发生什么呢?

试着做一个直观的实验。假设一位同事想让你开发的机器人帮忙检查一个文档里的某句话有没有语病:

from chatbot import rag# rag定义可以查看ACP(二)
# 看看当机器人遇到一个非知识问答类请求时会发生什么
rag.ask('请帮我审查下这句话:技术内容工程师需要设计和开发⾼质量的教育教材和课程吗?', query_engine=query_engine)

是的,内容开发工程师确实需要设计和开发高质量的教育教材和课程。这包括撰写教学大纲、制作课件、设计评估工具等,以确保内容符合教育标准和学习目标,提供全面的学习体验。同时,还会考虑不同学习者的需求,确保内容能够适应各种学习风格和水平。

看到这个结果,你可能会感到困惑。机器人并没有**“审查”这句话,而直接“回答”**了这个问题。它为什么会犯这样的错误?

还记得RAG工作流程吗?机器人会先从知识库里检索与问题相关的内容。在这个例子中,它找到了以下信息:

片段:内容开发工程师…

  1. 内容研究与分析 对最新的教育技术趋势、学习理论和市场需求进行深入研究。这包括分析竞争对手的产品,评估现有教育资源的有效性,并探索如何将新兴技术(如人工智能、虚拟现实等)整合进我们的教育内容中。通过持续的市场调研,我能够确保我们的内容在技术上始终处于前沿,并能够满足教育者和学习者的真实需求。
  2. 教材和课程开发 根据研究和市场反馈,我将设计和开发高质量的教育教材和课程。这包括撰写教学大纲、制作课件、设计评估工具等。我的职责还包括确保内容符合教育标准和学习目标,以提供全面的学习体验。同时,我会考虑不同学习者的需求,确保内容能够适应各种学习风格和水平。 …

现在,问题清晰了。RAG流程忠实地完成了它的任务——它在你的问题中看到了“技术内容工程师”这个关键词,并成功地从知识库中检索到了相关的职责描述。然后,大模型基于这些信息,给出了一个内容正确、但完全“答非所问”的答案。

怎么解决这个问题呢?

你可以退一步思考。如果你是这个机器人,你会怎么做?你可能会先判断一下:“用户这次想让我回答问题,还是想让我检查语法?”

这个“判断”的动作,就是解决问题的关键。你可以让大模型自己来做这个判断!

试试 个最直观的思路:在处理用户请求之前,先用一个简单的提示词让大模型判断用户的意图

from chatbot import rag, llm
# 引入一个“意图判断”步骤
question = "请帮我审查下这句话:技术内容工程师需要设计和开发⾼质量的教育教材和课程吗?"# 第一步:判断用户意图
# 用一个简单的提示词让大模型做一个二选一的判断
intent_prompt = f"""
用户的请求是关于“知识库问答”还是“文本审查”?请只回答类别名称。
用户请求:{question}
"""
intent = llm.invoke(intent_prompt)
print(f"识别到的用户意图是:{intent}")# 第二步:根据意图选择不同的处理方式# 问题属于文档审查,不使用RAG
if "文本审查" in intent:print("意图是文本审查,将不使用 RAG,直接调用大模型进行审查...")llm.invoke_with_stream_log(question)
else:print("意图是知识库问答,将使用 RAG...")rag.ask(question, query_engine)
识别到的用户意图是:文本审查
意图是文本审查,将不使用 RAG,直接调用大模型进行审查...
这句话表述得比较清楚,但可以稍微调整一下,使其更加流畅和专业。以下是修改后的版本:"技术内容工程师是否需要设计和开发高质量的教育教材和课程?"这样修改后,句子的结构更符合中文的表达习惯,同时问题的意图也更加明确。

通过这个简单的代码实验,你亲手实现了一个更智能的工作流程。这里并没有引入什么复杂的技术,只是在流程中增加一个判断步骤,就让机器人学会了“看情况办事”。

这个识别用户真实目的的过程,在行业内有一个专门的术语,叫做意图识别(Intent Recognition)

你可以把意图识别想象成一个智能客服前台。当客户走近时,前台不会立刻把公司手册丢给他,而是会先问:“您好,请问有什么可以帮您?”根据客户的回答(意图),前台再决定是引导他去编辑部、销售部,还是仅仅回答一个简单的问题。

这正是ACP(二)提到的上下文工程(Context
engineering)的一个更深层次的应用。回顾一下:

  • 通过RAG添加知识,是你为大模型填充上下文,解决它“不知道”的问题。
  • 通过意图识别,你开始控制上下文,决定在何时、何种情况下、填充什么样的上下文(甚至不填充)。
    你正在从一个单纯的“信息投喂员”,转变为一个能够设计和指挥复杂工作流的“总工程师”。通过为不同的意图设计不同的处理流程,你可以让你的应用更高效、更节省成本,并且极大地减少因信息干扰而导致的“答非所问”。

在接下来的内容中,你将了解系统化的思路,构建一个更强大的意图识别路由器,让你的答疑机器人能够处理更多样的任务。

意图识别

接下来,将构建提示词使大模型对问题分类。由于经过意图识别后要取得格式化的内容,才能进行文档审查或者使用RAG应用,所以为了能将用户的总是准确分类,将考虑以下提示词技巧:

  • 明确输出格式:指定输出格式,使分类结果规范且易于解析。
  • Few-shot示例:提供示例,帮助大模型理解每个类别的特征和分类规则。
from chatbot import llm# 构建提示词
prompt = '''
【角色背景】
你是一个问题分类路由器,负责判断用户问题的类型,并将其归入下列三类之一:
1. 公司内部文档查询
2. 内容翻译
3. 文档审查【任务要求】
你的任务是根据用户的输入内容,判断其意图并仅选择一个最贴切的分类。请仅输出分类名称,不需要多余的解释。判断依据如下:- 如果问题涉及公司政策、流程、内部工具或职位描述与职责等内容,选择“公司内部文档查询”。
- 如果问题涉及任意一门非中文的语言,且输入中出现任何外语或“翻译”等字眼,选择“内容翻译”。
- 如果问题涉及检查或总结外部文档或链接内容,选择“文档审查”。
- 用户的前后输入与问题分类并没有任何关系,请单独为每次对话考虑分类类别。【Few-shot 示例】
示例1:用户输入:“我们公司内部有哪些常用的项目管理工具?”
分类:公司内部文档查询示例2:用户输入:“请翻译下列句子:How can we finish the assignment on time?”
分类:内容翻译示例3:用户输入:“请审查下这个链接下的文档:https://help.aliyun.com/zh/model-studio/user-guide/long-context-qwen-long”
分类:文档审查示例4:用户输入:“请审查以下内容:技术内容工程师需要设计和开发⾼质量的教育教材和课程吗?”
分类:文档审查示例5:用户输入:“技术内容工程师核心职责是什么?”
分类:公司内部文档查询【用户输入】
以下是用户的输入,请判断分类:
'''# 获取问题的类型
def get_question_type(question):return llm.invoke(prompt + question)print(get_question_type('https://www.promptingguide.ai/zh/techniques/fewshot'),'\n')
print(get_question_type('That is a big one I dont know why'),'\n')
print(get_question_type('作为技术内容工程师有什么需要注意的吗?'),'\n')
文档审查 内容翻译 公司内部文档查询 

将意图识别应用到答疑机器人中

对用户的总是进行意图识别后,你就可以让答疑机器人先识别问题的类型,再使用不同的提示词和工作流程来回答问题。

def ask_llm_route(question):question_type = get_question_type(question)print(f'问题:{question}\n类型:{question_type}')reviewer_prompt = """【角色背景】你是文档纠错专家,负责找出文档中或网页内容的明显错误【任务要求】- 你需要言简意赅的回复。- 如果没有明显问题,请直接回复没有问题\n【输入如下】\n"""translator_prompt = """【任务要求】你是一名翻译专家,你要识别不同语言的文本,并翻译为中文。【输入如下】\n"""if question_type == '文档审查':return llm.invoke(reviewer_prompt + question)elif question_type == '公司内部文档查询':return rag.ask(question, query_engine=query_engine)elif question_type == '内容翻译':return llm.invoke(translator_prompt + question)else:return "未能识别问题类型,请重新输入。"query_engine =rag.create_query_engine(index=rag.load_index())
# 问题1
print(ask_llm_route('https://www.promptingguide.ai/zh/techniques/fewshot'),'\n')# 问题2
print(ask_llm_route('请帮我检查下这段文档:技术内容工程师有需要进行内容优化与更新与跨部门合作吗?'),'\n')# 问题3
print(ask_llm_route('技术内容工程师有需要进行内容优化与更新与跨部门合作吗?'),'\n')# 问题4:
print(ask_llm_route('A true master always carries the heart of a student.'),'\n')
问题:https://www.promptingguide.ai/zh/techniques/fewshot
类型:文档审查
没有问题 问题:请帮我检查下这段文档:技术内容工程师有需要进行内容优化与更新与跨部门合作吗?
类型:文档审查
没有问题。 问题:技术内容工程师有需要进行内容优化与更新与跨部门合作吗?
类型:公司内部文档查询
技术内容工程师确实需要进行内容优化与更新,这包括根据学习者的反馈和评价来识别内容中的潜在问题并及时调整,同时定期更新材料以反映新的研究成果、技术进步和市场变化,确保内容的时效性和相关性。此外,他们也需要与教学设计师、教育心理学家、技术团队及市场营销人员等多个部门紧密合作,确保内容的技术实施过程顺利进行,并有效传达给目标受众。这种跨部门的合作有助于共同创造出既有教育价值又具市场竞争力的产品。None 问题:A true master always carries the heart of a student.
类型:内容翻译
一位真正的大师总是怀有一颗学生的心。 

从上述实验中可以看出,通过引入意图识别这一步骤,我们的答疑机器人变得更加智能。这正是上下文工程中“控制流”设计的体现。它不再是一个简单的“提问-检索-回答”的线性流程,而是根据任务类型动态地调整其行为。这样做的好处是显而易见的:

  • 节省资源:对于检查文档错误的问题,大模型其实可以直接回复,并不需要检索参考资料,之前的实现存在资源浪费。
  • 避免误解:之前的实现每次会检索参考资料,这些被召回的相关文本段可能会干扰大模型理解问题,导致答非所问。

我们刚刚构建的意图识别模块,本质上是一个简单的“路由器”,它决定了用户的请求应该走哪条处理路径。这种路由和规划的思想,是更高级智能体系统的核心。现在你已经了解了如何应对不同类型的任务,让我们回到最初的工具型智能体,看看如何处理需要组合使用多个工具的更复杂的任务。这将引导我们进入多智能体(Multi-Agent)系统的世界。

多智能体Multi-Agent

当完成员工信息的查询后,接下来你还需要对员工请假申请进行申请与记录,所以你需要再加一个新的工具函数以满足此需求。

这个工具函数将员工输入的请假日期作为输入参数,并返回一个申请成功的字符串。为了帮助你更多地关注到Agent的内容,下方的示例模拟了请假申请步骤,而没有实际去公司系统中提交请假申请。

def send_leave_application(date):'''输入请假时间,输出请假申请发送结果'''return f'已为你发送请假申请,请假日期是{date}。'# 测试一下这个函数
print(send_leave_application("明后两天"))
已为你发送请假申请,请假日期是明后两天。

在确定新的工具函数正常工作后,你需要将这个新函数集成进你之前创建的agent中:

new_tool = {'type': 'function','function': {'name': '发送请假申请','description': '当需要帮助员工发送请假申请时非常有用。','parameters': {'type': 'object','properties': {# 需要请假的时间'date': {'type': 'str','description': '员工想要请假的时间。'},},'required': ['date']},}}
ChatAssistant.tools.append(new_tool)
function_mapper["发送请假申请"] = send_leave_application
print('请假工具函数与function.name映射关系建立完成')

在确认集成成功后,你可以来测试一下模型的输出效果以确保一切功能正常运作:

get_agent_response(ChatAssistant,"张三的HR是谁?给他请三天假")
function is {'name': '查询员工信息', 'arguments': '{"query": "张三的HR是谁"}', 'output': None}
原始SQL语句为:SELECT HR FROM employees WHERE name = '张三';
标准化后SQL语句为:select hr from employees where name = '张三'
'张三的HR是李四。\n\n关于请假申请,我需要更多信息才能帮你完成操作,例如:\n- 请假类型(如年假、病假等)\n- 请假起止时间\n\n请提供这些信息后,我将为你提交请假申请。'

通过上面的输出结果,你会发现在处理复杂任务时,特别是当机器人需要在一个请求中执行多个操作时,单个智能体可能无法有效完成所有子任务。

例如,用户请求“张三的HR是谁?给他请三天假”,这就涉及到员工信息查询和请假申请两个操作。单个智能体通常只能处理一种任务,无法同时调动多个工具或API接口来完成所有子任务。

为了克服这种 多操作需求的局限性,你可以为答疑机器人引入一种新能力:将任务拆解成多个独立的模块进行处理,而多智能体系统正是为此而生。

多智能体系统通过将任务拆解成多个子任务,并由不同的智能体分别处理这些任务,从而克服了单一智能体无法同时完成多个操作的局限性。每个智能体专注于一个特定任务,像一个团队中的成员,各司其职,最终协作完成整个任务。

这种设计不仅能够提高任务处理的效率,还能增强灵活性,确保每个子任务得到专门的处理。

Multi-Agent系统有多种设计思路,本教程将介绍一个由Planner Agent、若干个负责执行工具函数的Agent,以及一个Summary Agent组成的Multi-Agent系统。

  • Planner Agent规划智能体:根据用户的输入内容,选择要将任务分发给哪个Agent或Agent组合完成任务。
  • 执行工具函数的Agent智能体:根据Planner Agent分发的任务,执行属于自己的工具函数。
  • Summart Agent总结智能体:根据用户的输入,以及执行工具函数的Agent的输出,生成总结并返回给用户。
    在这里插入图片描述

回到之前的示例——“张三的HR是谁?给他请三天假”。在多智能体系统中,这个任务会被拆解成两个子任务:

查询张三的HR信息:由一个Agent负责。

发送请假申请:由另一个Agent负责。

通过多智能体系统,Planner Agent首先分析用户请求并拆解成这两个子任务,然后将每个任务交给对应的执行Agent处理。最后,Summary Agent会将各个Agent的结果汇总,生成最终的响应。

Planner Agent

Planner Agent是Multi-Agent系统的核心部分,它负责分析问题,并决定将任务分发到哪个Agent或Agent组合上。

首先利用Assistant API创建Planner Agent,此处你可以先不对instructions进行指定:

# 决策级别的agent,决定使用哪些agent,以及它们的运行顺序。
planner_agent = Assistants.create(model="qwen-plus",name='流程编排机器人',description='你是团队的leader,你的手下有很多agent,你需要根据用户的输入,决定要以怎样的顺序去使用这些agent'
)print("Planner Agent创建完成")

创建完成后你可以先来看看在未定义instructions时,Planner Agent的输出是什么样的:

print(get_agent_response(planner_agent,'谁是张三的HR?教育部门一共有多少员工?'))
要回答这两个问题,我需要依次执行以下步骤:1. **确定张三的HR是谁**  → 需要调用 **员工信息查询Agent**,输入“张三”,获取其直属HR或HRBP信息。2. **查询教育部门的员工总数**  → 需要调用 **组织架构与部门统计Agent**,输入“教育部门”,获取该部门的总人数。### 执行顺序如下:
1. 调用 **员工信息查询Agent**(查询张三的HR)
2. 调用 **组织架构与部门统计Agent**(查询教育部门员工总数)待两个Agent返回结果后,合并输出给用户。

从机器人的回答中你可以发现,其输出的回答包含许多额外的信息且没有告知所需要调用的Agent名称。

接下来你需要通过instructions指定其可调用的Agent及输出格式。

目前,你需要机器 人能够帮助员工进行公司内部员工信息查询、请假申请以及其他日常对话。

另外,由于Agent的返回值为字符串格式,当你后续需要通过返回内容来分别调用所对应的智能体时会产生不便,所以你需要要求Planner Agent输出列表形式的字符串。例如:[“employee_info_agent”,“leave_agent”,“company_into_agent”],以便后续使用字符串解析工具将其转换为结构化数据。

planner_agent=Assistants.update(planner_agent.id,instructions="""你的团队中有以下agent。employee_info_agent:可以查询公司的员工信息,如果提问中关于部门、HR等信息,则调用该agent;leave_agent:可以帮助员工发送请假申请,如果用户提出请假,则调用该agent;chat_agent:如果用户的问题无需以上agent,则调用该agent。你需要根据用户的问题,判断要以什么顺序使用这些agent,一个agent可以被多次调用。你的返回形式是一个列表,不能返回其它信息。比如:["employee_info_agent", "leave_agent"]或者["chat_agent"],列表中的元素只能为上述的agent。""")print("Planner Agent 的 instructions 已更新")

接下来尝试几个测试问题,看下Planner Agent能否分发到正确的Agent。

query_stk = ["谁是张三的HR?教育部门一共有多少员工?","王五在哪个部门?帮我提交下周三请假的申请","你好"
]
for query in query_stk:print("提问是:")print(query)print(get_agent_response(planner_agent,query))print("\n")
提问是:
谁是张三的HR?教育部门一共有多少员工?
["employee_info_agent", "employee_info_agent"]提问是:
王五在哪个部门?帮我提交下周三请假的申请
["employee_info_agent", "leave_agent"]提问是:
你好
["chat_agent"]

对于这三个测试问题,Planner Agent都做出了正确的选择。

你可以观察到当Planner Agent返回任务规划结果后,其输出是一个描述任务执行顺序的列表形式字符串,例如:[“employee_info_agent”,“leave_agent”]。为了便于后续处理和执行,你需要将其转换为Python原生的列表结构(list)并保留相对应的调用顺序。在这里,你可以使用了Python的ast.literal_eval方法,它可以安全地将字符串表达式解析为相应的Python数据类型,例如列表、字典等。

通过这种方式,你可以将任务规划转化为易于操作的列表对象,并逐步解析出每个任务的执行步骤,以简化后续的多智能体协作。

import ast# 使用Planner Agent获取任务规划
planner_response = get_agent_response(planner_agent, "王五在哪个部门?帮我提交下周三请假的申请")# 将Planner Agent的字符串形式回复解析为列表
# Planner Agent返回的是一个描述调用顺序的列表形式字符串,例如:["employee_info_agent", "leave_agent"]
order_stk = ast.literal_eval(planner_response)# 打印出Planner Agent的规划结果
print("Planner Agent的任务规划结果:")
for i, agent in enumerate(order_stk, start=1):print(f'第{i}步调用:{agent}')
Planner Agent的任务规划结果:
第1步调用:employee_info_agent
第2步调用:leave_agent

执行工具函数的Agent

上一章节你已经 完成 Planner Agent的规划工作。它如同蚁巢中的蚁后,能够统筹规划任务并下达命令。然而,单靠蚁后是不足以让整个蚁巢运转起来的——需要无数的工蚁去执行具体任务,比如搜集食物或修筑巢穴。同样的道理,在你的多智能体系统中,仅有Planner Agent还不足以完成任务,必须为其配备执行任务的工具函数Agent,才能真正实现整个系统的高效协作。

以下,你将需要基于上节中的规划结果,为两个不同任务创建独立的执行工具函数Agent,使它们分别负责具体的操作任务。这种设计不仅让系统更加模块化,还能最大限度地发挥Planner Agent的协调作用。

需要确保agent变量名与Planner Agent的instructions中定义的agent变量名一致。

# 员工信息查询agent
employee_info_agent = Assistants.create(model="qwen-plus",name='员工信息查询助手',description='一个智能助手,能够查询员工信息。',instructions='''你是员工信息查询助手,负责查询员工姓名、部门、HR等信息''',tools=[{'type': 'function','function': {'name': '查询员工信息','description': '当需要查询员工信息时非常有用,比如查询员工张三的HR是谁,查询教育部门总人数等。','parameters': {'type': 'object','properties': {'query': {'type': 'str','description': '用户的提问。'},},'required': ['query']},}}]
)
print(f'{employee_info_agent.name}创建完成')# 请假申请agent
leave_agent = Assistants.create(model="qwen-plus",name='请假申请助手',description='一个智能助手,能够帮助员工提交请假申请。',instructions='''你是员工请假申请助手,负责帮助员工提交请假申请。''',tools=[{'type': 'function','function': {'name': '发送请假申请','description': '当需要帮助员工发送请假申请时非常有用。','parameters': {'type': 'object','properties': {# 需要请假的时间'date': {'type': 'str','description': '员工想要请假的时间。'},},'required': ['date']},}}]
)
print(f'{leave_agent.name}创建完成')
# 功能是回复日常问题。对于日常问题来说,可以使用价格较为低廉的模型作为agent的基座
chat_agent = Assistants.create(# 因为该Agent对大模型性能要求不高,因此使用成本较低的qwen-turbo模型model="qwen-turbo",name='回答日常问题的机器人',description='一个智能助手,解答用户的问题',instructions='请礼貌地回答用户的问题'
)
print(f'{chat_agent.name}创建完成')

创建Summary Agent并测试Multi-Agent效果

在完成了Planner Agent和执行工具函数Agent的创建后,你还需要创建Summary Agent,该Agent会根据用户的问题与之前Agent输出 的参考信息,全面、完整地回答用户问题。

summary_agent = Assistants.create(model="qwen-plus",name='总结机器人',description='一个智能助手,根据用户的问题与参考信息,全面、完整地回答用户问题',instructions='你是一个智能助手,根据用户的问题与参考信息,全面、完整地回答用户问题'
)
print(f'{summary_agent.name}创建完成')

你可以将以上步骤封装为一个get_multi_agent_response函数,这样可以将复杂的多Agent协作过程抽象为一个简单接口。通过这种封装方式,用户只需提供输入问题,函数将:

  1. 调用Planner Agent,规划任务顺序。
  2. 根据规划顺序依次调用对应的工具函数Agent执行任务。
  3. 汇总所任务结果,最后通过Summary Agent生成最终回答。

这种设计不仅让主流程更加清晰,还便于复用和扩展。

由于列表中的元素为字符串,因此通过一个agent_mapper方法将字符串格式的Agent映射到定义好的Agent对象。

# 将列表中的字符串映射到Agent对象上
# 将字符串格式的Agent名称映射到具体Agent对象
agent_mapper = {"employee_info_agent": employee_info_agent,"leave_agent": leave_agent,"chat_agent": chat_agent
}def get_multi_agent_response(query):# 获取Agent的运行顺序agent_order = get_agent_response(planner_agent,query)# 由于大模型输出可能不稳定,因此加入异常处理模块处理列表字符串解析失败的问题try:order_stk = ast.literal_eval(agent_order)print("Planner Agent正在工作:")for i in range(len(order_stk)):print(f'第{i+1}步调用:{order_stk[i]}')# 随着多Agent的加入,需要将Agent的输出添加到用户问题中,作为参考信息cur_query = queryAgent_Message = ""# 依次运行Agentfor i in range(len(order_stk)):cur_agent = agent_mapper[order_stk[i]]response = get_agent_response(cur_agent,cur_query)Agent_Message += f"*{order_stk[i]}*的回复为:{response}\n\n"# 如果当前Agent为最后一个Agent,则将其输出作为Multi Agent的输出if i == len(order_stk)-1:prompt = f"请参考已知的信息:{Agent_Message},回答用户的问题:{query}。"multi_agent_response = get_agent_response(summary_agent,prompt)print(f'Multi-Agent回复为:{multi_agent_response}')return multi_agent_response# 如果当前Agent不是最后一个Agent,则将上一个Agent的输出response添加到下一轮的query中,作为参考信息else:# 在参考信息前后加上特殊标识符,可以防止大模型混淆参考信息与提问cur_query = f"你可以参考已知的信息:{response}你要完整地回答用户的问题。问题是:{query}。"# 兜底策略,如果上述程序运行失败,则直接调用大模型except Exception as e:return get_agent_response(chat_agent,query)# 此处来用 “王五在哪个部门?帮我提交下周三请假的申请”进行一个测试
get_multi_agent_response("王五在哪个部门?帮我提交下周三请假的申请")
Planner Agent正在工作:
第1步调用:employee_info_agent
第2步调用:leave_agent
function is {'name': '查询员工信息', 'arguments': '{"query": "王五在哪个部门"}', 'output': None}
原始SQL语句为:SELECT department FROM employees WHERE name = '王五';
标准化后SQL语句为:select department from employees where name = '王五'
function is {'name': '发送请假申请', 'arguments': '{"date": "2025年9月24日"}', 'output': None}
Multi-Agent回复为:王五在后勤部。已为您提交下周三(2025924日)的请假申请。如有其他需要,请随时告知!
'王五在后勤部。\n\n已为您提交下周三(2025年9月24日)的请假申请。如有其他需要,请随时告知!'

大模型平台的多智能体编排功能

上一小节你了解了Multi-Agent系统的设计理念与实现方法。可以看出,在自主构建一个Multi-Agent系统时,虽然能够提供高度的灵活性,但也伴随着一定的工作量和复杂性。对于许多企业来说,快速实现复杂业务逻辑更为重要。

Dify 是智能体流程画布的开创者之一。Dify的画布工具让用户能够通过可视化的流程图来设计和管理智能体任务的执行逻辑,在业内树立了标杆。其直观的界面设计,为许多开发者提供了参考。

尽管 Dify.ai 在智能体流程编排方面表现出色,但相比之下,百炼平台在以下方面更适合国内企业与开发者使用

image

百炼平台的多智能体编排功能,依托大模型的强大能力,支持智能体自主决策和任务分工。通过平台内置的画布式编排工具,用户可以轻松实现以下目标:

定义智能体的执行逻辑:用户能够直观地通过画布式工具配置各个智能体的执行规则和逻辑链路。
编排多个智能体之间的协作:通过模块化设计,各个智能体能够高效配合,完成复杂任务。
快速验证业务效果:内置的仿真和测试功能,帮助用户快速验证编排逻辑是否符合预期。
百炼平台支持用户从零开始设计智能体系统,即使没有深厚的技术背景,用户也可以通过其易用的界面快速搭建并验证多智能体协作系统。对于需要快速落地多智能体技术的企业,百炼平台是一个理想的选择。

扩展阅读

你已经了解了如何使用多智能体系统来处理复杂任务,但这种设计不仅仅局限于基础操作。事实上,合理设计多智能体系统,能够帮助你将更为复杂的业务流程自动化处理,提升效率和精度,甚至可以实现一些需要人介入的繁琐操作。

效果展示与流程图解析

下面是一个实际的智能体系统应用案例,假设你需要对一个网页进行内容验证——不仅仅是检查静态内容,还包括与页面元素的交互。此时,多智能体系统的优势就显现出来了。通过分工合作,多个智能体能够共同高效完成任务,而你无需手动干预每个步骤。
当面对一个需要解析HTM页面并执行特定操作的任务时,多智能体的分工如下:

  • Planner Agent规划器:分解任务,例如识别HTML元素中的列表或按钮。
  • Selector Agent执行器:负责具体的操作任务,例如选择特定元素并执行点击动作
  • Monitor Agent 监视器:实时监控任务的执行,确保流程按计划完成,如检测是否点击正确的按钮。

视觉标注在智能体就用中的结合

在实际项目中,尤其是涉及到网页内容交互时,光靠传统的文本分析能力往往无法精确地识别界面元素,导致智能体的执行效率和准确性下降。为了解决这一问题,视觉标注被应用于自动化测试和控制台界面的元素识别。

在某些场景下,智能体在操作控制台界面时,由于界面元素的复杂性,模型可能难以准确地识别目标。此时,通过引入视觉标注技术,可以提高模型对界面元素的理解和操作准确性。

就像你在使用一款软件时,可能需要通过眼睛去识别按钮、下拉菜单或表单,才能做出正确的操作。如果没有清晰的视觉标识,你可能会找错位置或操作错误。类似地,在智能体执行任务时,界面元素的准确识别至关重要。通过视觉标注,系统可以为这些元素加上“标签”,帮助智能体更准确地“看到”并正确执行操作。

例如,在处理HTML页面的自动化测试时,通过使用视觉标注,系统可以对页面中按钮、链接或表单进行标记,并指导智能体精确选择和操作这些元素。这种视觉增强的设计大大提高了模型处理复杂界面的能力,使得多智能体系统在面对不同的UI元素时更加灵活高效。

多智能体系统的灵活性与技术结合

通过这个案例,你可以发现多智能体系统的设计并不局限于某一类技术或场景。虽然在这个示例中多智能体系统结合了视觉标注,但实际上,多智能体系统的灵活性使得它能够与各种技术结合,解决不同的需求。

例如,你可以结合机器 学习技术优化智能体的决策过程,或者使用图像处理技术提升界面元素识别能力。

又如,你可以将多智能体系统的架构和技术组合,最终为你带来最佳的自动化解决方案。

多智能体系统不仅是一个工具,它更是一种 思维方式,能够根据不同任务和场景灵活设计和调整,从而在不同的应用中发挥巨大价值。在今后的应用中,无论是面对页面自动化测试,还是处理更复杂的业务流程,合理设计的多智能体系统都能帮助你提高效率,优化操作,甚至替代部分人类工作,解放你的生产力。

http://www.dtcms.com/a/393474.html

相关文章:

  • 迁移学习:从理论到实践,让模型 “举一反三” 的核心技术
  • ACP(六)自动化评测机器人的表现
  • 【MySQL数据库】MySQL的第一步:从安装启动到用户权限配置的一站式实战指南
  • MySQL笔记7
  • 【C语言】C语言预处理详解,从基础到进阶的全面讲解
  • Spotify:递归嵌入与聚类(四)
  • 三种查询语言比较:SQL、SPL、PromQL
  • [Windows] 迅连科技音频处理工具 CyberLink AudioDirector 2026 16.0.5703.0 中文多语免费版
  • (一)React面试(虚拟DOM/类组件)
  • 亲历 2025 机器人大赛:科技碰撞的震撼与启迪
  • Chromium 138 编译指南 Ubuntu篇:Python环境与开发工具配置(五)
  • 在CentOS上配置SVN至Web目录的自动同步
  • 一款不错的PDF工具,吾爱出品
  • Sleuth + Zipkin:微服务监控之分布式链路追踪技术
  • JVM 调优在分布式场景下的特殊策略:从集群 GC 分析到 OOM 排查实战(一)
  • 【开题答辩全过程】以 基于Vue技术实现权限管理系统为例,包含答辩的问题和答案
  • Redis 高可用架构全解析:主从复制、哨兵与集群模式
  • Redis全面解析:从基础配置到高可用集群
  • Redis:高性能Key-Value存储与缓存利器
  • Redis 三种核心服务架构详解:主从复制、哨兵模式与集群模式
  • Redis 三种服务架构详解:主从复制、哨兵模式与集群
  • 速通ACM省铜第十一天 赋源码(Gellyfish and Flaming Peony)
  • JAVA八股文——JAVA堆
  • Spark专题-第二部分:Spark SQL 入门(7)-算子介绍-Windows
  • JavaScript 闭包(Closure)深度讲解
  • QT与Spring Boot通信:实现HTTP请求的完整指南
  • 服务器ubuntu 22.04装nvidia驱动
  • nginx流量复制
  • spring-ai-alibaba-nl2sql 学习(五)——python 分析
  • 分布式链路追踪关键指标实战:精准定位服务调用 “慢节点” 全指南(三)