agent范式

为了更好地组织智能体的“思考”与“行动”过程,业界涌现出了多种经典的架构范式。在本章中,我们将聚焦于其中最具代表性的三种,并一步步从零实现它们:

  • ReAct (Reasoning and Acting): 一种将“思考”和“行动”紧密结合的范式,让智能体边想边做,动态调整。

  • Plan-and-Solve: 一种“三思而后行”的范式,智能体首先生成一个完整的行动计划,然后严格执行。

  • Reflection: 一种赋予智能体“反思”能力的范式,通过自我批判和修正来优化结果。

LLM最基本的操作:

self.client = OpenAI(api_key=apiKey, base_url=baseUrl, timeout=timeout)

response = self.client.chat.completions.create( model=self.model, messages=messages, temperature=temperature, stream=True, )

print("✅ 大语言模型响应成功:")

collected_content = [] for chunk in response: content = chunk.choices[0].delta.content or "" print(content, end="", flush=True) collected_content.append(content)

ReAct

ReAct的巧妙之处在于,它认识到思考与行动是相辅相成的。思考指导行动,而行动的结果又反过来修正思考。为此,ReAct范式通过一种特殊的提示工程来引导模型,使其每一步的输出都遵循一个固定的轨迹:

  • Thought (思考): 这是智能体的“内心独白”。它会分析当前情况、分解任务、制定下一步计划,或者反思上一步的结果。

  • Action (行动): 这是智能体决定采取的具体动作,通常是调用一个外部工具,例如 Search[‘华为最新款手机’]

  • Observation (观察): 这是执行Action后从外部工具返回的结果,例如搜索结果的摘要或API的返回值。

智能体将不断重复这个 Thought -> Action -> Observation 的循环,将新的观察结果追加到历史记录中,形成一个不断增长的上下文,直到它在Thought中认为已经找到了最终答案,然后输出结果。这个过程形成了一个强大的协同效应:推理使得行动更具目的性,而行动则为推理提供了事实依据。

这种机制特别适用于以下场景:

  • 需要外部知识的任务:如查询实时信息(天气、新闻、股价)、搜索专业领域的知识等。

  • 需要精确计算的任务:将数学问题交给计算器工具,避免LLM的计算错误。

  • 需要与API交互的任务:如操作数据库、调用某个服务的API来完成特定功能。

ToolExecutor

一个良好定义的工具应包含以下三个核心要素:

  1. 名称 (Name): 一个简洁、唯一的标识符,供智能体在 Action 中调用,例如 Search

  2. 描述 (Description): 一段清晰的自然语言描述,说明这个工具的用途。这是整个机制中最关键的部分,因为大语言模型会依赖这段描述来判断何时使用哪个工具。

  3. 执行逻辑 (Execution Logic): 真正执行任务的函数或方法。

我们可以设计一个工具的封装类:

from typing import Dict, Any

class ToolExecutor: """ 一个工具执行器,负责管理和执行工具。 """ def init(self): self.tools: Dict[str, Dict[str, Any]] = {}

def registerTool(self, name: str, description: str, func: callable):
    """
    向工具箱中注册一个新工具。
    """
    if name in self.tools:
        print(f"警告:工具 '{name}' 已存在,将被覆盖。")
    self.tools[name] = {"description": description, "func": func}
    print(f"工具 '{name}' 已注册。")

def getTool(self, name: str) -> callable:
    """
    根据名称获取一个工具的执行函数。
    """
    return self.tools.get(name, {}).get("func")

def getAvailableTools(self) -> str:
    """
    获取所有可用工具的格式化描述字符串。
    """
    return "\n".join([
        f"- {name}: {info['description']}" 
        for name, info in self.tools.items()
    ])

search tool

我们可以设计一个searchtools,核心代码:

# 配置参数
params = {
“engine”: “google”,
“q”: query,
“api_key”: api_key,
“gl”: “cn”,  # 国家代码
“hl”: “zh-cn”, # 语言代码
}

加载参数和启动配置

client = SerpApiClient(params) results = client.get_dict()

返回的数据也要进行解析,首先会检查是否存在 answer_boxGoogle的答案摘要框)或 knowledge_graph(知识图谱)等信息,如果存在,就直接返回这些最精确的答案。如果不存在,它才会退而求其次,返回前三个常规搜索结果的摘要。

# 智能解析:优先寻找最直接的答案
if “answer_box_list” in results:
return “\n”.join(results[“answer_box_list”])
if “answer_box” in results and “answer” in results[“answer_box”]:
return results[“answer_box”][“answer”]
if “knowledge_graph” in results and “description” in results[“knowledge_graph”]:
return results[“knowledge_graph”][“description”]
if “organic_results” in results and results[“organic_results”]:
# 如果没有直接答案,则返回前三个有机结果的摘要
snippets = [
f"[{i+1}] {res.get(’title’, ‘’)}\n{res.get(‘snippet’, ‘’)}"
for i, res in enumerate(results[“organic_results”][:3])
]
return “\n\n”.join(snippets)

langchain

LangChain作为一个专为大语言模型设计的开发框架,围绕任务链(Chain)与内存模块(Memory)构建了核心架构。这两大组件是LangChain高效构建复杂语言应用的关键所在,使模型在多任务环境中得以应对任务管理、上下文维护、记忆存储等多种需求。

可以说langchain就是一个专门为agent设计的框架,但除此之外,并不见得有这么好。

demo

任务链的工作原理在于将一个大任务拆分为多个子任务,每个子任务独立执行但又依赖前序任务的结果。在LangChain中,每一个任务链由不同的任务节点构成,节点之间通过数据流连接。

我们可以根据不同的逻辑去配置任务链,这是根据抽象思想来分类的,如顺序执行对应的顺序链、if对应的选择链、并行计算对应的并行链、loop对应的循环链等。

下面给出顺序链的demo,先定义prompt:

# 定义意图识别的 Prompt 模板
intent_prompt = PromptTemplate(
input_variables=[“input_text”],
template=“请根据以下输入识别用户意图:{input_text}”
)

定义实体提取的 Prompt 模板

entity_prompt = PromptTemplate( input_variables=[“intent”, “input_text”], template=“根据用户的意图 ‘{intent}’,从以下输入中提取相关实体:{input_text}” )

定义动作确定的 Prompt 模板

action_prompt = PromptTemplate( input_variables=[“intent”, “entities”], template=“根据意图 ‘{intent}’ 和提取的实体 ‘{entities}’,确定要执行的操作。” )

定义响应生成的 Prompt 模板

response_prompt = PromptTemplate( input_variables=[“action”], template=“根据确定的动作 ‘{action}’ 生成用户的响应。” )

然后创建langchain的节点:

# 创建各个任务节点的链
intent_chain = SequentialChain(
llm=llm,
prompt=intent_prompt,
output_key=“intent”,
memory=ConversationBufferMemory()
)

entity_chain = SequentialChain( llm=llm, prompt=entity_prompt, output_key=“entities”, memory=ConversationBufferMemory() )

action_chain = SequentialChain( llm=llm, prompt=action_prompt, output_key=“action”, memory=ConversationBufferMemory() )

response_chain = SequentialChain( llm=llm, prompt=response_prompt, output_key=“response”, memory=ConversationBufferMemory() )

最后进行顺序排列:

# 定义顺序链,按顺序执行各个任务节点
sequential_chain = SequentialChain(
chains=[intent_chain, entity_chain, action_chain, response_chain],
input_variables=[“input_text”],
output_variables=[“response”],
memory=ConversationBufferMemory()
)

示例输入文本

input_text = “我想查询北京的天气。”

运行顺序链

final_response = sequential_chain.run({“input_text”: input_text})

print("\n最终生成的响应:", final_response[“response”])

这里我们已经大致给出了一个demo去说明整个过程,我们展开来说具体还有什么细节可以用。

提示词工程

提示词承担着引导模型生成目标内容的任务。提示词设计得当,可以使模型生成的内容更加符合任务需求。

提示词的设计需要遵循一些基本原则:明确任务、控制范围和语言风格。

langchain提供了一个被继承PromptTemplate类,便于开发者在模板中定义插槽,这里需要注意的是关键词一定要精准,不能是能不能、是不是之类的疑问句。

# 定义多变量模板
template = PromptTemplate(
template=“请评价{product}的优缺点,包括{aspect1}和{aspect2}。”,
input_variables=[“product”, “aspect1”, “aspect2”]
)

使用模板生成提示词

prompt_1 = template.format(product=“智能手机”, aspect1=“电池续航”, aspect2=“拍照质量”) prompt_2 = template.format(product=“笔记本电脑”, aspect1=“处理速度”, aspect2=“便携性”)

print(“提示词1:”, prompt_1) print(“提示词2:”, prompt_2)

最基本的,在某一个模块中通常来说是动态的用插槽技术调节prompt:

def generate_complex_prompt(topic, aspect, task_type=“描述”, detail_level=“简要”):
# 根据不同任务类型和细节要求生成Prompt
if task_type == “描述”:
if detail_level == “简要”:
prompt = f"请简要描述{topic}的{aspect}。"
elif detail_level == “详细”:
prompt = f"请详细说明{topic}的{aspect},包括关键细节。"
elif task_type == “分析”:
prompt = f"请分析{topic}的{aspect},特别关注其优缺点。"
elif task_type == “预测”:
prompt = f"请预测{topic}在未来的{aspect},并说明可能的影响。"
else:
prompt = f"请描述{topic}的{aspect}。"
return prompt

测试不同任务组合生成的Prompt

print(generate_complex_prompt(“深度学习”, “应用”, task_type=“描述”, detail_level=“简要”)) print(generate_complex_prompt(“深度学习”, “应用”, task_type=“描述”, detail_level=“详细”)) print(generate_complex_prompt(“深度学习”, “前景”, task_type=“预测”))

请简要描述深度学习的应用。

请详细说明深度学习的应用,包括关键细节。

请预测深度学习在未来的前景,并说明可能的影响。

这种插槽的技术,多用于减少人工编写的重复性,并确保生成的内容风格一致。

这只是其中的一个模块,那么串通所有模块的肯定需要一个逻辑,也就是demo说的链式提示词。链式提示词是一种将内容生成任务分解为多个步骤的方法,每一步生成的结果可以作为下一步的输入,逐步构建一个完整的报告,从概述到应用,再到案例研究和总结,这种方式适用于逻辑结构复杂或信息量较大的任务,例如自动摘要、深度分析、报告生成等。

链式提示词设计主要遵循以下原则:

  • 多轮任务需要连续的上下文,保留恰当的过渡信息

  • 每一轮的目标是引导模型识别用户的意图

  • 可以设定每轮都输入的上下文,当上下文过长也要进行压缩,可以在某一轮让llm用总结性的话语去压缩

比如说生成一篇关于“机器学习”的报告时:

  • 层级1:生成机器学习的概要。

  • 层级2:基于概要生成不同应用领域的简述。

  • 层级3:在每个领域中深入描述机器学习的具体应用实例。

为了提高多级提示词生成的灵活性,可以使用动态管理的方式,根据任务的复杂度选择生成的层级。例如,当用户只想要概要和领域应用,而不需要具体实例时,可以仅调用前两层的生成函数。

import openai

层级 1:生成概要

def generate_overview(): prompt = “请简要介绍机器学习的基本概念。” response = openai.Completion.create( engine=“text-davinci-003”, prompt=prompt, max_tokens=50, temperature=0.5 ) return response.choices[0].text.strip()

层级 2:生成不同领域的应用简述

def generate_field_applications(overview): prompt = f"{overview}\n接下来,请简要描述机器学习在金融、医疗和教育等领域的应用。" response = openai.Completion.create( engine=“text-davinci-003”, prompt=prompt, max_tokens=100, temperature=0.5 ) return response.choices[0].text.strip()

层级 3:深入描述应用实例

def generate_detailed_example(application_summary, field): prompt = f"{application_summary}\n请详细说明机器学习在{field}领域的具体应用实例。" response = openai.Completion.create( engine=“text-davinci-003”, prompt=prompt, max_tokens=100, temperature=0.5 ) return response.choices[0].text.strip()

调用多级Prompt逐步生成内容

overview = generate_overview() print(“层级 1 - 概要:”, overview)

application_summary = generate_field_applications(overview) print(“层级 2 - 应用简述:”, application_summary)

detailed_example_finance = generate_detailed_example(application_summary, “金融”) print(“层级 3 - 金融领域实例:”, detailed_example_finance)

除此之外,还有引导模型识别用户意图,这里可以通过举例子的想法,也就是Few-shot提示词。Few-shot提示词的核心在于通过在提示词中加入几个典型的示例,让模型学习到特定任务的输出方式。在选择Few-shot示例时,有以下3点建议:

  • 包含常见结构:示例应体现回答的常见结构,如简洁的定义、优缺点列举等。

  • 涵盖不同问题类型:选择不同类型的问题,确保模型能够适应多样化内容生成。

  • 确保示例简洁明了:示例应尽量简短,以便提示词的整体长度在合理范围内

# 定义示例库
few_shot_examples_general = """
示例1:
问题:机器学习的优势是什么?
回答:机器学习能够处理大量数据,自动识别模式,提高预测的准确性。

示例2: 问题:深度学习的主要应用是什么? 回答:深度学习应用于计算机视觉、自然语言处理和语音识别等领域。 """

few_shot_examples_finance = """ 示例1: 问题:机器学习在金融行业的应用是什么? 回答:机器学习用于风险管理、客户行为分析和自动化交易等方面。

示例2: 问题:区块链的优势是什么? 回答:区块链技术保证了数据的透明性和安全性。 """

根据领域选择不同的Few-shot示例

def select_examples(domain): if domain == “金融”: return few_shot_examples_finance else: return few_shot_examples_general

用户输入和领域选择

user_question = “区块链的挑战是什么?” domain = “金融”

构建Prompt

selected_examples = select_examples(domain) prompt = f"{selected_examples}\n问题:{user_question}\n回答:"

调用API生成内容

response = openai.Completion.create( engine=“text-davinci-003”, prompt=prompt, max_tokens=100, temperature=0.5 )

输出模型的回答

generated_answer = response.choices[0].text.strip() print(“模型生成的回答:”, generated_answer)

模型生成的回答: 区块链的挑战包括高能源消耗、扩展性不足以及对现有金融系统的整合难度。

参考文档:

hello agent —— best agent tutorial

凌峰——《LangChain核心技术与LLM项目实践》