企业大语言模型应用:智能体框架之间的技术选型(上)

文摘   2024-09-23 00:04   四川  

本文分为两个部分,两天内完成。

1 引言

随着人工智能领域的快速发展,智能体技术正逐渐成熟,有望在2024年实现自主系统在处理电子邮件、预订航班、数据管理等任务上的广泛应用。然而,要实现这一目标,我们仍需克服诸多挑战。开发者在构建智能体时,不仅要选择合适的模型和用例,还要决定使用哪个框架。是选择经过时间考验的LangGraph,还是尝试新兴的LlamaIndex工作流?或者,是否应该坚持传统,自行编写代码?

本文旨在简化这一决策过程。在过去几周,我对比了多个主流框架,并在每个框架中构建了相同的智能体,以便从技术角度评估它们的优缺点。

2 智能体测试背景

用于测试的智能体包括函数调用、工具或技能的集成、与外部资源的交互以及共享状态或内存的管理。

智能体具备以下功能:

  1. 从知识库中检索并回答问题。

  2. 与数据对话:针对LLM应用程序遥测数据提供答案。

  3. 数据分析:识别并分析遥测数据中的高级趋势和模式。

为实现这些功能,智能体配备了三项基础技能:使用产品文档的RAG、在跟踪数据库上生成SQL查询以及进行数据分析。智能体的用户界面采用简单的渐进式驱动设计,其本身被构建为一个聊天机器人。

3 基于代码的智能体(无框架)

在开发智能体时,一个可行的选择是完全绕过现有的框架,从头开始构建。这正是我在项目初期所采取的方法。

3.1 纯代码架构(Pure Code Architecture)

我构建的基于代码的智能体核心是一个由OpenAI驱动的路由器,它通过函数调用来决定使用哪种技能。一旦技能执行完毕,它会将控制权交回路由器,以便调用下一个技能或向用户返回响应。

智能体维护了一个持续更新的消息和响应列表,每次调用时都将这些信息传递给路由器,以保持对话的上下文连贯性。

# 定义路由器函数,用于处理消息并调用相应的技能def router(messages):      # 检查消息列表中是否包含系统提示,如果没有则添加    if not any(        isinstance(message, dict) and message.get("role") == "system" for message in messages    ):        system_prompt = {"role": "system", "content": SYSTEM_PROMPT}        messages.append(system_prompt)
# 使用OpenAI客户端发送消息并获取响应 response = client.chat.completions.create( model="gpt-4", # 使用GPT-4模型 messages=messages, # 传递消息列表 tools=skill_map.get_combined_function_description_for_openai(), # 获取技能描述 )
# 将响应消息添加到消息列表中 messages.append(response.choices[0].message) # 检查响应中是否包含工具调用 tool_calls = response.choices[0].message.tool_calls if tool_calls: # 处理工具调用并递归调用路由器 handle_tool_calls(tool_calls, messages) return router(messages) else: # 返回最终的响应内容 return response.choices[0].message.content

技能被定义在自己的类中(例如GenerateSQLQuery),这些类被统一管理在SkillMap中。路由器仅与SkillMap交互,通过它来加载技能的名称、描述和可调用的函数。这种方法的设计理念是,向智能体添加新技能就像编写一个新的类并将其添加到SkillMap的技能列表中一样简单,而无需修改路由器的代码。

# 定义SkillMap类,用于管理技能class SkillMap:    def __init__(self):        # 初始化技能列表        skills = [AnalyzeData(), GenerateSQLQuery()]        self.skill_map = {}
# 将技能添加到技能映射中 for skill in skills: self.skill_map[skill.get_function_name()] = ( skill.get_function_dict(), skill.get_function_callable(), )
def get_function_callable_by_name(self, skill_name) -> Callable: # 根据技能名称获取可调用的函数 return self.skill_map[skill_name][1]
def get_combined_function_description_for_openai(self): # 合并所有技能的描述,用于OpenAI客户端 combined_dict = [] for _, (function_dict, _) in self.skill_map.items(): combined_dict.append(function_dict) return combined_dict
def get_function_list(self): # 获取所有技能的名称列表 return list(self.skill_map.keys())
def get_list_of_function_callables(self): # 获取所有技能的可调用函数列表 return [skill[1] for skill in self.skill_map.values()]
def get_function_description_by_name(self, skill_name): # 根据技能名称获取技能描述 return str(self.skill_map[skill_name][0]["function"])

纯代码智能体的挑战与优势

挑战

  • 构建路由器系统提示是一个复杂的过程。例如,上述示例中的路由器需要自己生成SQL,而不是委托给特定的技能,这可能导致多轮调试的需要。

  • 处理每个步骤的不同输出格式也是一个挑战。由于没有采用结构化输出,必须为路由器和技能中每个LLM调用的多种不同格式做好准备。

优势

  • 基于代码的方法提供了一个清晰的起点,有助于理解智能体的工作原理,而不必依赖于框架提供的现成教程。

  • 尽管引导LLM的行为可能具有挑战性,但代码结构本身足够简单,易于使用,并且可能对某些特定用例非常有意义。(更多细节请参见下文的分析部分。)


架构师之道
研究企业架构,研究企业数字化转型,跟踪和探索云计算、大数据、工业互联网、物联网、区块链等领域的最新动向和技术分享,帮助架构师进阶首席科学家!
 最新文章