福生无量摸鱼天尊

agent note

2025/10/31
8
0

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项目实践》