# Agents-Course

## Docs

- [智能体框架介绍](https://huggingface.co/learn/agents-course/zh-CN/unit2/introduction.md)
- [快速自测（不计分）[[quiz2]]](https://huggingface.co/learn/agents-course/zh-CN/unit2/llama-index/quiz2.md)
- [在 LlamaIndex 中创建智能工作流](https://huggingface.co/learn/agents-course/zh-CN/unit2/llama-index/workflows.md)
- [结语](https://huggingface.co/learn/agents-course/zh-CN/unit2/llama-index/conclusion.md)
- [在 LlamaIndex 中使用工具](https://huggingface.co/learn/agents-course/zh-CN/unit2/llama-index/tools.md)
- [目录](https://huggingface.co/learn/agents-course/zh-CN/unit2/llama-index/README.md)
- [在 LlamaIndex 中使用智能体](https://huggingface.co/learn/agents-course/zh-CN/unit2/llama-index/agents.md)
- [LlamaIndex 简介](https://huggingface.co/learn/agents-course/zh-CN/unit2/llama-index/introduction.md)
- [小测验（不计分） [[quiz1]]](https://huggingface.co/learn/agents-course/zh-CN/unit2/llama-index/quiz1.md)
- [LlamaIndex 中的组件是什么？](https://huggingface.co/learn/agents-course/zh-CN/unit2/llama-index/components.md)
- [LlamaHub 简介](https://huggingface.co/learn/agents-course/zh-CN/unit2/llama-index/llama-hub.md)
- [多智能体系统](https://huggingface.co/learn/agents-course/zh-CN/unit2/smolagents/multi_agent_systems.md)
- [使用 smolagents 构建视觉智能体](https://huggingface.co/learn/agents-course/zh-CN/unit2/smolagents/vision_agents.md)
- [构建智能驱动的 RAG 系统](https://huggingface.co/learn/agents-course/zh-CN/unit2/smolagents/retrieval_agents.md)
- [小测验（不计分）[[quiz2]]](https://huggingface.co/learn/agents-course/zh-CN/unit2/smolagents/quiz2.md)
- [构建使用代码的智能体](https://huggingface.co/learn/agents-course/zh-CN/unit2/smolagents/code_agents.md)
- [结论](https://huggingface.co/learn/agents-course/zh-CN/unit2/smolagents/conclusion.md)
- [工具](https://huggingface.co/learn/agents-course/zh-CN/unit2/smolagents/tools.md)
- [测验时间！](https://huggingface.co/learn/agents-course/zh-CN/unit2/smolagents/final_quiz.md)
- [`smolagents` 简介](https://huggingface.co/learn/agents-course/zh-CN/unit2/smolagents/introduction.md)
- [为什么选择 smolagents](https://huggingface.co/learn/agents-course/zh-CN/unit2/smolagents/why_use_smolagents.md)
- [小测验 (不计分) [[quiz1]]](https://huggingface.co/learn/agents-course/zh-CN/unit2/smolagents/quiz1.md)
- [将操作编写为代码片段或 JSON 结构](https://huggingface.co/learn/agents-course/zh-CN/unit2/smolagents/tool_calling_agents.md)
- [什么是 `LangGraph`？](https://huggingface.co/learn/agents-course/zh-CN/unit2/langgraph/when_to_use_langgraph.md)
- [LangGraph 的核心构建模块](https://huggingface.co/learn/agents-course/zh-CN/unit2/langgraph/building_blocks.md)
- [构建你的第一个 LangGraph](https://huggingface.co/learn/agents-course/zh-CN/unit2/langgraph/first_graph.md)
- [结语](https://huggingface.co/learn/agents-course/zh-CN/unit2/langgraph/conclusion.md)
- [文档分析图](https://huggingface.co/learn/agents-course/zh-CN/unit2/langgraph/document_analysis_agent.md)
- [欢迎来到 `LangGraph` 的世界](https://huggingface.co/learn/agents-course/zh-CN/unit2/langgraph/introduction.md)
- [测试你对 LangGraph 的理解](https://huggingface.co/learn/agents-course/zh-CN/unit2/langgraph/quiz1.md)
- [Readme](https://huggingface.co/learn/agents-course/zh-CN/unit3/README.md)
- [智能体增强检索生成（Agentic RAG）](https://huggingface.co/learn/agents-course/zh-CN/unit3/agentic-rag/agentic-rag.md)
- [创建宾客信息检索生成（RAG）工具](https://huggingface.co/learn/agents-course/zh-CN/unit3/agentic-rag/invitees.md)
- [总结](https://huggingface.co/learn/agents-course/zh-CN/unit3/agentic-rag/conclusion.md)
- [构建并集成智能体工具](https://huggingface.co/learn/agents-course/zh-CN/unit3/agentic-rag/tools.md)
- [代理增强检索生成（Agentic RAG）用例介绍](https://huggingface.co/learn/agents-course/zh-CN/unit3/agentic-rag/introduction.md)
- [创建你的 Gala 智能体](https://huggingface.co/learn/agents-course/zh-CN/unit3/agentic-rag/agent.md)
- [领取你的证书 🎓](https://huggingface.co/learn/agents-course/zh-CN/unit4/get-your-certificate.md)
- [动手实践](https://huggingface.co/learn/agents-course/zh-CN/unit4/hands-on.md)
- [结论](https://huggingface.co/learn/agents-course/zh-CN/unit4/conclusion.md)
- [欢迎来到最后一个单元 [[介绍]]](https://huggingface.co/learn/agents-course/zh-CN/unit4/introduction.md)
- [什么是GAIA？](https://huggingface.co/learn/agents-course/zh-CN/unit4/what-is-gaia.md)
- [那现在呢？我应该学习哪些主题？](https://huggingface.co/learn/agents-course/zh-CN/unit4/additional-readings.md)
- [让我们为函数调用微调模型 (Let's Fine-Tune your model for function-calling)](https://huggingface.co/learn/agents-course/zh-CN/bonus-unit1/fine-tuning.md)
- [什么是函数调用？(What is Function Calling?)](https://huggingface.co/learn/agents-course/zh-CN/bonus-unit1/what-is-function-calling.md)
- [结论 (Conclusion) [[conclusion]]](https://huggingface.co/learn/agents-course/zh-CN/bonus-unit1/conclusion.md)
- [简介 (Introduction)](https://huggingface.co/learn/agents-course/zh-CN/bonus-unit1/introduction.md)
- [游戏中使用 LLM 的最新技术](https://huggingface.co/learn/agents-course/zh-CN/bonus-unit3/state-of-art.md)
- [构建你自己的宝可梦对战智能体](https://huggingface.co/learn/agents-course/zh-CN/bonus-unit3/building_your_pokemon_agent.md)
- [结论](https://huggingface.co/learn/agents-course/zh-CN/bonus-unit3/conclusion.md)
- [介绍](https://huggingface.co/learn/agents-course/zh-CN/bonus-unit3/introduction.md)
- [启动你的宝可梦战斗智能体](https://huggingface.co/learn/agents-course/zh-CN/bonus-unit3/launching_agent_battle.md)
- [从 LLM 到 AI 智能体](https://huggingface.co/learn/agents-course/zh-CN/bonus-unit3/from-llm-to-agents.md)
- [后续单元发布时间表及常见问题解答](https://huggingface.co/learn/agents-course/zh-CN/communication/next-units.md)
- [直播第一课：课程体系解读与首次答疑会](https://huggingface.co/learn/agents-course/zh-CN/communication/live1.md)
- [AI 智能体可观测性与评估](https://huggingface.co/learn/agents-course/zh-CN/bonus_unit2/what-is-agent-observability-and-evaluation.md)
- [附加单元 2：AI 智能体(AI Agent)的可观测性与评估](https://huggingface.co/learn/agents-course/zh-CN/bonus_unit2/monitoring-and-evaluating-agents-notebook.md)
- [AI 智能体(AI Agent)的可观测性与评估](https://huggingface.co/learn/agents-course/zh-CN/bonus_unit2/introduction.md)
- [测验：评估 AI 智能体](https://huggingface.co/learn/agents-course/zh-CN/bonus_unit2/quiz.md)
- [第一单元测验 (Unit 1 Quiz)](https://huggingface.co/learn/agents-course/zh-CN/unit1/final-quiz.md)
- [什么是大语言模型（LLMs）？](https://huggingface.co/learn/agents-course/zh-CN/unit1/what-are-llms.md)
- [动作：使智能体能够与环境交互](https://huggingface.co/learn/agents-course/zh-CN/unit1/actions.md)
- [快速自测（不计分）[[quiz2]]](https://huggingface.co/learn/agents-course/zh-CN/unit1/quiz2.md)
- [总结 [[conclusion]]](https://huggingface.co/learn/agents-course/zh-CN/unit1/conclusion.md)
- [什么是工具？](https://huggingface.co/learn/agents-course/zh-CN/unit1/tools.md)
- [消息和特殊 Tokens (Messages and Special Tokens)](https://huggingface.co/learn/agents-course/zh-CN/unit1/messages-and-special-tokens.md)
- [使用 smolagents 创建我们的第一个智能体](https://huggingface.co/learn/agents-course/zh-CN/unit1/tutorial.md)
- [什么是智能体？](https://huggingface.co/learn/agents-course/zh-CN/unit1/what-are-agents.md)
- [目录](https://huggingface.co/learn/agents-course/zh-CN/unit1/README.md)
- [思维机制：内部推理与 ReAct 方法](https://huggingface.co/learn/agents-course/zh-CN/unit1/thoughts.md)
- [Observe: 整合反馈以反思和调整](https://huggingface.co/learn/agents-course/zh-CN/unit1/observations.md)
- [智能体简介 (Introduction to Agents)](https://huggingface.co/learn/agents-course/zh-CN/unit1/introduction.md)
- [通过思考-行动-观察循环理解 AI 智能体 (Understanding AI Agents through the Thought-Action-Observation Cycle)](https://huggingface.co/learn/agents-course/zh-CN/unit1/agent-steps-and-structure.md)
- [小测验（不计分）[[quiz1]]](https://huggingface.co/learn/agents-course/zh-CN/unit1/quiz1.md)
- [简单智能体库 (Dummy Agent Library)](https://huggingface.co/learn/agents-course/zh-CN/unit1/dummy-agent-library.md)
- [启航准备：开启学习之旅 ⛵](https://huggingface.co/learn/agents-course/zh-CN/unit0/onboarding.md)
- [欢迎加入 🤗 AI Agents 课程 [[introduction]]](https://huggingface.co/learn/agents-course/zh-CN/unit0/introduction.md)
- [（选读） Discord 101 [[discord-101]]](https://huggingface.co/learn/agents-course/zh-CN/unit0/discord101.md)

### 智能体框架介绍
https://huggingface.co/learn/agents-course/zh-CN/unit2/introduction.md

# 智能体框架介绍

欢迎来到第二单元，在这里**我们将探索不同的智能体框架（agentic frameworks）**，这些框架可用于构建强大的智能体应用。

我们将学习：

- 在单元 2.1：[smolagents](https://huggingface.co/docs/smolagents/en/index)  
- 在单元 2.2：[LlamaIndex](https://www.llamaindex.ai/)
- 在单元 2.3：[LangGraph](https://www.langchain.com/langgraph)

让我们开始吧！🕵

## 何时使用智能体框架

**构建围绕大语言模型（LLMs）的应用时，并不总是需要智能体框架**。它们在工作流中提供了灵活性，可以高效地解决特定任务，但并非总是必需的。

有时，**预定义的工作流足以满足用户请求**，并且没有真正需要智能体框架。如果构建智能体的方法很简单，比如一系列提示，使用纯代码可能就足够了。优势在于开发者将**完全控制和理解他们的系统，没有抽象层**。

然而，当工作流变得更加复杂时，例如让大语言模型调用函数或使用多个智能体，这些抽象开始变得有用。

考虑到这些想法，我们已经可以确定对一些功能的需求：

* 一个驱动系统的*大语言模型引擎*。
* 智能体可以访问的*工具列表*。
* 用于从大语言模型输出中提取工具调用的*解析器*。
* 与解析器同步的*系统提示*。
* 一个*记忆系统*。
* *错误日志和重试机制*以控制大语言模型的错误。

我们将探讨这些主题在各种框架中如何解决，包括 `smolagents`、`LlamaIndex` 和 `LangGraph`。

## 智能体框架单元

| 框架  | 描述 | 单元作者 |
|------------|----------------|----------------|
| [smolagents](./smolagents/introduction) | 由 Hugging Face 开发的智能体框架。 | Sergio Paniego - [HF](https://huggingface.co/sergiopaniego) - [X](https://x.com/sergiopaniego) - [Linkedin](https://www.linkedin.com/in/sergio-paniego-blanco) |
| [Llama-Index](./llama-index/introduction) | 将上下文增强智能体带至生产端的端到端工具 | David Berenstein - [HF](https://huggingface.co/davidberenstein1957) - [X](https://x.com/davidberenstei) - [Linkedin](https://www.linkedin.com/in/davidberenstein) |
| [LangGraph](./langgraph/introduction) | 允许对智能体进行有序协调的智能体 | Joffrey THOMAS - [HF](https://huggingface.co/Jofthomas) - [X](https://x.com/Jthmas404) - [Linkedin](https://www.linkedin.com/in/joffrey-thomas) |

### 快速自测（不计分）[[quiz2]]
https://huggingface.co/learn/agents-course/zh-CN/unit2/llama-index/quiz2.md

# 快速自测（不计分）[[quiz2]]

什么？！又是测验？我们知道，我们知道...😅但这个简短的不计分测验是为了**帮助您巩固刚学到的关键概念**。

本测验涵盖智能体工作流程和交互——这些是构建高效AI智能体的核心组件。

### Q1: AgentWorkflow 在 LlamaIndex 中的主要作用是什么？

---

### Q2: 哪个对象用于跟踪工作流的状态？

---

### Q3: 如果希望智能体记住之前的交互，应该使用哪个方法？

---

### Q4: Agentic RAG 的关键特性是什么？

---

明白了吗？太棒了！现在让我们**简要回顾一下本单元！**

### 在 LlamaIndex 中创建智能工作流
https://huggingface.co/learn/agents-course/zh-CN/unit2/llama-index/workflows.md

# 在 LlamaIndex 中创建智能工作流

LlamaIndex 中的工作流提供了一种结构化方式来将代码组织成可管理的顺序步骤。

这种工作流通过定义由`事件（Events）`触发的`步骤（Steps）`来创建，这些步骤本身也会发出`事件`来触发后续步骤。
让我们看看 Alfred 展示的用于 RAG 任务的 LlamaIndex 工作流。

![工作流示意图](https://huggingface.co/datasets/agents-course/course-images/resolve/main/en/unit2/llama-index/workflows.png)

**工作流具有以下关键优势：**

- 将代码清晰地组织为离散步骤
- 事件驱动架构实现灵活控制流
- 步骤间类型安全的通信
- 内置状态管理
- 支持简单和复杂的智能体交互

正如您可能猜到的，**工作流在保持对整体流程控制的同时，实现了智能体的自主性之间的完美平衡。**

现在让我们学习如何自己创建工作流！

## 创建工作流

> [!TIP]
> 您可以通过 这个笔记本 中的代码进行实践，可使用 Google Colab 运行。

### 基础工作流创建

安装工作流包
如 [LlamaHub 章节](llama-hub) 介绍的，我们可以通过以下命令安装工作流包：

```python
pip install llama-index-utils-workflow
```

我们可以通过定义一个继承自 `Workflow` 的类并用 `@step` 装饰你的函数来创建一个单步工作流。
我们还需要添加 `StartEvent` 和 `StopEvent`，它们是用于指示工作流开始和结束的特殊事件。

```python
from llama_index.core.workflow import StartEvent, StopEvent, Workflow, step

class MyWorkflow(Workflow):
    @step
    async def my_step(self, ev: StartEvent) -> StopEvent:
        # do something here
        return StopEvent(result="Hello, world!")

w = MyWorkflow(timeout=10, verbose=False)
result = await w.run()
```

如您所见，我们现在可以通过调用“w.run()”来运行工作流程。

### 连接多个步骤

为了连接多个步骤，我们**创建在步骤之间传输数据的自定义事件**。
为此，我们需要添加一个在步骤之间传递的“事件”，并将第一步的输出传输到第二步。

```python
from llama_index.core.workflow import Event

class ProcessingEvent(Event):
    intermediate_result: str

class MultiStepWorkflow(Workflow):
    @step
    async def step_one(self, ev: StartEvent) -> ProcessingEvent:
        # Process initial data
        return ProcessingEvent(intermediate_result="Step 1 complete")

    @step
    async def step_two(self, ev: ProcessingEvent) -> StopEvent:
        # Use the intermediate result
        final_result = f"Finished processing: {ev.intermediate_result}"
        return StopEvent(result=final_result)

w = MultiStepWorkflow(timeout=10, verbose=False)
result = await w.run()
result
```

类型提示在这里很重要，因为它可以确保工作流正确执行。让我们把事情复杂化一点吧！

### 循环和分支

类型提示是工作流中最强大的部分，因为它允许我们创建分支、循环和连接以促进更复杂的工作流。

让我们展示一个使用联合运算符 `|` **创建循环** 的示例。
在下面的示例中，我们看到 `LoopEvent` 被作为步骤的输入，也可以作为输出返回。

```python
from llama_index.core.workflow import Event
import random

class ProcessingEvent(Event):
    intermediate_result: str

class LoopEvent(Event):
    loop_output: str

class MultiStepWorkflow(Workflow):
    @step
    async def step_one(self, ev: StartEvent | LoopEvent) -> ProcessingEvent | LoopEvent:
        if random.randint(0, 1) == 0:
            print("Bad thing happened")
            return LoopEvent(loop_output="Back to step one.")
        else:
            print("Good thing happened")
            return ProcessingEvent(intermediate_result="First step complete.")

    @step
    async def step_two(self, ev: ProcessingEvent) -> StopEvent:
        # Use the intermediate result
        final_result = f"Finished processing: {ev.intermediate_result}"
        return StopEvent(result=final_result)

w = MultiStepWorkflow(verbose=False)
result = await w.run()
result
```

### 绘制工作流程

我们还可以绘制工作流程。让我们使用 `draw_all_possible_flows` 函数来绘制工作流程。这会将工作流程存储在 HTML 文件中。

```python
from llama_index.utils.workflow import draw_all_possible_flows

w = ... # as defined in the previous section
draw_all_possible_flows(w, "flow.html")
```

![工作流程图](https://huggingface.co/datasets/agents-course/course-images/resolve/main/en/unit2/llama-index/workflow-draw.png)

课程中我们将介绍最后一个很酷的技巧，即向工作流添加状态的能力。

### 状态管理

当您想要跟踪工作流的状态时，状态管理非常有用，这样每个步骤都可以访问相同的状态。
我们可以在步骤函数中的参数上使用“上下文”类型提示来实现这一点。

```python
from llama_index.core.workflow import Context, StartEvent, StopEvent

@step
async def query(self, ctx: Context, ev: StartEvent) -> StopEvent:
    # 存储在上下文中
    await ctx.store.set("query", "What is the capital of France?")

    # 根据上下文和事件做某事
    val = ...

    # 从上下文中检索
    query = await ctx.store.get("query")

    return StopEvent(result=result)
```

太棒了！现在您知道如何在 LlamaIndex 中创建基本工作流了！

> [!TIP]
> 工作流还有一些更复杂的细微差别，您可以在LlamaIndex 文档中了解。

但是，还有另一种创建工作流的方法，它依赖于 `AgentWorkflow` 类。让我们看看如何使用它来创建多智能体工作流。

## 使用多智能体工作流自动化工作流

我们可以使用**`AgentWorkflow` 类来创建多智能体工作流**，而无需手动创建工作流。
`AgentWorkflow` 使用工作流智能体，允许您创建一个或多个智能体的系统，这些智能体可以根据其专门功能进行协作并相互交接任务。
这可以构建复杂的智能体系统，其中不同的智能体处理任务的不同方面。
我们将从`llama_index.core.agent.workflow` 导入智能体类，而不是从`llama_index.core.agent` 导入类。
在`AgentWorkflow` 构造函数中，必须将一个智能体指定为根智能体。
当用户消息传入时，它首先被路由到根智能体。

然后每个智能体可以：

- 使用他们的工具直接处理请求
- 交接给更适合该任务的另一个智能体
- 向用户返回响应

让我们看看如何创建多智能体工作流。

```python
from llama_index.core.agent.workflow import AgentWorkflow, ReActAgent
from llama_index.llms.huggingface_api import HuggingFaceInferenceAPI

# 定义一些工具
def add(a: int, b: int) -> int:
    """Add two numbers."""
    return a + b

def multiply(a: int, b: int) -> int:
    """Multiply two numbers."""
    return a * b

llm = HuggingFaceInferenceAPI(model_name="Qwen/Qwen2.5-Coder-32B-Instruct")

# 我们可以直接传递函数，而无需 FunctionTool——fn/docstring 会被解析为名称/描述
multiply_agent = ReActAgent(
    name="multiply_agent",
    description="Is able to multiply two integers",
    system_prompt="A helpful assistant that can use a tool to multiply numbers.",
    tools=[multiply],
    llm=llm,
)

addition_agent = ReActAgent(
    name="add_agent",
    description="Is able to add two integers",
    system_prompt="A helpful assistant that can use a tool to add numbers.",
    tools=[add],
    llm=llm,
)

# 创建工作流
workflow = AgentWorkflow(
    agents=[multiply_agent, addition_agent],
    root_agent="multiply_agent",
)

# 运行系统
response = await workflow.run(user_msg="Can you add 5 and 3?")
```

智能体工具还可以修改我们前面提到的工作流状态。在启动工作流之前，我们可以提供一个可供所有智能体使用的初始状态字典。
状态存储在工作流上下文的 state 键中。它将被注入到 state_prompt 中，以增强每个新用户消息。

让我们通过修改前面的示例来注入一个计数器来计数函数调用：

```python
from llama_index.core.workflow import Context

# 定义一些工具
async def add(ctx: Context, a: int, b: int) -> int:
    """Add two numbers."""
    # update our count
    cur_state = await ctx.store.get("state")
    cur_state["num_fn_calls"] += 1
    await ctx.store.set("state", cur_state)

    return a + b

async def multiply(ctx: Context, a: int, b: int) -> int:
    """Multiply two numbers."""
    # update our count
    cur_state = await ctx.store.get("state")
    cur_state["num_fn_calls"] += 1
    await ctx.store.set("state", cur_state)

    return a * b

...

workflow = AgentWorkflow(
    agents=[multiply_agent, addition_agent],
    root_agent="multiply_agent"
    initial_state={"num_fn_calls": 0},
    state_prompt="Current state: {state}. User message: {msg}",
)

# 使用上下文运行工作流程
ctx = Context(workflow)
response = await workflow.run(user_msg="Can you add 5 and 3?", ctx=ctx)

# 拉出并检查状态
state = await ctx.store.get("state")
print(state["num_fn_calls"])
```

恭喜！您现在已经掌握了 LlamaIndex 中 Agent 的基础知识！🎉

让我们继续进行最后一次测验来巩固您的知识！🚀

### 结语
https://huggingface.co/learn/agents-course/zh-CN/unit2/llama-index/conclusion.md

# 结语

恭喜完成第二单元 `llama-index` 模块的学习 🥳

您已掌握 `llama-index` 的核心基础，并学会了如何构建自主式工作流！
现在凭借掌握的 `llama-index` 技能，您可以开始创建解决实际任务的搜索引擎。

在本单元的下个模块中，您将学习**如何用 LangGraph 构建智能体**。

最后，我们诚挚希望**听取您对课程的评价和改进建议**。
如有任何反馈，请👉[填写此表单](https://docs.google.com/forms/d/e/1FAIpQLSe9VaONn0eglax0uTwi29rIn4tM7H2sYmmybmG5jJNlE5v0xA/viewform?usp=dialog)

### 持续学习，保持热情 🤗

### 在 LlamaIndex 中使用工具
https://huggingface.co/learn/agents-course/zh-CN/unit2/llama-index/tools.md

# 在 LlamaIndex 中使用工具

**定义清晰明确的工具集对性能至关重要。** 正如我们在[第一单元](../../unit1/tools)中讨论的，清晰的工具接口更便于 LLM 使用。
就像人类工程师使用的软件API接口一样，如果工具的工作原理容易理解，LLM就能更好地利用它。

LlamaIndex 中主要包含**四种工具类型**：

![工具](https://huggingface.co/datasets/agents-course/course-images/resolve/main/en/unit2/llama-index/tools.png)

1. `FunctionTool`：将任意 Python 函数转换为智能体可以使用的工具。它能自动识别函数的工作原理。
2. `QueryEngineTool`：让智能体能够使用查询引擎的工具。由于智能体本身基于查询引擎构建，因此它们也可以将其他智能体作为工具使用。
3. `Toolspecs`：由社区创建的预设工具集，通常包含针对特定服务（如 Gmail）的工具。
4. `Utility Tools`：帮助处理来自其他工具的大量数据的特殊工具。

下面我们将详细讲解每种工具的使用方法。

## 创建FunctionTool

> [!TIP]
> 您可以通过这个 Notebook中的代码进行实践（支持 Google Colab 运行）。

FunctionTool 提供了一种简单的方式，可以将任意 Python 函数封装成智能体可用的工具。
你可以传递同步或异步函数给这个工具，并可选地指定`name`和`description`参数。
工具名称和描述尤为重要，它们能帮助智能体理解何时以及如何有效地使用该工具。
以下示例演示了如何创建并调用 FunctionTool。

```python
from llama_index.core.tools import FunctionTool

def get_weather(location: str) -> str:
    """Useful for getting the weather for a given location."""
    print(f"Getting weather for {location}")
    return f"The weather in {location} is sunny"

tool = FunctionTool.from_defaults(
    get_weather,
    name="my_weather_tool",
    description="Useful for getting the weather for a given location.",
)
tool.call("New York")
```

> [!TIP]
> 使用带有函数调用的智能体或 LLM 时，所选工具（以及为该工具编写的参数）在很大程度上取决于工具名称以及工具用途和参数的描述。在函数调用指南。

## 创建 QueryEngineTool

我们在上一单元中定义的 `QueryEngine` 可以使用 `QueryEngineTool` 类轻松转换为工具。
让我们看看如何在下面的示例中从 `QueryEngine` 创建 `QueryEngineTool`。

```python
from llama_index.core import VectorStoreIndex
from llama_index.core.tools import QueryEngineTool
from llama_index.llms.huggingface_api import HuggingFaceInferenceAPI
from llama_index.embeddings.huggingface_api import HuggingFaceInferenceAPIEmbedding
from llama_index.vector_stores.chroma import ChromaVectorStore

embed_model = HuggingFaceInferenceAPIEmbedding("BAAI/bge-small-en-v1.5")

db = chromadb.PersistentClient(path="./alfred_chroma_db")
chroma_collection = db.get_or_create_collection("alfred")
vector_store = ChromaVectorStore(chroma_collection=chroma_collection)

index = VectorStoreIndex.from_vector_store(vector_store, embed_model=embed_model)

llm = HuggingFaceInferenceAPI(model_name="Qwen/Qwen2.5-Coder-32B-Instruct")
query_engine = index.as_query_engine(llm=llm)
tool = QueryEngineTool.from_defaults(query_engine, name="some useful name", description="some useful description")
```

## 创建 Toolspecs

将 `ToolSpecs` 视为可以和谐协作的工具集合 - 就像一个组织良好的专业工具包。
就像机械师的工具包包含用于车辆维修的互补工具一样，`ToolSpec` 会将相关工具组合起来用于特定目的。
例如，会计智能体的 `ToolSpec` 可以优雅地集成电子表格功能、电子邮件功能和计算工具，以精确高效地处理财务任务。

安装 Google Toolspec
正如 [LlamaHub 部分](llama-hub) 中介绍的那样，我们可以使用以下命令安装 Google toolspec：

```python
pip install llama-index-tools-google
```

现在我们可以加载工具规范并将其转换为工具列表。

```python
from llama_index.tools.google import GmailToolSpec

tool_spec = GmailToolSpec()
tool_spec_list = tool_spec.to_tool_list()
```

为了更详细地了解这些工具，我们可以查看每个工具的`元数据`。

```python
[(tool.metadata.name, tool.metadata.description) for tool in tool_spec_list]
```

## 实用工具

很多时候，直接查询 API **可能会返回过量数据**，其中部分可能无关紧要、超出 LLM 的上下文窗口容量，或导致不必要的 token 消耗。
让我们详细了解以下两个主要实用工具：

1. `OnDemandToolLoader`：该工具可将任何现有的 LlamaIndex 数据加载器（BaseReader 类）转化为智能体可使用的工具。该工具可通过调用数据加载器`load_data`所需的所有参数以及自然语言查询字符串来触发。在执行过程中，我们首先从数据加载器加载数据，建立索引（例如使用向量存储），然后进行"按需"查询。这三个步骤都在单个工具调用中完成。
2. `LoadAndSearchToolSpec`：该工具规范接受任何现有工具作为输入。作为工具规范，它实现了`to_tool_list`方法，调用该方法时会返回两个工具：加载工具和搜索工具。加载工具的执行会调用底层工具并对输出建立索引（默认使用向量索引）。搜索工具的执行会接受查询字符串作为输入并调用底层索引。

> [!TIP]
> 您可以在LlamaHub找到各种工具规范和实用工具

现在我们已经理解了 LlamaIndex 中智能体和工具的基础知识，接下来让我们看看如何**使用 LlamaIndex 创建可配置且易管理的工作流程！**

### 目录
https://huggingface.co/learn/agents-course/zh-CN/unit2/llama-index/README.md

# 目录

此 LlamaIndex 框架大纲是课程第 2 单元的一部分。您可以在 hf.co/learn 上访问有关 LlamaIndex 的第 2 单元 👉 这里

| 标题                            | 描述                                                                          |
| -------------------------------- | ------------------------------------------------------------------------------------ |
| [介绍](introduction.mdx) | LlamaIndex 简介                                                                    |
| [LlamaHub](llama-hub.mdx)        | LlamaHub：集成、智能体和工具注册表                              |
| [组件](components.mdx)     |  组件：工作流的构件                                         |
| [工具](tools.mdx)               |  工具：如何在 LlamaIndex 中构建工具                                                       |
| [测验 1](quiz1.mdx)              | 测验 1                                                                               |
| [智能体](agents.mdx)             | 智能体：如何在 LlamaIndex 中建立智能体                                            |
| [工作流](workflows.mdx)       | 工作流：由按顺序执行的组件组成的一系列步骤和事件 |
| [测验  2](quiz2.mdx)              | 测验 2                                                                               |
| [结论](conclusion.mdx)     | 结论                                                                           |

### 在 LlamaIndex 中使用智能体
https://huggingface.co/learn/agents-course/zh-CN/unit2/llama-index/agents.md

# 在 LlamaIndex 中使用智能体

还记得我们之前那位得力的管家智能体 Alfred 吗？现在他要迎来重大升级了！
在了解了 LlamaIndex 中的工具后，我们可以赋予 Alfred 新的能力来更好地服务我们。

不过在继续之前，让我们先回顾一下智能体（如 Alfred）的核心机制。
在第一单元中我们学习到：

> 智能体是一个利用 AI 模型与环境交互以实现用户定义目标的系统。它通过结合推理、规划和动作执行（通常通过外部工具）来完成各种任务。

LlamaIndex 支持**三种主要类型的推理智能体**：

![智能体类型](https://huggingface.co/datasets/agents-course/course-images/resolve/main/en/unit2/llama-index/agents.png)

1. `函数调用智能体` - 适用于支持调用特定函数的 AI 模型
2. `ReAct 智能体` - 适用于具有聊天或文本完成能力的 AI 模型，擅长处理复杂推理任务
3. `高级自定义智能体` - 使用更复杂的方法处理高阶任务和工作流

> [!TIP]
> 有关高级智能体的更多信息，请访问 BaseWorkflowAgent

## 初始化智能体

> [!TIP]
> 您可以通过 这个 Notebook 跟随代码实践（使用 Google Colab 运行）。

创建智能体时，我们首先需要为其提供**定义其能力的功能/工具集合**。
让我们看看如何使用基础工具创建智能体。当前实现中，智能体会自动使用函数调用 API（如果可用），或标准的 ReAct 智能体循环。

支持工具/函数 API 的 LLM 是相对较新的技术，它们通过避免特定提示工程、允许 LLM 根据提供的模式创建工具调用，提供了强大的工具调用能力。

ReAct 智能体同样擅长复杂推理任务，可与任何具备聊天或文本完成能力的 LLM 配合使用。这类智能体的响应更详细，会展示其决策背后的推理过程。

```python
from llama_index.llms.huggingface_api import HuggingFaceInferenceAPI
from llama_index.core.agent.workflow import AgentWorkflow
from llama_index.core.tools import FunctionTool

# 定义示例工具--类型注释、函数名和 docstrings 都包含在解析的模式中！
def multiply(a: int, b: int) -> int:
    """Multiplies two integers and returns the resulting integer"""
    return a * b

# 初始化 llm
llm = HuggingFaceInferenceAPI(model_name="Qwen/Qwen2.5-Coder-32B-Instruct")

# 初始化 agent
agent = AgentWorkflow.from_tools_or_functions(
    [FunctionTool.from_defaults(multiply)],
    llm=llm
)
```

**智能体默认是无状态的**，如需记忆过往交互，需显式使用 `Context` 对象。
这在需要记忆历史交互的场景中非常有用，例如：需要跨消息保持上下文的聊天机器人，或需要追踪长期任务进度的任务管理器。

```python
# stateless
response = await agent.run("What is 2 times 2?")

# remembering state
from llama_index.core.workflow import Context

ctx = Context(agent)

response = await agent.run("My name is Bob.", ctx=ctx)
response = await agent.run("What was my name again?", ctx=ctx)
```

您会注意到 `LlamaIndex` 中的智能体采用异步模式（使用 Python 的 `await` 操作符）。如果您是 Python 异步编程的新手，或需要复习相关知识，请参考 [官方异步编程指南](https://docs.llamaindex.ai/en/stable/getting_started/async_python/)。

现在我们已经掌握基础知识，接下来让我们探索如何为智能体赋予更复杂的工具能力。

## 使用 QueryEngineTools 创建 RAG 智能体

**智能体增强检索（Agentic RAG）** 是通过智能体实现数据问答的强大范式。我们可以为 Alfred 配备多种工具来辅助问题解答。
但不同于传统 RAG 直接基于文档回答问题，Alfred 能够自主决定是否使用其他工具或流程来响应查询。

![智能体增强 RAG](https://huggingface.co/datasets/agents-course/course-images/resolve/main/en/unit2/llama-index/agentic-rag.png)

将 `QueryEngine` 封装为智能体工具非常简单。
在此过程中，我们需要**定义工具名称和描述**，这些元数据将帮助 LLM 正确选择和使用工具。
以下示例演示如何基于[组件章节](02_components)创建的 `QueryEngine` 加载 `QueryEngineTool`：

```python
from llama_index.core.tools import QueryEngineTool

query_engine = index.as_query_engine(llm=llm, similarity_top_k=3) # as shown in the previous section

query_engine_tool = QueryEngineTool.from_defaults(
    query_engine=query_engine,
    name="name",
    description="a specific description",
    return_direct=False,
)
query_engine_agent = AgentWorkflow.from_tools_or_functions(
    [query_engine_tool],
    llm=llm,
    system_prompt="You are a helpful assistant that has access to a database containing persona descriptions. "
)
```

## 创建多智能体系统

`AgentWorkflow` 类原生支持多智能体系统。通过为每个智能体分配名称和描述，系统可维护单一活跃会话主体，同时允许智能体之间进行任务交接。

通过精准定义每个智能体的职责边界，我们能够有效提升系统响应消息时的整体准确性。

**LlamaIndex 中的智能体也可直接作为其他智能体的工具**，这种设计支持构建复杂的定制化场景：

```python
from llama_index.core.agent.workflow import (
    AgentWorkflow,
    FunctionAgent,
    ReActAgent,
)

# Define some tools
def add(a: int, b: int) -> int:
    """Add two numbers."""
    return a + b

def subtract(a: int, b: int) -> int:
    """Subtract two numbers."""
    return a - b

# 创建智能体配置
# 注意：我们可以在此使用 FunctionAgent 或 ReActAgent。
# FunctionAgent 适用于具有函数调用 API 的 LLM。
# ReActAgent 适用于任何 LLM。
calculator_agent = ReActAgent(
    name="calculator",
    description="Performs basic arithmetic operations",
    system_prompt="You are a calculator assistant. Use your tools for any math operation.",
    tools=[add, subtract],
    llm=llm,
)

query_agent = ReActAgent(
    name="info_lookup",
    description="Looks up information about XYZ",
    system_prompt="Use your tool to query a RAG system to answer information about XYZ",
    tools=[query_engine_tool],
    llm=llm
)

# 创建并运行工作流程
agent = AgentWorkflow(
    agents=[calculator_agent, query_agent], root_agent="calculator"
)

# 运行系统
response = await agent.run(user_msg="Can you add 5 and 3?")
```

> [!TIP]
> 还没学够吗？在《AgentWorkflow 基本介绍》或《Agent 学习指南》中，您可以了解到更多关于流媒体、上下文序列化和人在环路中的信息！

现在我们已经掌握了 LlamaIndex 中智能体和工具的基础知识，接下来让我们探索如何利用 LlamaIndex 来**创建可配置、易管理的工作流**！

### LlamaIndex 简介
https://huggingface.co/learn/agents-course/zh-CN/unit2/llama-index/introduction.md

# LlamaIndex 简介

欢迎来到本模块，您将学习如何使用 [LlamaIndex](https://www.llamaindex.ai/) 工具包构建基于大语言模型（LLM）的智能体。

LlamaIndex 是**通过索引和工作流在您的数据上创建 LLM 驱动智能体的完整工具包**。本课程我们将重点关注构建 LlamaIndex 智能体的三个核心部分：**组件**、**智能体与工具**以及**工作流**。

![LlamaIndex](https://huggingface.co/datasets/agents-course/course-images/resolve/main/en/unit2/llama-index/thumbnail.png)

让我们解析这些核心要素及其对智能体的支持：

- **组件**是 LlamaIndex 中的基础构建模块，包括提示词、模型和数据库等。组件通常帮助 LlamaIndex 与其他工具和库进行集成。
- **工具**：提供特定功能的组件，如搜索、计算或访问外部服务。它们是支撑智能体执行任务的基础模块。
- **智能体**：能够自主使用工具并做出决策的独立组件，通过协调工具使用来实现复杂目标。
- **工作流**：按步骤处理逻辑的流程。智能体工作流是一种无需显式使用智能体即可构建智能行为的方式。

## LlamaIndex 的独特之处

虽然LlamaIndex与其他框架（如 smolagents）有相似之处，但具备以下关键优势：

- **清晰的工作流系统**。通过事件驱动和异步优先的语法，工作流帮助您逐步分解智能体的决策过程，实现逻辑的清晰组合与组织。
- **基于 LlamaParse 的高级文档解析** 专为 LlamaIndex 打造的文档解析工具，尽管是付费功能，但提供无缝集成体验。
- **丰富的即用组件** 凭借长期的技术积累，LlamaIndex 与众多框架兼容，提供大量经过验证的可靠组件（如 LLM、检索器、索引等）。
- **LlamaHub** 提供了数百个此类组件、智能体和工具的注册中心，方便您在 LlamaIndex 中直接使用。

这些概念在不同场景中都是创建实用智能体的必要元素。
后续章节我们将深入解析每个概念。
掌握这些知识后，我们将运用所学**通过 Alfred 智能体创建实际应用案例**！

准备好探索 LlamaIndex 的精彩世界了吗？让我们立即启程，**通过 LlamaHub 查找并安装所需集成吧！🚀**

### 小测验（不计分） [[quiz1]]
https://huggingface.co/learn/agents-course/zh-CN/unit2/llama-index/quiz1.md

# 小测验（不计分） [[quiz1]]

到目前为止，我们已经讨论了 LlamaIndex 的关键组件和工具。
是时候做个小测验了，因为**自我测试**是最好的学习方式，也能[避免能力错觉](https://www.coursera.org/lecture/learning-how-to-learn/illusions-of-competence-BuFzf)。
这将帮助您发现**哪些知识需要加强**。

本测验为可选项目，不计入成绩。

### Q1: 什么是 QueryEngine？
以下哪项最能描述 QueryEngine 组件？

---

### Q2: FunctionTools 的作用是什么？
为什么 FunctionTools 对 Agent 很重要？

---

### Q3: LlamaIndex 中的 Toolspecs 是什么？
Toolspecs 的主要目的是什么？

---

### Q4: 创建工具时需要什么？
创建工具时必须包含哪些信息？

---

恭喜完成测验 🥳！如果有错误，请重新阅读章节巩固知识。如果全部正确，您已准备好深入学习这些组件的构建了！

### LlamaIndex 中的组件是什么？
https://huggingface.co/learn/agents-course/zh-CN/unit2/llama-index/components.md

# LlamaIndex 中的组件是什么？

还记得第一单元中我们那位得力的管家智能体 Alfred 吗？
要有效协助我们，Alfred 需要理解我们的请求，并**准备、查找和使用相关信息来帮助完成任务**。
这正是 LlamaIndex 组件发挥作用的地方。

虽然 LlamaIndex 包含众多组件，但**我们将重点关注 `QueryEngine` 组件**。
为什么？因为它可以作为智能体的检索增强生成（RAG）工具。

那么什么是 RAG？大语言模型通过海量数据训练获得通用知识，
但它们可能缺乏相关且最新的特定领域数据。
RAG 通过从您的数据中检索相关信息并传递给 LLM 来解决这个问题。

![RAG](https://huggingface.co/datasets/agents-course/course-images/resolve/main/en/unit2/llama-index/rag.png)

试想 Alfred 的工作机制：

1. 您让 Alfred 帮忙策划晚宴
2. Alfred 需要查看您的日历、饮食偏好和过往成功菜单
3. `QueryEngine` 帮助 Alfred 查找这些信息并用于晚宴策划

这使得 `QueryEngine` 成为在 LlamaIndex 中**构建智能 RAG 工作流的核心组件**。
正如 Alfred 需要检索家庭信息才能发挥作用，任何智能体都需要理解和查找相关数据的能力。
`QueryEngine` 正好提供了这种核心能力。

现在让我们深入探讨如何**组合组件来创建 RAG 流程**。

## 使用组件构建 RAG 流程

> [!TIP]
> 您可以通过 这个 Notebook 跟随代码实践（使用 Google Colab 运行）。

RAG 包含五个关键阶段，这些阶段将构成您构建的大部分应用：

1. **加载**：将数据从原始位置（文本文件、PDF、网站、数据库或 API）导入工作流。LlamaHub 提供数百种集成方案
2. **索引**：创建便于查询的数据结构。对于 LLM 通常意味着创建向量嵌入——数据的语义数值表示，也可以包含其他元数据策略
3. **存储**：索引完成后需要持久化存储，避免重复构建
4. **查询**：支持多种检索策略，包括子查询、多步查询和混合策略
5. **评估**：通过客观指标评估响应准确性、忠实度和响应速度，对比不同策略效果

接下来我们将演示如何使用组件实现这些阶段。

### 数据加载与嵌入

如前所述，LlamaIndex 可以基于您的自有数据进行操作，但**在访问数据之前，我们需要先完成加载**。
LlamaIndex 提供三种主要的数据加载方式：

1. `SimpleDirectoryReader`: 内置加载器，支持从本地目录加载多种文件类型
2. `LlamaParse`: LlamaIndex 官方 PDF 解析工具，提供托管 API 服务
3. `LlamaHub`: 包含数百个数据加载库的注册中心，支持从任意数据源获取数据

> [!TIP]
> 对于复杂数据源，建议深入了解 LlamaHub 加载器和 LlamaParse 解析器的使用方法。

**最简单的数据加载方式是使用 `SimpleDirectoryReader`**。
这个多功能组件可以从文件夹中加载各类文件，并将其转换为 LlamaIndex 可处理的 `Document` 对象。
以下是具体使用方法：

```python
from llama_index.core import SimpleDirectoryReader

reader = SimpleDirectoryReader(input_dir="path/to/directory")
documents = reader.load_data()
```

加载文档后，我们需要将其分解为更小的单元——`Node`对象。
`Node`是原始文档中的文本片段，既便于AI处理，又保留了对原`Document`对象的引用。

`IngestionPipeline`通过两个关键转换步骤帮助我们创建这些节点：
1. `SentenceSplitter`通过自然语句边界将文档拆分为可管理的文本块
2. `HuggingFaceInferenceAPIEmbedding`将每个文本块转换为数值化的向量表示——这种以AI能高效处理的方式捕捉语义信息

该流程能帮助我们以更适合搜索和分析的方式组织文档。

```python
from llama_index.core import Document
from llama_index.embeddings.huggingface_api import HuggingFaceInferenceAPIEmbedding
from llama_index.core.node_parser import SentenceSplitter
from llama_index.core.ingestion import IngestionPipeline

# 通过转换创建管道
pipeline = IngestionPipeline(
    transformations=[
        SentenceSplitter(chunk_overlap=0),
        HuggingFaceInferenceAPIEmbedding(model_name="BAAI/bge-small-en-v1.5"),
    ]
)

nodes = await pipeline.arun(documents=[Document.example()])
```

### 存储与索引文档

创建完`节点`对象后，我们需要对其进行索引，使其可以被搜索，但在此之前，我们需要一个存储数据的地方。

由于我们使用的是摄取管道，因此可以直接在管道上附加一个向量存储来填充数据。
在本例中，我们将使用 `Chroma` 来存储我们的文档。

安装 ChromaDB
正如在[关于 LlamaHub 的章节](llama-hub)中所介绍的，我们可以使用以下命令安装 ChromaDB 向量存储：

```bash
pip install llama-index-vector-stores-chroma
```

```python
import chromadb
from llama_index.vector_stores.chroma import ChromaVectorStore

db = chromadb.PersistentClient(path="./alfred_chroma_db")
chroma_collection = db.get_or_create_collection("alfred")
vector_store = ChromaVectorStore(chroma_collection=chroma_collection)

pipeline = IngestionPipeline(
    transformations=[
        SentenceSplitter(chunk_size=25, chunk_overlap=0),
        HuggingFaceInferenceAPIEmbedding(model_name="BAAI/bge-small-en-v1.5"),
    ],
    vector_store=vector_store,
)
```

> [!TIP]
> An overview of the different vector stores can be found in the LlamaIndex documentation.

这就是向量嵌入的作用所在--通过将查询和节点嵌入同一向量空间，我们可以找到相关的匹配项。
`向量存储索引`会为我们处理这个问题，使用我们在摄取时使用的相同嵌入模型来确保一致性。

让我们看看如何从向量存储和嵌入中创建该索引：

```python
from llama_index.core import VectorStoreIndex
from llama_index.embeddings.huggingface_api import HuggingFaceInferenceAPIEmbedding

embed_model = HuggingFaceInferenceAPIEmbedding(model_name="BAAI/bge-small-en-v1.5")
index = VectorStoreIndex.from_vector_store(vector_store, embed_model=embed_model)
```

所有信息都会自动保存在 `ChromaVectorStore` 对象和传递的目录路径中。

太棒了 现在我们可以轻松保存和加载索引了，让我们探索一下如何以不同方式查询索引。

### 使用提示词和 LLM 查询 VectorStoreIndex

在查询索引之前，我们需要将其转换为查询接口。最常见的转换选项包括：

- `as_retriever`：用于基础文档检索，返回带有相似度得分的`NodeWithScore`对象列表
- `as_query_engine`：用于单次问答交互，返回书面响应
- `as_chat_engine`：用于需要保持跨消息记忆的对话交互，通过聊天历史记录和索引上下文返回书面响应

我们将重点介绍查询引擎（query engine），因为它在类智能体交互中更为常见。
我们还需要向查询引擎传入一个大语言模型（LLM）用于生成响应。

```python
from llama_index.llms.huggingface_api import HuggingFaceInferenceAPI

llm = HuggingFaceInferenceAPI(model_name="Qwen/Qwen2.5-Coder-32B-Instruct")
query_engine = index.as_query_engine(
    llm=llm,
    response_mode="tree_summarize",
)
query_engine.query("What is the meaning of life?")
# The meaning of life is 42
```

### 响应处理

底层实现中，查询引擎不仅使用 LLM 来回答问题，还会采用 `ResponseSynthesizer` 作为响应处理策略。
同样，该组件完全可定制，但默认提供三种高效的工作策略：

- `refine`（迭代优化）: 通过逐个处理每个检索到的文本块来创建并优化答案。每个 Node/文本块都会触发独立的 LLM 调用
- `compact`（紧凑模式，默认）: 与迭代优化类似，但会预先拼接文本块，从而减少 LLM 调用次数
- `tree_summarize`（树状归纳）: 通过遍历所有检索到的文本块并构建答案的树状结构来生成详细响应

> [!TIP]
> 通过底层组合 API 实现查询流程的精细控制。该 API 允许您自定义和优化查询流程的每个步骤，与 Workflows 配合使用效果更佳

由于语言模型的输出具有不可预测性，我们需要**通过评估答案质量**来确保结果的可靠性。

### 评估与可观测性

LlamaIndex 提供**内置评估工具来量化响应质量**。
这些评估器利用 LLM 对回答进行多维度分析。
主要评估器包括：

- `FaithfulnessEvaluator`（忠实性评估器）: 验证答案是否得到上下文的支持
- `AnswerRelevancyEvaluator`（答案相关性评估器）: 评估答案与问题的关联程度
- `CorrectnessEvaluator`（正确性评估器）: 检验答案的正确性

```python
from llama_index.core.evaluation import FaithfulnessEvaluator

query_engine = # from the previous section
llm = # from the previous section

# 查询索引
evaluator = FaithfulnessEvaluator(llm=llm)
response = query_engine.query(
    "What battles took place in New York City in the American Revolution?"
)
eval_result = evaluator.evaluate_response(response=response)
eval_result.passing
```

即使没有直接评估，我们也可以**通过可观察性深入了解我们的系统是如何运行的。**
当我们构建更复杂的工作流程并想要了解每个组件是如何运行的时候，这尤其有用。

安装 LlamaTrace
正如 [LlamaHub 部分](llama-hub) 中介绍的那样，我们可以使用以下命令从 Arize Phoenix 安装 LlamaTrace 回调：

```bash
pip install -U llama-index-callbacks-arize-phoenix
```

此外，我们需要将 `PHOENIX_API_KEY` 环境变量设置为我们的 LlamaTrace API 密钥。我们可以通过以下方式获取此密钥：
- 在 [LlamaTrace](https://llamatrace.com/login) 创建一个帐户
- 在您的帐户设置中生成 API 密钥
- 使用下面代码中的 API 密钥启用跟踪

```python
import llama_index
import os

PHOENIX_API_KEY = ""
os.environ["OTEL_EXPORTER_OTLP_HEADERS"] = f"api_key={PHOENIX_API_KEY}"
llama_index.core.set_global_handler(
    "arize_phoenix",
    endpoint="https://llamatrace.com/v1/traces"
)
```

> [!TIP]
> 想要了解有关组件的更多信息以及如何使用它们？继续您的旅程，阅读组件指南或RAG 指南。

我们已经了解了如何使用组件创建 `QueryEngine`。现在，让我们看看如何**将 `QueryEngine` 用作智能体的工具！**

### LlamaHub 简介
https://huggingface.co/learn/agents-course/zh-CN/unit2/llama-index/llama-hub.md

# LlamaHub 简介

**LlamaHub 是一个包含数百个集成组件、智能体和工具的注册中心，这些资源均可用于LlamaIndex框架。**

![LlamaHub](https://huggingface.co/datasets/agents-course/course-images/resolve/main/en/unit2/llama-index/llama-hub.png)

在本课程中我们将使用多种集成组件，因此让我们首先了解LlamaHub及其如何助力开发。

接下来我们将学习如何查找和安装所需组件的依赖项。

## 安装

LlamaIndex的安装说明可通过结构清晰的**[LlamaHub官网](https://llamahub.ai/)**获取。
初次接触可能会感到有些复杂，但大多数**安装命令都遵循易于记忆的格式**：

```bash
pip install llama-index-{component-type}-{framework-name}
```

让我们尝试使用 [Hugging Face 推理 API 集成](https://llamahub.ai/l/llms/llama-index-llms-huggingface-api?from=llms) 安装 LLM 组件的依赖项。

```bash
pip install llama-index-llms-huggingface-api
```

## 用法

安装后，我们可以看到使用模式。您会注意到导入路径跟在安装命令后面！
下面，我们可以看到**LLM 组件的 Hugging Face 推理 API** 的使用示例。

```python
from llama_index.llms.huggingface_api import HuggingFaceInferenceAPI

llm = HuggingFaceInferenceAPI(
    model_name="Qwen/Qwen2.5-Coder-32B-Instruct",
    temperature=0.7,
    max_tokens=100,
    token="hf_xxx",
)

llm.complete("Hello, how are you?")
# I am good, how can I help you today?
```

太棒了，我们现在知道如何查找、安装和使用我们所需组件的集成。
**让我们深入了解这些组件**，看看如何使用它们来构建我们自己的智能体。

### 多智能体系统
https://huggingface.co/learn/agents-course/zh-CN/unit2/smolagents/multi_agent_systems.md

# 多智能体系统

多智能体系统使**专业智能体能够在复杂任务上进行协作**，提高模块化、可扩展性和稳健性。不依赖单一智能体，任务分配给具有不同能力的智能体。

在 **smolagents** 中，不同的智能体可以组合起来生成 Python 代码、调用外部工具、执行网络搜索等。通过编排这些智能体，我们可以创建强大的工作流。

一个典型的设置可能包括：

- **管理智能体（Manager Agent）** 用于任务委派
- **代码解释器智能体（Code Interpreter Agent）** 用于代码执行
- **网络搜索智能体（Web Search Agent）** 用于信息检索

下图说明了一个简单的多智能体架构，其中**管理智能体**协调**代码解释器工具**和**网络搜索智能体**，后者利用像 `DuckDuckGoSearchTool` 和 `VisitWebpageTool` 这样的工具来收集相关信息。

## 多智能体系统实战

多智能体系统由多个专业智能体在 **编排智能体（Orchestrator Agent）** 的协调下共同工作组成。这种方法通过在具有不同角色的智能体之间分配任务来实现复杂的工作流。

例如，**多智能体 RAG 系统**可以整合：

- **网络智能体（Web Agent）** 用于浏览互联网。
- **检索智能体（Retriever Agent）** 用于从知识库获取信息。
- **图像生成智能体（Image Generation Agent）** 用于生成视觉内容。

所有这些智能体在管理任务委派和交互的编排者下运行。

## 用多智能体层次结构解决复杂任务

> [!TIP]
> 你可以在{" "}
>        href="https://huggingface.co/agents-course/notebooks/blob/main/unit2/smolagents/multiagent_notebook.ipynb"
>     target="_blank"
>   >
>     这个笔记本
>   {" "}
>   中跟随代码，可以使用 Google Colab 运行。

接待会即将到来！在你的帮助下，阿尔弗雷德现在几乎完成了准备工作。

但现在有个问题：蝙蝠车不见了。阿尔弗雷德需要找到替代品，而且要快。

幸运的是，已经有一些关于布鲁斯·韦恩生活的传记电影，所以也许阿尔弗雷德可以从某个电影拍摄现场留下的汽车中获取一辆，并将其重新改造到现代标准，这当然会包括完全自动驾驶选项。

但这可能在世界各地的任何拍摄地点——可能数量众多。

所以阿尔弗雷德需要你的帮助。你能构建一个能够解决这个任务的智能体吗？

> 👉 Find all Batman filming locations in the world, calculate the time to transfer via boat to there, and represent them on a map, with a color varying by boat transfer time. Also represent some supercar factories with the same boat transfer time.

让我们来构建这个！

这个例子需要一些额外的包，所以首先安装它们：

```bash
pip install 'smolagents[litellm]' matplotlib geopandas shapely kaleido -q
```

### 我们首先制作一个工具来获取货运飞机转运时间。

```python
import math
from typing import Optional, Tuple

from smolagents import tool

@tool
def calculate_cargo_travel_time(
    origin_coords: Tuple[float, float],
    destination_coords: Tuple[float, float],
    cruising_speed_kmh: Optional[float] = 750.0,  # 货运飞机的平均速度
) -> float:
    """
    Calculate the travel time for a cargo plane between two points on Earth using great-circle distance.

    Args:
        origin_coords: Tuple of (latitude, longitude) for the starting point
        destination_coords: Tuple of (latitude, longitude) for the destination
        cruising_speed_kmh: Optional cruising speed in km/h (defaults to 750 km/h for typical cargo planes)

    Returns:
        float: The estimated travel time in hours

    Example:
        >>> # Chicago (41.8781° N, 87.6298° W) to Sydney (33.8688° S, 151.2093° E)
        >>> result = calculate_cargo_travel_time((41.8781, -87.6298), (-33.8688, 151.2093))
    """

    def to_radians(degrees: float) -> float:
        return degrees * (math.pi / 180)

    # 提取坐标
    lat1, lon1 = map(to_radians, origin_coords)
    lat2, lon2 = map(to_radians, destination_coords)

    # 地球半径（公里）
    EARTH_RADIUS_KM = 6371.0

    # 使用半正矢公式计算大圆距离
    dlon = lon2 - lon1
    dlat = lat2 - lat1

    a = (
        math.sin(dlat / 2) ** 2
        + math.cos(lat1) * math.cos(lat2) * math.sin(dlon / 2) ** 2
    )
    c = 2 * math.asin(math.sqrt(a))
    distance = EARTH_RADIUS_KM * c

    # 增加10%以考虑非直接路线和空中交通管制
    actual_distance = distance * 1.1

    # 计算飞行时间
    # 为起飞和着陆程序增加1小时
    flight_time = (actual_distance / cruising_speed_kmh) + 1.0

    # 格式化结果
    return round(flight_time, 2)

print(calculate_cargo_travel_time((41.8781, -87.6298), (-33.8688, 151.2093)))
```

### 设置智能体

对于模型提供商，我们使用 Together AI，这是 [Hub 上的新推理提供商](https://huggingface.co/blog/inference-providers)之一！

GoogleSearchTool 使用 [Serper API](https://serper.dev) 搜索网络，因此这需要设置环境变量 `SERPAPI_API_KEY` 并传递 `provider="serpapi"` 或者拥有 `SERPER_API_KEY` 并传递 `provider=serper`。

如果你没有设置任何 Serp API 提供商，你可以使用 `DuckDuckGoSearchTool`，但请注意它有速率限制。

```python
import os
from PIL import Image
from smolagents import CodeAgent, GoogleSearchTool, InferenceClientModel, VisitWebpageTool

model = InferenceClientModel(model_id="Qwen/Qwen2.5-Coder-32B-Instruct", provider="together")
```

我们可以先创建一个简单的智能体作为基线，为我们提供一个简单的报告。

```python
task = """Find all Batman filming locations in the world, calculate the time to transfer via cargo plane to here (we're in Gotham, 40.7128° N, 74.0060° W), and return them to me as a pandas dataframe.
Also give me some supercar factories with the same cargo plane transfer time."""
```

```python
agent = CodeAgent(
    model=model,
    tools=[GoogleSearchTool("serper"), VisitWebpageTool(), calculate_cargo_travel_time],
    additional_authorized_imports=["pandas"],
    max_steps=20,
)
```

```python
result = agent.run(task)
```

```python
result
```

在我们的例子中，它生成了这个输出：

```python
|  | Location                                             | Travel Time to Gotham (hours) |
|--|------------------------------------------------------|------------------------------|
| 0  | Necropolis Cemetery, Glasgow, Scotland, UK         | 8.60                         |
| 1  | St. George's Hall, Liverpool, England, UK         | 8.81                         |
| 2  | Two Temple Place, London, England, UK             | 9.17                         |
| 3  | Wollaton Hall, Nottingham, England, UK           | 9.00                         |
| 4  | Knebworth House, Knebworth, Hertfordshire, UK    | 9.15                         |
| 5  | Acton Lane Power Station, Acton Lane, Acton, UK  | 9.16                         |
| 6  | Queensboro Bridge, New York City, USA            | 1.01                         |
| 7  | Wall Street, New York City, USA                  | 1.00                         |
| 8  | Mehrangarh Fort, Jodhpur, Rajasthan, India       | 18.34                        |
| 9  | Turda Gorge, Turda, Romania                      | 11.89                        |
| 10 | Chicago, USA                                     | 2.68                         |
| 11 | Hong Kong, China                                 | 19.99                        |
| 12 | Cardington Studios, Northamptonshire, UK        | 9.10                         |
| 13 | Warner Bros. Leavesden Studios, Hertfordshire, UK | 9.13                         |
| 14 | Westwood, Los Angeles, CA, USA                  | 6.79                         |
| 15 | Woking, UK (McLaren)                             | 9.13                         |
```

我们可以通过添加一些专门的规划步骤和更多的提示来进一步改进这一点。

规划步骤允许智能体提前思考并规划其下一步行动，这对于更复杂的任务非常有用。

```python
agent.planning_interval = 4

detailed_report = agent.run(f"""
You're an expert analyst. You make comprehensive reports after visiting many websites.
Don't hesitate to search for many queries at once in a for loop.
For each data point that you find, visit the source url to confirm numbers.

{task}
""")

print(detailed_report)
```

```python
detailed_report
```

在我们的例子中，它生成了这个输出：

```python
|  | Location                                         | Travel Time (hours) |
|--|--------------------------------------------------|---------------------|
| 0  | Bridge of Sighs, Glasgow Necropolis, Glasgow, UK | 8.6                 |
| 1  | Wishart Street, Glasgow, Scotland, UK         | 8.6                 |
```

感谢这些快速更改，我们通过简单地为我们的智能体提供详细提示，并赋予它规划能力，获得了更加简洁的报告！

模型的上下文窗口正在快速填满。所以**如果我们要求我们的智能体将详细搜索的结果与另一个结合起来，它将变得更慢，并且会迅速增加 token 数量和成本**。

➡️ 我们需要改进系统的结构。

### ✌️ 在两个智能体之间分割任务

多智能体结构允许在不同子任务之间分离记忆，带来两大好处：

- 每个智能体更专注于其核心任务，因此性能更佳
- 分离记忆减少了每个步骤的输入 token 数量，从而减少延迟和成本。

让我们创建一个团队，包含一个专门的网络搜索智能体，由另一个智能体管理。

管理智能体应该具有绘图功能来编写其最终报告：因此让我们给它访问额外导入的权限，包括 `matplotlib` 和 `geopandas` + `shapely` 用于空间绘图。

```python
model = InferenceClientModel(
    "Qwen/Qwen2.5-Coder-32B-Instruct", provider="together", max_tokens=8096
)

web_agent = CodeAgent(
    model=model,
    tools=[
        GoogleSearchTool(provider="serper"),
        VisitWebpageTool(),
        calculate_cargo_travel_time,
    ],
    name="web_agent",
    description="Browses the web to find information",
    verbosity_level=0,
    max_steps=10,
)
```

管理智能体需要进行一些较重的思考工作。

所以我们给它更强大的模型 [DeepSeek-R1](https://huggingface.co/deepseek-ai/DeepSeek-R1)，并添加 `planning_interval` 到组合中。

```python
from smolagents.utils import encode_image_base64, make_image_url
from smolagents import OpenAIServerModel

def check_reasoning_and_plot(final_answer, agent_memory):
    final_answer
    multimodal_model = OpenAIServerModel("gpt-4o", max_tokens=8096)
    filepath = "saved_map.png"
    assert os.path.exists(filepath), "Make sure to save the plot under saved_map.png!"
    image = Image.open(filepath)
    prompt = (
        f"Here is a user-given task and the agent steps: {agent_memory.get_succinct_steps()}. Now here is the plot that was made."
        "Please check that the reasoning process and plot are correct: do they correctly answer the given task?"
        "First list reasons why yes/no, then write your final decision: PASS in caps lock if it is satisfactory, FAIL if it is not."
        "Don't be harsh: if the plot mostly solves the task, it should pass."
        "To pass, a plot should be made using px.scatter_map and not any other method (scatter_map looks nicer)."
    )
    messages = [
        {
            "role": "user",
            "content": [
                {
                    "type": "text",
                    "text": prompt,
                },
                {
                    "type": "image_url",
                    "image_url": {"url": make_image_url(encode_image_base64(image))},
                },
            ],
        }
    ]
    output = multimodal_model(messages).content
    print("Feedback: ", output)
    if "FAIL" in output:
        raise Exception(output)
    return True

manager_agent = CodeAgent(
    model=InferenceClientModel("deepseek-ai/DeepSeek-R1", provider="together", max_tokens=8096),
    tools=[calculate_cargo_travel_time],
    managed_agents=[web_agent],
    additional_authorized_imports=[
        "geopandas",
        "plotly",
        "shapely",
        "json",
        "pandas",
        "numpy",
    ],
    planning_interval=5,
    verbosity_level=2,
    final_answer_checks=[check_reasoning_and_plot],
    max_steps=15,
)
```

让我们检查这个团队是什么样子：

```python
manager_agent.visualize()
```

这将生成类似于下面的内容，帮助我们理解智能体和使用的工具之间的结构和关系：

```python
CodeAgent | deepseek-ai/DeepSeek-R1
├── ✅ Authorized imports: ['geopandas', 'plotly', 'shapely', 'json', 'pandas', 'numpy']
├── 🛠️ Tools:
│   ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
│   ┃ Name                        ┃ Description                           ┃ Arguments                             ┃
│   ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
│   │ calculate_cargo_travel_time │ Calculate the travel time for a cargo │ origin_coords (`array`): Tuple of     │
│   │                             │ plane between two points on Earth     │ (latitude, longitude) for the         │
│   │                             │ using great-circle distance.          │ starting point                        │
│   │                             │                                       │ destination_coords (`array`): Tuple   │
│   │                             │                                       │ of (latitude, longitude) for the      │
│   │                             │                                       │ destination                           │
│   │                             │                                       │ cruising_speed_kmh (`number`):        │
│   │                             │                                       │ Optional cruising speed in km/h       │
│   │                             │                                       │ (defaults to 750 km/h for typical     │
│   │                             │                                       │ cargo planes)                         │
│   │ final_answer                │ Provides a final answer to the given  │ answer (`any`): The final answer to   │
│   │                             │ problem.                              │ the problem                           │
│   └─────────────────────────────┴───────────────────────────────────────┴───────────────────────────────────────┘
└── 🤖 Managed agents:
    └── web_agent | CodeAgent | Qwen/Qwen2.5-Coder-32B-Instruct
        ├── ✅ Authorized imports: []
        ├── 📝 Description: Browses the web to find information
        └── 🛠️ Tools:
            ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
            ┃ Name                        ┃ Description                       ┃ Arguments                         ┃
            ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
            │ web_search                  │ Performs a google web search for  │ query (`string`): The search      │
            │                             │ your query then returns a string  │ query to perform.                 │
            │                             │ of the top search results.        │ filter_year (`integer`):          │
            │                             │                                   │ Optionally restrict results to a  │
            │                             │                                   │ certain year                      │
            │ visit_webpage               │ Visits a webpage at the given url │ url (`string`): The url of the    │
            │                             │ and reads its content as a        │ webpage to visit.                 │
            │                             │ markdown string. Use this to      │                                   │
            │                             │ browse webpages.                  │                                   │
            │ calculate_cargo_travel_time │ Calculate the travel time for a   │ origin_coords (`array`): Tuple of │
            │                             │ cargo plane between two points on │ (latitude, longitude) for the     │
            │                             │ Earth using great-circle          │ starting point                    │
            │                             │ distance.                         │ destination_coords (`array`):     │
            │                             │                                   │ Tuple of (latitude, longitude)    │
            │                             │                                   │ for the destination               │
            │                             │                                   │ cruising_speed_kmh (`number`):    │
            │                             │                                   │ Optional cruising speed in km/h   │
            │                             │                                   │ (defaults to 750 km/h for typical │
            │                             │                                   │ cargo planes)                     │
            │ final_answer                │ Provides a final answer to the    │ answer (`any`): The final answer  │
            │                             │ given problem.                    │ to the problem                    │
            └─────────────────────────────┴───────────────────────────────────┴───────────────────────────────────┘
```

```python
manager_agent.run("""
Find all Batman filming locations in the world, calculate the time to transfer via cargo plane to here (we're in Gotham, 40.7128° N, 74.0060° W).
Also give me some supercar factories with the same cargo plane transfer time. You need at least 6 points in total.
Represent this as spatial map of the world, with the locations represented as scatter points with a color that depends on the travel time, and save it to saved_map.png!

Here's an example of how to plot and return a map:
import plotly.express as px
df = px.data.carshare()
fig = px.scatter_map(df, lat="centroid_lat", lon="centroid_lon", text="name", color="peak_hour", size=100,
     color_continuous_scale=px.colors.sequential.Magma, size_max=15, zoom=1)
fig.show()
fig.write_image("saved_image.png")
final_answer(fig)

Never try to process strings using code: when you have a string to read, just print it and you'll see it.
""")
```

我不知道在你的运行中情况如何，但在我的运行中，管理智能体巧妙地将任务分配给网络智能体，首先是 `1. Search for Batman filming locations`，然后是 `2. Find supercar factories`，最后聚合列表并绘制地图。

让我们通过直接从智能体状态查看地图：

```python
manager_agent.python_executor.state["fig"]
```

这将输出地图：

![多智能体系统示例输出地图](https://huggingface.co/datasets/agents-course/course-images/resolve/main/en/unit2/smolagents/output_map.png)

## 资源

- [多智能体系统](https://huggingface.co/docs/smolagents/main/en/examples/multiagents) – 多智能体系统概述。
- [什么是智能体 RAG？](https://weaviate.io/blog/what-is-agentic-rag) – 智能体 RAG 介绍。
- [多智能体 RAG 系统 🤖🤝🤖 配方](https://huggingface.co/learn/cookbook/multiagent_rag_system) – 构建多智能体 RAG 系统的分步指南。

### 使用 smolagents 构建视觉智能体
https://huggingface.co/learn/agents-course/zh-CN/unit2/smolagents/vision_agents.md

# 使用 smolagents 构建视觉智能体

> [!WARNING]
> 本节示例需要接入强大的视觉语言模型（VLM）。我们使用 GPT-4o API 进行了测试。  
> 若需了解 smolagents 和 Hugging Face 支持的其他替代方案，请参考为什么选择smolagents章节。

赋予智能体视觉能力对于超越文本处理的任务至关重要。网页浏览、文档理解等现实场景都需要解析丰富的视觉内容。smolagents 内置支持视觉语言模型（VLMs），使智能体能够有效处理图像信息。

假设韦恩庄园的管家 Alfred 需要核验派对嘉宾身份。考虑到他可能无法识别所有来宾，我们可以构建基于 VLM 的智能体，通过视觉信息检索来辅助身份验证决策。以下是具体实现：

## 初始执行阶段提供图像

> [!TIP]
> 配套代码可在Google Colab 笔记本中查看。

该方法在智能体启动时通过 task_images 参数传入图像，智能体在执行过程中持续处理这些图像。

假设 Alfred 需要核验超级英雄身份，他已有历史派对嘉宾图像数据库。

当新访客到来时，智能体可通过图像比对进行准入决策。

当前场景中，Alfred 怀疑访客可能是小丑假扮的神奇女侠。我们需要构建身份验证系统：

```python
from PIL import Image
import requests
from io import BytesIO

image_urls = [
    "https://upload.wikimedia.org/wikipedia/commons/e/e8/The_Joker_at_Wax_Museum_Plus.jpg", # 小丑图像
    "https://upload.wikimedia.org/wikipedia/en/9/98/Joker_%28DC_Comics_character%29.jpg" # 小丑图像
]

images = []
for url in image_urls:
    headers = {
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36" 
    }
    response = requests.get(url,headers=headers)
    image = Image.open(BytesIO(response.content)).convert("RGB")
    images.append(image)
```

完成图像加载后，智能体将判断访客身份：究竟是超级英雄（Wonder Woman）还是反派角色（The Joker）。

```python
from smolagents import CodeAgent, OpenAIServerModel

model = OpenAIServerModel(model_id="gpt-4o")

# 实例化智能体
agent = CodeAgent(
    tools=[],
    model=model,
    max_steps=20,
    verbosity_level=2
)

response = agent.run(
    """
    Describe the costume and makeup that the comic character in these photos is wearing and return the description.
    Tell me if the guest is The Joker or Wonder Woman.
    """,
    images=images
)
```

以下是我的运行结果（实际输出可能因环境差异有所不同，正如前文所述）：

```python
    {
        'Costume and Makeup - First Image': (
            'Purple coat and a purple silk-like cravat or tie over a mustard-yellow shirt.',
            'White face paint with exaggerated features, dark eyebrows, blue eye makeup, red lips forming a wide smile.'
        ),
        'Costume and Makeup - Second Image': (
            'Dark suit with a flower on the lapel, holding a playing card.',
            'Pale skin, green hair, very red lips with an exaggerated grin.'
        ),
        'Character Identity': 'This character resembles known depictions of The Joker from comic book media.'
    }
```

在这种情况下，输出结果揭示了这个人正在冒充他人，因此我们可以阻止 The Joker 进入派对！

## 提供动态检索图像

> [!TIP]
> 您可以在 这个 Python 文件 中查看代码。

前面的方法具有很高的价值，并且有许多潜在的应用场景。然而，在客人不在数据库中的情况下，我们需要探索其他识别方式。一种可能的解决方案是从外部来源动态检索图像和信息，例如通过浏览网页获取详细信息。

在此方法中，图像是在执行过程中动态添加到智能体的记忆中的。我们知道，`smolagents` 中的智能体基于 `MultiStepAgent` 类，该类是 ReAct 框架的抽象。此类以结构化的周期运行，在不同阶段记录各种变量和知识：

1. **SystemPromptStep:** 存储系统提示。
2. **TaskStep:** 记录用户查询和提供的任何输入。
3. **ActionStep:** 捕获智能体操作和结果的日志。

这种结构化的方法使智能体能够动态地结合视觉信息，并对不断变化的任务做出适应性响应。以下是已经见过的图表，展示了动态工作流程过程以及不同步骤如何在智能体生命周期内集成。在浏览时，智能体可以截取屏幕截图并将其保存为 `ActionStep` 中的 `observation_images`。

![Dynamic image retrieval](https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/blog/smolagents-can-see/diagram_adding_vlms_smolagents.png)

现在我们理解了需求，让我们构建完整的示例。在这种情况下，Alfred 希望完全控制访客验证过程，因此浏览详情成为可行的解决方案。为了完成这个示例，我们需要为智能体提供一组新的工具。此外，我们将使用 Selenium 和 Helium，这些是浏览器自动化工具。这将使我们能够构建一个探索网络、搜索潜在访客详情并检索验证信息的智能体。让我们安装所需的工具：

```bash
pip install "smolagents[all]" helium selenium python-dotenv
```

我们需要一组专为浏览设计的智能体工具，例如“search_item_ctrl_f”、“go_back”和“close_popups”。这些工具允许智能体像浏览网页的人一样行事。

```python
@tool
def search_item_ctrl_f(text: str, nth_result: int = 1) -> str:
    """
    Searches for text on the current page via Ctrl + F and jumps to the nth occurrence.
    Args:
        text: The text to search for
        nth_result: Which occurrence to jump to (default: 1)
    """
    elements = driver.find_elements(By.XPATH, f"//*[contains(text(), '{text}')]")
    if nth_result > len(elements):
        raise Exception(f"Match n°{nth_result} not found (only {len(elements)} matches found)")
    result = f"Found {len(elements)} matches for '{text}'."
    elem = elements[nth_result - 1]
    driver.execute_script("arguments[0].scrollIntoView(true);", elem)
    result += f"Focused on element {nth_result} of {len(elements)}"
    return result

@tool
def go_back() -> None:
    """Goes back to previous page."""
    driver.back()

@tool
def close_popups() -> str:
    """
    Closes any visible modal or pop-up on the page. Use this to dismiss pop-up windows! This does not work on cookie consent banners.
    """
    webdriver.ActionChains(driver).send_keys(Keys.ESCAPE).perform()
```

我们还需要保存屏幕截图的功能，因为这是我们的 VLM 智能体完成任务时必不可少的一部分。此功能会捕获屏幕截图并将其保存在 `step_log.observations_images = [image.copy()]` 中，从而允许智能体在导航时动态存储和处理图像。

```python
def save_screenshot(step_log: ActionStep, agent: CodeAgent) -> None:
    sleep(1.0)  # 让 JavaScript 动画在截图之前完成
    driver = helium.get_driver()
    current_step = step_log.step_number
    if driver is not None:
        for step_logs in agent.logs:  # 从日志中删除先前的截图以进行精简处理
            if isinstance(step_log, ActionStep) and step_log.step_number 

这是最终输出：

```python
Final answer: Wonder Woman is typically depicted wearing a red and gold bustier, blue shorts or skirt with white stars, a golden tiara, silver bracelets, and a golden Lasso of Truth. She is Princess Diana of Themyscira, known as Diana Prince in the world of men.
```

通过这些步骤，我们成功地为派对创建了一个身份验证系统！ Alfred 现在拥有必要的工具，可以确保只有正确的宾客能够进入庄园。一切准备就绪，可以享受在韦恩庄园的美好时光！

## 进一步阅读

- [我们让 smolagents 有了视觉能力](https://huggingface.co/blog/smolagents-can-see) -  博客文章描述了视觉智能体的功能。
- [使用智能体进行网页浏览 🤖🌐](https://huggingface.co/docs/smolagents/examples/web_browser) - 使用视觉智能体进行网页浏览的示例。
- [网页浏览视觉智能体示例](https://github.com/huggingface/smolagents/blob/main/src/smolagents/vision_web_browser.py) - 使用视觉智能体进行网页浏览的示例。

### 构建智能驱动的 RAG 系统
https://huggingface.co/learn/agents-course/zh-CN/unit2/smolagents/retrieval_agents.md

# 构建智能驱动的 RAG 系统

> [!TIP]
> 您可以通过 此 Notebook 跟随代码实践，该文件支持在 Google Colab 中运行。

检索增强生成（Retrieval-Augmented Generation，RAG）系统结合了数据检索和生成模型的能力，以提供上下文感知的响应。例如，用户的查询会被传递给搜索引擎，检索结果与查询一起提供给模型，模型随后根据查询和检索到的信息生成响应。

智能驱动的 RAG（Retrieval-Augmented Generation）通过**将自主智能体与动态知识检索相结合**，扩展了传统 RAG 系统。

传统 RAG 系统使用 LLM 根据检索数据回答查询，而智能驱动的 RAG **实现了对检索和生成流程的智能控制**，从而提高了效率和准确性。

传统 RAG 系统面临关键限制，例如**依赖单次检索步骤**，以及过度关注与用户查询的直接语义相似性，这可能会忽略相关信息。

智能驱动的 RAG 通过允许智能体自主制定搜索查询、评估检索结果并进行多次检索步骤，以生成更定制化和全面的输出，从而解决这些问题。

## 基于 DuckDuckGo 的基础检索

让我们构建一个能够使用 DuckDuckGo 进行网页搜索的简单智能体。该智能体将检索信息并综合响应来回答查询。通过智能驱动的 RAG，Alfred 的智能体可以：

* 搜索最新的超级英雄派对趋势
* 优化结果以包含奢侈元素
* 将信息综合成完整方案

以下是 Alfred 的智能体实现此功能的代码示例：

```python
from smolagents import CodeAgent, DuckDuckGoSearchTool, InferenceClientModel

# 初始化搜索工具
search_tool = DuckDuckGoSearchTool()

# 初始化模型
model = InferenceClientModel()

agent = CodeAgent(
    model=model,
    tools=[search_tool]
)

# 使用示例
response = agent.run(
    "Search for luxury superhero-themed party ideas, including decorations, entertainment, and catering."
)
print(response)
```

智能体遵循以下流程：

1. **请求分析：** Alfred 的智能体识别查询的关键要素——重点关注装饰、娱乐和餐饮的豪华超级英雄主题派对规划
2. **执行检索：** 智能体利用 DuckDuckGo 搜索最新相关信息，确保符合 Alfred 对奢侈活动的精细要求
3. **信息综合：** 收集结果后，智能体将其处理为覆盖派对所有方面的可执行方案
4. **未来参考存储：** 智能体存储检索信息以便后续活动规划时快速访问，优化后续任务效率

## 自定义知识库工具

对于专业任务，自定义知识库非常宝贵。让我们创建可以查询技术文档或专业知识的向量数据库工具。通过语义搜索，智能体可以找到与 Alfred 需求最相关的信息。

向量数据库（vector database）是通过专业 ML 模型实现丰富文档表示的集合，能够快速搜索和检索文档。

该方法将预定义知识与语义搜索相结合，为活动规划提供上下文感知解决方案。通过专业知识访问，Alfred 可以完善派对的每个细节。

在此示例中，我们将创建从自定义知识库检索派对策划创意的工具。使用 BM25 检索器搜索知识库并返回最佳结果，同时使用 `RecursiveCharacterTextSplitter` 将文档分割为更小的块以提高搜索效率：

```python
from langchain.docstore.document import Document
from langchain.text_splitter import RecursiveCharacterTextSplitter
from smolagents import Tool
from langchain_community.retrievers import BM25Retriever
from smolagents import CodeAgent, InferenceClientModel

class PartyPlanningRetrieverTool(Tool):
    name = "party_planning_retriever"
    description = "Uses semantic search to retrieve relevant party planning ideas for Alfred’s superhero-themed party at Wayne Manor."
    inputs = {
        "query": {
            "type": "string",
            "description": "The query to perform. This should be a query related to party planning or superhero themes.",
        }
    }
    output_type = "string"

    def __init__(self, docs, **kwargs):
        super().__init__(**kwargs)
        self.retriever = BM25Retriever.from_documents(
            docs, k=5  # 检索前 5 个文档
        )

    def forward(self, query: str) -> str:
        assert isinstance(query, str), "Your search query must be a string"

        docs = self.retriever.invoke(
            query,
        )
        return "\nRetrieved ideas:\n" + "".join(
            [
                f"\n\n===== Idea {str(i)} =====\n" + doc.page_content
                for i, doc in enumerate(docs)
            ]
        )

# 模拟派对策划知识库
party_ideas = [
    {"text": "A superhero-themed masquerade ball with luxury decor, including gold accents and velvet curtains.", "source": "Party Ideas 1"},
    {"text": "Hire a professional DJ who can play themed music for superheroes like Batman and Wonder Woman.", "source": "Entertainment Ideas"},
    {"text": "For catering, serve dishes named after superheroes, like 'The Hulk's Green Smoothie' and 'Iron Man's Power Steak.'", "source": "Catering Ideas"},
    {"text": "Decorate with iconic superhero logos and projections of Gotham and other superhero cities around the venue.", "source": "Decoration Ideas"},
    {"text": "Interactive experiences with VR where guests can engage in superhero simulations or compete in themed games.", "source": "Entertainment Ideas"}
]

source_docs = [
    Document(page_content=doc["text"], metadata={"source": doc["source"]})
    for doc in party_ideas
]

# 分割文档以提高搜索效率
    text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=500,
    chunk_overlap=50,
    add_start_index=True,
    strip_whitespace=True,
    separators=["\n\n", "\n", ".", " ", ""],
)
docs_processed = text_splitter.split_documents(source_docs)

# 创建检索工具
party_planning_retriever = PartyPlanningRetrieverTool(docs_processed)

# 初始化智能体
agent = CodeAgent(tools=[party_planning_retriever], model=InferenceClientModel())

# 使用示例
response = agent.run(
    "Find ideas for a luxury superhero-themed party, including entertainment, catering, and decoration options."
)

print(response)
```

增强后的智能体能够：
1. 首先检查文档中的相关信息
2. 结合知识库的洞察
3. 在内存中维护对话上下文

## 增强的检索能力

构建智能驱动的 RAG 系统时，智能体可以采用以下高级策略：

1. **查询重构：** 智能体可以优化原始查询，生成更匹配目标文档的搜索词
2. **多步检索：** 智能体可以进行多次搜索，利用初步结果优化后续查询
3. **多源整合：** 结合来自网页搜索和本地文档等多个来源的信息
4. **结果验证：** 在将检索内容纳入响应前分析其相关性和准确性

有效的智能驱动 RAG 系统需要仔细考虑几个关键方面。智能体**应根据查询类型和上下文选择可用工具**，记忆系统帮助维护对话历史避免重复检索，后备策略确保在主要检索方法失败时系统仍能提供价值，验证步骤则帮助确保检索信息的准确性和相关性。

## 资源

- [Agentic RAG: 使用查询重构和自查询加速您的 RAG 系统！🚀](https://huggingface.co/learn/cookbook/zh-CN/agent_rag) - 使用 smolagents 开发智能驱动 RAG 系统的实践指南

### 小测验（不计分）[[quiz2]]
https://huggingface.co/learn/agents-course/zh-CN/unit2/smolagents/quiz2.md

# 小测验（不计分）[[quiz2]]

现在该测试您对*代码智能体*、*工具调用智能体*和*工具*章节的理解了。本测验为可选且不计分。

---

### Q1: 使用 `@tool` 装饰器创建工具与创建 `Tool` 的子类之间的主要区别是什么？

以下哪个陈述最能描述这两种定义工具方法的区别？

@tool 装饰器是检索类工具的强制要求，而 Tool 的子类仅用于文本生成任务",
    explain: "两种方法都适用于任何类型的工具，包括检索类和文本生成类工具。",
  },
  {
    text: "推荐使用 @tool 装饰器创建简单的基于函数的工具，而 Tool 的子类能为复杂功能或自定义元数据提供更大灵活性",
    explain: "正确。装饰器方法更简单，但子类化允许更定制化的行为。",
    correct: true
  },
  {
    text: "@tool 只能用于多智能体系统，而创建 Tool 的子类适用于单智能体场景",
    explain: "所有智能体（单或多）都可以使用这两种方法定义工具，没有此类限制。",
  },
  {
    text: "用 @tool 装饰函数可以替代文档字符串，而子类工具必须不包含文档字符串",
    explain: "两种方法都需要清晰的文档字符串。装饰器不会替代它们，子类仍然可以包含文档字符串。",
  }
]}
/>

---

### Q2: CodeAgent 如何使用 ReAct（推理+行动）方法处理多步骤任务？

哪个陈述正确描述了 CodeAgent 执行系列步骤来解决任务的方式？

---

### Q3: 在 Hugging Face Hub 上共享工具的主要优势是什么？

选择开发者可能上传和共享自定义工具的最佳原因。

---

### Q4: ToolCallingAgent 在执行操作方面与 CodeAgent 有何不同？

选择准确描述 ToolCallingAgent 工作方式的选项。

---

### Q5: smolagents 默认工具箱包含哪些内容？为什么要使用它？

哪个陈述最能体现 smolagents 默认工具箱的目的和内容？

---

恭喜完成测验！🎉 如果有任何问题让您感到困难，请重新访问*代码智能体*、*工具调用智能体*或*工具*章节以加强理解。如果您表现出色，那么您已踏上构建强大 smolagents 应用的道路！

### 构建使用代码的智能体
https://huggingface.co/learn/agents-course/zh-CN/unit2/smolagents/code_agents.md

# 构建使用代码的智能体

代码智能体（Code agents）是 `smolagents` 中的默认智能体类型。它们生成 Python 工具调用来执行操作，实现高效、表达力强且准确的操作表示。

它们的简化方法减少了所需操作的数量，简化了复杂操作，并实现了对现有代码函数的重用。`smolagents` 提供了一个轻量级框架，用约 1,000 行代码实现构建代码智能体（code agents）。

![代码与 JSON 操作对比](https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/transformers/code_vs_json_actions.png)
图片来自论文 [Executable Code Actions Elicit Better LLM Agents](https://huggingface.co/papers/2402.01030)

> [!TIP]
> 如果你想了解更多关于为什么代码智能体如此有效的信息，请查看 smolagents 文档中的这个指南。

## 为什么选择代码智能体？

在多步骤智能体过程中，大语言模型（LLM）编写并执行操作，通常涉及外部工具调用。传统方法使用 JSON 格式来指定工具名称和参数作为字符串，**系统必须解析这些内容以确定要执行哪个工具**。

然而，研究表明，**工具调用型大语言模型直接使用代码工作更有效**。这是 `smolagents` 的核心原则，如上图所示，来自论文 [Executable Code Actions Elicit Better LLM Agents](https://huggingface.co/papers/2402.01030)。

用代码而非 JSON 编写操作提供了几个关键优势：

* **可组合性（Composability）**：轻松组合和重用操作
* **对象管理（Object Management）**：直接处理复杂结构，如图像
* **通用性（Generality）**：表达任何计算上可能的任务
* **适合大语言模型**：高质量代码已存在于大语言模型的训练数据中

## 代码智能体如何工作？

![来自 https://huggingface.co/docs/smolagents/conceptual_guides/react](https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/smolagents/codeagent_docs.png)

上图说明了 `CodeAgent.run()` 如何操作，遵循我们在第 1 单元中提到的 ReAct 框架。`smolagents` 中智能体的主要抽象是 `MultiStepAgent`，它作为核心构建块。如我们将在下面的示例中看到的，`CodeAgent` 是一种特殊的 `MultiStepAgent`。

`CodeAgent` 通过一系列步骤执行操作，将现有变量和知识整合到智能体的上下文中，这些内容保存在执行日志中：

1. 系统提示存储在 `SystemPromptStep` 中，用户查询记录在 `TaskStep` 中。

2. 然后，执行以下循环：

    2.1 方法 `agent.write_memory_to_messages()` 将智能体的日志写入大语言模型可读的[聊天消息](https://huggingface.co/docs/transformers/en/chat_templating)列表中。
    
    2.2 这些消息发送给 `Model`，生成补全（completion）。
    
    2.3 解析补全内容以提取操作，在我们的例子中，这应该是代码片段，因为我们使用的是 `CodeAgent`。
    
    2.4 执行该操作。
    
    2.5 将结果记录到内存中的 `ActionStep` 中。

在每个步骤结束时，如果智能体包含任何函数调用（在 `agent.step_callback` 中），它们将被执行。

## 让我们看一些例子

> [!TIP]
> 你可以在 这个笔记本 中跟随代码，可以使用 Google Colab 运行。

阿尔弗雷德（Alfred）正在韦恩家族大宅计划一场派对，需要你的帮助确保一切顺利进行。为了帮助他，我们将应用我们所学到的关于多步骤 `CodeAgent` 如何运作的知识。

如果你还没有安装 `smolagents`，可以运行以下命令进行安装：

```bash
pip install smolagents -U
```

让我们也登录到 Hugging Face Hub，以便访问无服务器推理 API（Serverless Inference API）。

```python
from huggingface_hub import login

login()
```

### 使用 `smolagents` 为派对选择播放列表

音乐是成功派对的重要组成部分！阿尔弗雷德需要一些帮助来选择播放列表。幸运的是，`smolagents` 能够帮助我们！我们可以构建一个能够使用 DuckDuckGo 搜索网络的智能体。要让智能体访问此工具，我们在创建智能体时将其包含在工具列表中。

对于模型，我们将依赖 `InferenceClientModel`，它提供对 Hugging Face 的[无服务器推理 API](https://huggingface.co/docs/api-inference/index)的访问。默认模型是 `"Qwen/Qwen2.5-Coder-32B-Instruct"`，它性能良好并可用于快速推理，但你可以从 Hub 中选择任何兼容的模型。

运行智能体相当简单：

```python
from smolagents import CodeAgent, DuckDuckGoSearchTool, InferenceClientModel

agent = CodeAgent(tools=[DuckDuckGoSearchTool()], model=InferenceClientModel())

agent.run("Search for the best music recommendations for a party at the Wayne's mansion.")
```

当你运行这个例子时，输出将**显示正在执行的工作流步骤的跟踪**。它还会打印相应的 Python 代码，并附带消息：

```python
 ─ Executing parsed code: ──────────────────────────────────────────────────────────────────────────────────────── 
  results = web_search(query="best music for a Batman party")                                                      
  print(results)                                                                                                   
 ───────────────────────────────────────────────────────────────────────────────────────────────────────────────── 
```

几个步骤后，你将看到阿尔弗雷德可以用于派对的生成播放列表！🎵

### 使用自定义工具准备菜单

现在我们已经选择了播放列表，我们需要为客人组织菜单。同样，阿尔弗雷德可以利用 `smolagents` 来做到这一点。在这里，我们使用 `@tool` 装饰器定义一个作为工具的自定义函数。我们稍后将更详细地介绍工具创建，所以现在我们可以简单地运行代码。

如下例所示，我们将使用 `@tool` 装饰器创建一个工具，并将其包含在 `tools` 列表中。

```python
from smolagents import CodeAgent, tool, InferenceClientModel

# 根据场合建议菜单的工具
@tool
def suggest_menu(occasion: str) -> str:
    """
    Suggests a menu based on the occasion.
    Args:
        occasion: The type of occasion for the party.
    """
    if occasion == "casual":
        return "Pizza, snacks, and drinks."
    elif occasion == "formal":
        return "3-course dinner with wine and dessert."
    elif occasion == "superhero":
        return "Buffet with high-energy and healthy food."
    else:
        return "Custom menu for the butler."

# 管家阿尔弗雷德，为派对准备菜单
agent = CodeAgent(tools=[suggest_menu], model=InferenceClientModel())

# 为派对准备正式菜单
agent.run("Prepare a formal menu for the party.")
```

智能体将运行几个步骤，直到找到答案。

菜单准备好了！🥗

### 在智能体内部使用 Python 导入

我们已经准备好了播放列表和菜单，但我们需要检查另一个关键细节：准备时间！

阿尔弗雷德需要计算如果他现在开始准备，什么时候一切都会准备就绪，以防他们需要其他超级英雄的帮助。

`smolagents` 专门用于编写和执行 Python 代码片段的智能体，处于安全方面的考量，其提供沙盒执行环境。

**代码执行有严格的安全措施** - 默认情况下，预定义安全列表之外的导入被阻止。但是，您可以通过将它们作为字符串传递到 `additional_authorized_imports` 中来授权额外的导入。
有关安全代码执行的更多详情，请参阅官方[指南](https://huggingface.co/docs/smolagents/tutorials/secure_code_execution)。

创建智能体时，我们将使用 `additional_authorized_imports` 来允许导入 `datetime` 模块。

```python
from smolagents import CodeAgent, InferenceClientModel
import numpy as np
import time
import datetime

agent = CodeAgent(tools=[], model=InferenceClientModel(), additional_authorized_imports=['datetime'])

agent.run(
    """
    Alfred needs to prepare for the party. Here are the tasks:
    1. Prepare the drinks - 30 minutes
    2. Decorate the mansion - 60 minutes
    3. Set up the menu - 45 minutes
    3. Prepare the music and playlist - 45 minutes

    If we start right now, at what time will the party be ready?
    """
)
```

这些例子只是代码智能体能做的事情的开始，我们已经开始看到它们对准备派对的实用性。
你可以在 [smolagents 文档](https://huggingface.co/docs/smolagents) 中了解更多关于如何构建代码智能体的信息。

总之，`smolagents` 专注于编写和执行 Python 代码片段的智能体，提供安全的沙盒执行环境。它支持本地和基于 API 的语言模型，使其适应各种开发环境。

### 将我们的自定义派对准备智能体分享到 Hub

**将我们自己的阿尔弗雷德智能体与社区分享**难道不会很棒吗？通过这样做，任何人都可以轻松地从 Hub 下载并直接使用这个智能体，将哥谭市的终极派对策划者带到他们的指尖！让我们实现这个想法！🎉

`smolagents` 库使这成为可能，允许你与社区分享完整的智能体，并下载其他人的智能体立即使用。这很简单，如下所示：

```python
# 更改为你的用户名和仓库名
agent.push_to_hub('sergiopaniego/AlfredAgent')
```

要再次下载智能体，请使用以下代码：

```python
# 更改为你的用户名和仓库名
alfred_agent = agent.from_hub('sergiopaniego/AlfredAgent')

alfred_agent.run("Give me the best playlist for a party at Wayne's mansion. The party idea is a 'villain masquerade' theme")  
```

令人兴奋的是，共享的智能体直接作为 Hugging Face Spaces 提供，允许你实时与它们交互。你可以在[这里](https://huggingface.co/spaces/davidberenstein1957/smolagents-and-tools)探索其他智能体。

例如，_AlfredAgent_ 在[这里](https://huggingface.co/spaces/sergiopaniego/AlfredAgent)可用。你可以直接在下面尝试：

你可能想知道——阿尔弗雷德如何使用 `smolagents` 构建这样一个智能体？通过整合几个工具，他可以生成一个如下的智能体。现在不用担心这些工具，因为我们将在本单元后面有专门的部分详细探讨：

```python
from smolagents import CodeAgent, DuckDuckGoSearchTool, FinalAnswerTool, InferenceClientModel, Tool, tool, VisitWebpageTool

@tool
def suggest_menu(occasion: str) -> str:
    """
    Suggests a menu based on the occasion.
    Args:
        occasion: The type of occasion for the party.
    """
    if occasion == "casual":
        return "Pizza, snacks, and drinks."
    elif occasion == "formal":
        return "3-course dinner with wine and dessert."
    elif occasion == "superhero":
        return "Buffet with high-energy and healthy food."
    else:
        return "Custom menu for the butler."

@tool
def catering_service_tool(query: str) -> str:
    """
    This tool returns the highest-rated catering service in Gotham City.
    
    Args:
        query: A search term for finding catering services.
    """
    # 餐饮服务及其评分的示例列表    
    services = {
        "Gotham Catering Co.": 4.9,
        "Wayne Manor Catering": 4.8,
        "Gotham City Events": 4.7,
    }
    
    # 找到评分最高的餐饮服务（模拟搜索查询过滤）
    best_service = max(services, key=services.get)
    
    return best_service

class SuperheroPartyThemeTool(Tool):
    name = "superhero_party_theme_generator"
    description = """
    This tool suggests creative superhero-themed party ideas based on a category.
    It returns a unique party theme idea."""
    
    inputs = {
        "category": {
            "type": "string",
            "description": "The type of superhero party (e.g., 'classic heroes', 'villain masquerade', 'futuristic Gotham').",
        }
    }
    
    output_type = "string"

    def forward(self, category: str):
        themes = {
            "classic heroes": "Justice League Gala: Guests come dressed as their favorite DC heroes with themed cocktails like 'The Kryptonite Punch'.",
            "villain masquerade": "Gotham Rogues' Ball: A mysterious masquerade where guests dress as classic Batman villains.",
            "futuristic Gotham": "Neo-Gotham Night: A cyberpunk-style party inspired by Batman Beyond, with neon decorations and futuristic gadgets."
        }
        
        return themes.get(category.lower(), "Themed party idea not found. Try 'classic heroes', 'villain masquerade', or 'futuristic Gotham'.")

# 管家阿尔弗雷德，为派对准备菜单
agent = CodeAgent(
    tools=[
        DuckDuckGoSearchTool(), 
        VisitWebpageTool(),
        suggest_menu,
        catering_service_tool,
        SuperheroPartyThemeTool()
    ], 
    model=InferenceClientModel(),
    max_steps=10,
    verbosity_level=2
)

agent.run("Give me best playlist for a party at the Wayne's mansion. The party idea is a 'villain masquerade' theme")
```

如你所见，我们创建了一个具有多种工具的 `CodeAgent`，这些工具增强了智能体的功能，将其变成了准备好与社区分享的终极派对策划者！🎉

现在，轮到你了：使用我们刚刚学到的知识，构建你自己的智能体并与社区分享！🕵️‍♂️💡

> [!TIP]
> 如果你想分享你的智能体项目，请创建一个 space 并在 Hugging Face Hub 上标记 [agents-course](https://huggingface.co/agents-course)。我们很想看看你创造了什么！

### 使用 OpenTelemetry 和 Langfuse 检查我们的派对准备智能体 📡

随着阿尔弗雷德对派对准备智能体的微调，他对调试其运行越来越疲惫。智能体本质上是不可预测的，难以检查。但由于他的目标是构建终极派对准备智能体并将其部署到生产环境中，他需要强大的可追踪性，用于未来的监控和分析。

再一次，`smolagents` 来救援！它采用 [OpenTelemetry](https://opentelemetry.io/) 标准来检测智能体运行，允许无缝检查和日志记录。在 [Langfuse](https://langfuse.com/) 和 `SmolagentsInstrumentor` 的帮助下，阿尔弗雷德可以轻松跟踪和分析他的智能体行为。

设置非常简单！

首先，我们需要安装必要的依赖项：

```bash
pip install opentelemetry-sdk opentelemetry-exporter-otlp openinference-instrumentation-smolagents langfuse
```

接下来，阿尔弗雷德已经在 Langfuse 上创建了一个账户，并准备好了他的 API 密钥。如果你还没有这样做，你可以在[这里](https://cloud.langfuse.com/)注册 Langfuse Cloud 或探索[替代方案](https://huggingface.co/docs/smolagents/tutorials/inspect_runs)。

一旦你有了 API 密钥，它们需要正确配置如下：

```python
import os

# Get keys for your project from the project settings page: https://cloud.langfuse.com
os.environ["LANGFUSE_PUBLIC_KEY"] = "pk-lf-..." 
os.environ["LANGFUSE_SECRET_KEY"] = "sk-lf-..." 
os.environ["LANGFUSE_HOST"] = "https://cloud.langfuse.com" # 🇪🇺 EU region
# os.environ["LANGFUSE_HOST"] = "https://us.cloud.langfuse.com" # 🇺🇸 US region
```

With the environment variables set, we can now initialize the Langfuse client. get_client() initializes the Langfuse client using the credentials provided in the environment variables.

```python
from langfuse import get_client
 
langfuse = get_client()
 
# Verify connection
if langfuse.auth_check():
    print("Langfuse client is authenticated and ready!")
else:
    print("Authentication failed. Please check your credentials and host.")
```

最后，阿尔弗雷德准备初始化 `SmolagentsInstrumentor` 并开始跟踪他的智能体性能。

```python
from openinference.instrumentation.smolagents import SmolagentsInstrumentor

SmolagentsInstrumentor().instrument()
```

阿尔弗雷德现在已连接 🔌！来自 `smolagents` 的运行正在 Langfuse 中记录，让他完全可见智能体的行为。有了这个设置，他准备重新访问之前的运行并进一步改进他的派对准备智能体。

```python
from smolagents import CodeAgent, InferenceClientModel

agent = CodeAgent(tools=[], model=InferenceClientModel())
alfred_agent = agent.from_hub('sergiopaniego/AlfredAgent', trust_remote_code=True)
alfred_agent.run("Give me the best playlist for a party at Wayne's mansion. The party idea is a 'villain masquerade' theme")  
```

阿尔弗雷德现在可以在[这里](https://cloud.langfuse.com/project/cm7bq0abj025rad078ak3luwi/traces/995fc019255528e4f48cf6770b0ce27b?timestamp=2025-02-19T10%3A28%3A36.929Z)访问这些日志来审查和分析它们。

同时，[建议的播放列表](https://open.spotify.com/playlist/0gZMMHjuxMrrybQ7wTMTpw)为派对准备设置了完美的氛围。很酷，对吧？🎶

---

现在我们已经创建了我们的第一个代码智能体，让我们**学习如何创建工具调用智能体（Tool Calling Agents）**，这是 `smolagents` 中可用的第二种智能体类型。

## 资源

- [smolagents 博客](https://huggingface.co/blog/smolagents) - smolagents 和代码交互介绍
- [smolagents：构建好的智能体](https://huggingface.co/docs/smolagents/tutorials/building_good_agents) - 可靠智能体的最佳实践
- [构建有效的智能体 - Anthropic](https://www.anthropic.com/research/building-effective-agents) - 智能体设计原则
- [使用 OpenTelemetry 共享运行](https://huggingface.co/docs/smolagents/tutorials/inspect_runs) - 关于如何为跟踪你的智能体设置 OpenTelemetry 的详细信息

### 结论
https://huggingface.co/learn/agents-course/zh-CN/unit2/smolagents/conclusion.md

# 结论

恭喜你完成了第二单元的 `smolagents` 模块 🥳

你刚刚掌握了 `smolagents` 的基础知识，并且构建了自己的智能体！现在你已经具备了 `smolagents` 的技能，你可以开始创建能够解决你感兴趣任务的智能体。

在下一个模块中，你将学习**如何使用 LlamaIndex 构建智能体（Agents）**。

最后，我们非常想**听听你对这门课程的看法以及我们如何改进它**。如果你有任何反馈，请👉 [填写这个表格](https://docs.google.com/forms/d/e/1FAIpQLSe9VaONn0eglax0uTwi29rIn4tM7H2sYmmybmG5jJNlE5v0xA/viewform?usp=dialog)

### 继续学习，保持优秀 🤗

### 工具
https://huggingface.co/learn/agents-course/zh-CN/unit2/smolagents/tools.md

# 工具

正如我们在[第一单元](https://huggingface.co/learn/agents-course/unit1/tools)所探讨的，智能体通过工具执行各类操作。在`smolagents`框架中，工具被视为 **LLM 可以在智能体系统中调用的函数**。

要使LLM能够调用工具，需要为其提供包含以下关键要素的**接口描述**：

- **名称**：工具的标识名称
- **工具描述**：工具的功能说明  
- **输入类型及描述**：工具接受的参数说明
- **输出类型**：工具的返回结果类型

以韦恩庄园筹备派对为例，Alfred 需要多种工具来收集信息——从搜索餐饮服务到寻找派对主题创意。以下是一个简单搜索工具的接口示例：

- **名称:** `web_search`
- **工具描述:** 根据特定查询进行网络搜索
- **输入:** `query` (字符串) - 需要查找的搜索关键词
- **输出:** 包含搜索结果的字符串

通过使用这些工具，Alfred 能够做出明智决策并收集派对筹备所需的所有信息。

下方动画展示了工具调用的管理流程：

![来自 https://huggingface.co/docs/smolagents/conceptual_guides/react 的智能体流程](https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/transformers/Agent_ManimCE.gif)

## 工具创建方法

在`smolagents`中，可以通过两种方式定义工具：  
1. **使用`@tool`装饰器**创建基于函数的简单工具
2. **创建`Tool`的子类**实现复杂功能    

### `@tool`装饰器

`@tool`装饰器是**定义简单工具的推荐方式**。在底层，smolagents 会从 Python 函数解析基本信息。因此，清晰的函数命名和规范的文档字符串（docstring）能让 LLM 更易理解工具用途。

使用此方法时，我们需要定义包含以下要素的函数：  

- **明确描述性的函数名称**：帮助LLM理解其用途  
- **输入输出的类型提示**：确保正确使用  
- **详细描述**：包含明确描述各参数的`Args:`部分，这些描述为 LLM 提供关键上下文信息

#### 创建餐饮评分查询工具

> [!TIP]
> 您可以通过 此 Notebook 跟随代码实践，该文件支持在 Google Colab 中运行。

假设 Alfred 已确定派对菜单，但需要为大量宾客准备食物。为此，他希望雇佣餐饮服务并需要找到当地评分最高的选择。

以下是通过`@tool`装饰器实现该功能的示例：

```python
from smolagents import CodeAgent, InferenceClientModel, tool

# 假设我们有一个获取最高评分餐饮服务的函数
@tool
def catering_service_tool(query: str) -> str:
    """
    This tool returns the highest-rated catering service in Gotham City.
    
    Args:
        query: A search term for finding catering services.
    """
   # 示例餐饮服务及评分列表
    services = {
        "Gotham Catering Co.": 4.9,
        "Wayne Manor Catering": 4.8,
        "Gotham City Events": 4.7,
    }
    
    # 查找评分最高的餐饮服务（模拟搜索查询过滤）
    best_service = max(services, key=services.get)
    
    return best_service

agent = CodeAgent(tools=[catering_service_tool], model=InferenceClientModel())

# 运行智能体寻找最佳餐饮服务
result = agent.run(
    "Can you give me the name of the highest-rated catering service in Gotham City?"
)

print(result)   # Output: Gotham Catering Co.
```

### 通过Python类定义工具

此方法需要创建[`Tool`](https://huggingface.co/docs/smolagents/v1.8.1/en/reference/tools#smolagents.Tool)的子类。对于复杂工具，我们可以通过类封装函数及其元数据来帮助 LLM 理解使用方式。在类中需要定义：

- `name`: 工具名称  
- `description`: 用于构建智能体系统提示的描述  
- `inputs`: 包含`type`和`description`的字典，帮助Python解释器处理输入  
- `output_type`: 指定期望的输出类型  
- `forward`: 包含执行逻辑的方法  

以下是通过`Tool`类构建工具并与`CodeAgent`集成的示例：

#### 创建超级英雄主题派对创意生成工具

Alfred 计划在庄园举办**超级英雄主题派对**，但需要独特创意让活动与众不同。作为完美管家，他希望用新颖主题给宾客带来惊喜。

为此，我们可以创建根据类别生成派对创意的工具，帮助 Alfred 找到最惊艳的主题方案：

```python
from smolagents import Tool, CodeAgent, InferenceClientModel

class SuperheroPartyThemeTool(Tool):
    name = "superhero_party_theme_generator"
    description = """
    This tool suggests creative superhero-themed party ideas based on a category.
    It returns a unique party theme idea."""
    
    inputs = {
        "category": {
            "type": "string",
            "description": "The type of superhero party (e.g., 'classic heroes', 'villain masquerade', 'futuristic Gotham').",
        }
    }
    
    output_type = "string"

    def forward(self, category: str):
        themes = {
            "classic heroes": "Justice League Gala: Guests come dressed as their favorite DC heroes with themed cocktails like 'The Kryptonite Punch'.",
            "villain masquerade": "Gotham Rogues' Ball: A mysterious masquerade where guests dress as classic Batman villains.",
            "futuristic Gotham": "Neo-Gotham Night: A cyberpunk-style party inspired by Batman Beyond, with neon decorations and futuristic gadgets."
        }
        
        return themes.get(category.lower(), "Themed party idea not found. Try 'classic heroes', 'villain masquerade', or 'futuristic Gotham'.")

# 实例化工具
party_theme_tool = SuperheroPartyThemeTool()
agent = CodeAgent(tools=[party_theme_tool], model=InferenceClientModel())

# 运行智能体生成派对主题
result = agent.run(
    "What would be a good superhero party idea for a 'villain masquerade' theme?"
)

print(result)  # Output: "Gotham Rogues' Ball: A mysterious masquerade where guests dress as classic Batman villains."
```

借助此工具，Alfred 将成为终极超级管家，为宾客呈现难忘的超级英雄主题派对！🦸♂️🦸♀️

## 默认工具箱  

`smolagents` 自带一组预构建工具，可直接注入到您的智能体中。[默认工具箱](https://huggingface.co/docs/smolagents/guided_tour?build-a-tool=Decorate+a+function+with+%40tool#default-toolbox) 包含:  

- **PythonInterpreterTool**  
- **FinalAnswerTool**  
- **UserInputTool**  
- **DuckDuckGoSearchTool**  
- **GoogleSearchTool**  
- **VisitWebpageTool**  

Alfred 可以使用多种工具来确保韦恩庄园的完美派对:

- 首先，他可以使用 `DuckDuckGoSearchTool` 搜索创意超级英雄主题派对灵感  

- 对于餐饮，他依赖 `GoogleSearchTool` 查找哥谭市评分最高的服务  

- 要管理座位安排，Alfred 可以通过 `PythonInterpreterTool` 运行计算  

- 收集完所有信息后，他使用 `FinalAnswerTool` 整合计划  

通过这些工具，Alfred 确保派对既出众又顺利。🦇💡  

## 共享与导入工具  

**smolagents** 最强大的功能之一是能够将自定义工具共享到 Hub 并无缝集成社区创建的工具。这包括与 **HF Spaces** 和 **LangChain 工具**的连接，显著增强了 Alfred 策划难忘韦恩庄园派对的能力。🎭  

通过这些集成，Alfred 可以利用高级活动策划工具——无论是调整灯光营造完美氛围、为派对策划理想歌单，还是与哥谭市最优秀的餐饮服务商协调。  

以下是展示这些功能如何提升派对体验的示例:  

### 向 Hub 共享工具  

与社区分享自定义工具非常简单！只需使用 `push_to_hub()` 方法将其上传到您的 Hugging Face 账户。  

例如，Alfred 可以分享他的 `party_theme_tool` 以帮助其他人找到哥谭市最好的餐饮服务。具体操作如下:  

```python
party_theme_tool.push_to_hub("{your_username}/party_theme_tool", token="")
```

### 从 Hub 导入工具

您可以使用 `load_tool()` 函数轻松导入其他用户创建的工具。例如，Alfred 可能希望使用 AI 生成派对的宣传图片。无需从头构建工具，他可以直接使用社区预定义的方案:

```python
from smolagents import load_tool, CodeAgent, InferenceClientModel

image_generation_tool = load_tool(
    "m-ric/text-to-image",
    trust_remote_code=True
)

agent = CodeAgent(
    tools=[image_generation_tool],
    model=InferenceClientModel()
)

agent.run("Generate an image of a luxurious superhero-themed party at Wayne Manor with made-up superheros.")
```

### 将 Hugging Face Space 导入为工具

您可以使用 `Tool.from_space()` 将 HF Space 作为工具导入。这开启了与社区数千个 Space 集成的可能性，从图像生成到数据分析均可实现。

工具将通过 `gradio_client` 连接 Space 的后端，请确保已通过 `pip` 安装该依赖（如果尚未安装）。

对于本次派对，Alfred 可以使用现有的 HF Space 生成公告所需的 AI 图像（替代之前提到的预建工具）。让我们开始构建：

```python
from smolagents import CodeAgent, InferenceClientModel, Tool

image_generation_tool = Tool.from_space(
    "black-forest-labs/FLUX.1-schnell",
    name="image_generator",
    description="Generate an image from a prompt"
)

model = InferenceClientModel("Qwen/Qwen2.5-Coder-32B-Instruct")

agent = CodeAgent(tools=[image_generation_tool], model=model)

agent.run(
    "Improve this prompt, then generate an image of it.", 
    additional_args={'user_prompt': 'A grand superhero-themed party at Wayne Manor, with Alfred overseeing a luxurious gala'}
)
```

### 导入 LangChain 工具

我们将在后续章节讨论 `LangChain` 框架。目前需要注意的是，您可以在 smolagents 工作流中复用 LangChain 工具！

您可以使用 `Tool.from_langchain()` 方法轻松加载 LangChain 工具。

追求完美的 Alfred 正在筹备一场盛大的超级英雄之夜活动（趁韦恩一家外出时），为确保每个细节都超出预期，他借助 LangChain 工具来寻找顶级的娱乐创意。

具体实现如下：

```python
from langchain.agents import load_tools
from smolagents import CodeAgent, InferenceClientModel, Tool

search_tool = Tool.from_langchain(load_tools(["serpapi"])[0])

agent = CodeAgent(tools=[search_tool], model=model)

agent.run("Search for luxury entertainment ideas for a superhero-themed event, such as live performances and interactive experiences.")
```

通过此设置，Alfred 能快速发现高端娱乐选项，确保哥谭的精英宾客获得难忘体验。该工具帮助他策划韦恩庄园的完美超级英雄主题活动！🎉

## 资源

- [工具教程](https://huggingface.co/docs/smolagents/tutorials/tools) - 通过本教程学习如何高效使用工具
- [工具文档](https://huggingface.co/docs/smolagents/v1.8.1/en/reference/tools) - 全面的工具参考文档
- [工具使用导览](https://huggingface.co/docs/smolagents/v1.8.1/en/guided_tour#tools) - 逐步指导如何构建和使用工具
- [构建高效智能体](https://huggingface.co/docs/smolagents/tutorials/building_good_agents) - 关于开发可靠高性能自定义函数智能体的最佳实践指南

### 测验时间！
https://huggingface.co/learn/agents-course/zh-CN/unit2/smolagents/final_quiz.md

# 测验时间！

恭喜你完成了 `smolagents` 的学习材料！你已经取得了很多成就。现在，是时候通过一个测验来测试你的知识了。🧠

## 说明

- 测验由代码问题组成。
- 你将得到完成代码片段的指示。
- 仔细阅读指示并相应地完成代码片段。
- 对于每个问题，你将得到结果和一些反馈。

🧘 **这个测验不计分也不提供证书**。这是关于你理解 `smolagents` 库，并了解你是否应该在书面材料上花更多时间。在接下来的单元中，你将在用例和项目中测试这些知识。

让我们开始吧！

## 测验 🚀

你也可以点击👉 [这里](https://huggingface.co/spaces/agents-course/unit2_smolagents_quiz) 访问测验

### `smolagents` 简介
https://huggingface.co/learn/agents-course/zh-CN/unit2/smolagents/introduction.md

# `smolagents` 简介

欢迎来到本模块，在这里你将学习**如何使用 [`smolagents`](https://github.com/huggingface/smolagents) 库构建有效的智能体**，该库提供了一个轻量级框架，用于创建功能强大的AI智能体。  

`smolagents` 是 Hugging Face 的一个库；因此，我们非常感谢您通过**加星标**的方式支持 smolagents [`仓库`](https://github.com/huggingface/smolagents)：

## 模块概览

本模块提供了使用 `smolagents` 构建智能体的关键概念和实用策略的全面概述。

面对众多可用的开源框架，了解使 `smolagents` 成为有用选择的组件和功能，或确定何时另一种解决方案可能更合适，这一点至关重要。

我们将探索关键的智能体类型，包括为软件开发任务设计的代码智能体（code agents），用于创建模块化、函数驱动工作流的工具调用智能体（tool calling agents），以及访问和综合信息的检索智能体（retrieval agents）。

此外，我们还将讨论多个智能体的编排，以及视觉能力和网络浏览的集成，这为动态和上下文感知应用开辟了新的可能性。

在本单元中，第一单元的智能体阿尔弗雷德（Alfred）回归了。这次，他使用 `smolagents` 框架进行内部运作。我们将一起探索这个框架背后的关键概念，同时阿尔弗雷德将处理各种任务。阿尔弗雷德正在韦恩庄园（Wayne Manor）组织一场派对，趁韦恩家族🦇外出时，他有很多事情要做。跟随我们一起展示他的旅程，看他如何使用 `smolagents` 处理这些任务！

> [!TIP]
> 在本单元中，您将学习使用 `smolagents` 库构建AI智能体。您的智能体将能够搜索数据、执行代码并与网页交互。您还将学习如何结合多个智能体来创建更强大的系统。

![Alfred the agent](https://huggingface.co/datasets/agents-course/course-images/resolve/main/en/unit1/this-is-alfred.jpg)

## 内容

在这个关于 `smolagents` 的单元中，我们涵盖：  

### 1️⃣ [为什么使用 smolagents](./why_use_smolagents)

`smolagents` 是众多可用于应用程序开发的开源智能体框架之一。其他选择包括 `LlamaIndex` 和 `LangGraph`，这些在本课程的其他模块中也有涵盖。`smolagents` 提供了几个关键特性，可能使其非常适合特定用例，但在选择框架时，我们应该始终考虑所有选项。我们将探讨使用 `smolagents` 的优势和缺点，帮助您根据项目需求做出明智的决定。

### 2️⃣ [代码智能体](./code_agents)

`CodeAgents`（代码智能体）是 `smolagents` 中的主要智能体类型。这些智能体不是生成 JSON 或文本，而是生成 Python 代码来执行操作。本模块探讨它们的目的、功能以及工作原理，并提供实际例子来展示它们的能力。  

### 3️⃣ [工具调用智能体](./tool_calling_agents)

`ToolCallingAgents`（工具调用智能体）是 `smolagents` 支持的第二种智能体类型。与生成 Python 代码的 `CodeAgents` 不同，这些智能体依赖于系统必须解析和解释以执行操作的 JSON/文本块。本模块涵盖它们的功能、与 `CodeAgents` 的主要区别，并提供示例说明其用法。

### 4️⃣ [工具](./tools)

正如我们在第 1 单元中看到的，工具是大语言模型（LLM）可以在智能体系统中使用的函数，它们作为智能体行为的基本构建块。本模块涵盖如何创建工具、它们的结构，以及使用 `Tool` 类或 `@tool` 装饰器的不同实现方法。您还将了解默认工具箱、如何与社区共享工具，以及如何加载社区贡献的工具以在您的智能体中使用。

### 5️⃣ [检索智能体](./retrieval_agents)

检索智能体（Retrieval agents）使模型能够访问知识库，从而可以从多个来源搜索、综合和检索信息。它们利用向量存储（vector stores）进行高效检索，并实现 **检索增强生成（Retrieval-Augmented Generation，RAG）** 模式。这些智能体特别适用于将网络搜索与自定义知识库集成，同时通过记忆系统维持对话上下文。本模块探讨实施策略，包括用于稳健信息检索的回退机制。

### 6️⃣ [多智能体系统](./multi_agent_systems)

有效地编排多个智能体对于构建强大的多智能体系统至关重要。通过组合具有不同能力的智能体（例如，将网络搜索智能体与代码执行智能体结合），您可以创建更复杂的解决方案。本模块专注于设计、实施和管理多智能体系统，以最大限度地提高效率和可靠性。  

### 7️⃣ [视觉和浏览器智能体](./vision_agents)

视觉智能体（Vision agents）通过整合 **视觉-语言模型（Vision-Language Models，VLMs）** 扩展了传统智能体的能力，使其能够处理和解释视觉信息。本模块探讨如何设计和集成由 VLM 驱动的智能体，从而解锁诸如基于图像的推理、视觉数据分析和多模态交互等高级功能。我们还将使用视觉智能体构建一个浏览器智能体，能够浏览网络并从中提取信息。

## 资源

- [smolagents 文档](https://huggingface.co/docs/smolagents) - smolagents 库的官方文档
- [构建有效的智能体](https://www.anthropic.com/research/building-effective-agents) - 关于智能体架构的研究论文
- [智能体指南](https://huggingface.co/docs/smolagents/tutorials/building_good_agents) - 构建可靠智能体的最佳实践
- [LangGraph 智能体](https://langchain-ai.github.io/langgraph/) - 智能体实现的其他示例
- [函数调用指南](https://platform.openai.com/docs/guides/function-calling) - 了解大语言模型中的函数调用
- [RAG 最佳实践](https://www.pinecone.io/learn/retrieval-augmented-generation/) - 实施有效 RAG 的指南

### 为什么选择 smolagents
https://huggingface.co/learn/agents-course/zh-CN/unit2/smolagents/why_use_smolagents.md

# 为什么选择 smolagents

在本模块中，我们将探讨使用 [smolagents](https://huggingface.co/docs/smolagents/en/index) 的优缺点，帮助您做出明智的决策，判断它是否是满足您需求的正确框架。

## 什么是 `smolagents`？

`smolagents` 是一个简单而强大的框架，用于构建 AI 智能体。它为 LLM 提供了与现实世界互动的能力，例如搜索或生成图像。

正如我们在第 1 单元中学到的，AI 智能体是使用 LLM 基于 **'观察'** 生成 **'思考'** 并执行 **'操作'** 的程序。接下来我们来探讨这在 smolagents 中是如何实现的。

### `smolagents` 的关键优势
- **简洁性：** 最小的代码复杂性和抽象层，使框架易于理解、采用和扩展。
- **灵活的 LLM 支持：** 通过与 Hugging Face 工具和外部 API 的集成，支持任何 LLM。
- **代码优先方法：** 首选支持直接在代码中编写操作的 Code Agents，无需解析并简化工具调用。
- **HF Hub 集成：** 与 Hugging Face Hub 无缝集成，允许使用 Gradio Spaces 作为工具。

### 何时使用 smolagents？

考虑到这些优势，我们应该在什么情况下选择 smolagents 而不是其他框架？

smolagents 在以下情况下是最理想的：
- 您需要一个 **轻量级且最小化的解决方案**。
- 您希望 **快速实验** 而无需复杂的配置。
- 您的应用逻辑 **相对简单**。

### 代码 vs. JSON 操作
与其他框架中的智能体以 JSON 形式编写操作不同，`smolagents` **专注于代码中的工具调用**，简化了执行过程。这是因为无需解析 JSON 来构建调用工具的代码：输出可以直接执行。

下图展示了这种差异：

![代码 vs. JSON 操作](https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/transformers/code_vs_json_actions.png)

要回顾代码与 JSON 操作之间的区别，您可以重新访问 [第 1 单元的操作部分](https://huggingface.co/learn/agents-course/unit1/actions#actions-enabling-the-agent-to-engage-with-its-environment)。

### `smolagents` 中的智能体类型

`smolagents` 中的智能体作为 **多步骤智能体** 运行。

每个 [`MultiStepAgent`](https://huggingface.co/docs/smolagents/main/en/reference/agents#smolagents.MultiStepAgent) 执行：
- 一次思考
- 一次工具调用和执行

除了使用 **[CodeAgent](https://huggingface.co/docs/smolagents/main/en/reference/agents#smolagents.CodeAgent)** 作为主要类型的智能体外，smolagents 还支持 **[ToolCallingAgent](https://huggingface.co/docs/smolagents/main/en/reference/agents#smolagents.ToolCallingAgent)**，后者以 JSON 形式编写工具调用。

我们将在接下来的部分中更详细地探讨每种智能体类型。

> [!TIP]
> 在 smolagents 中，工具是使用 @tool 装饰器包装 Python 函数或 Tool 类定义的。

### `smolagents` 中的模型集成
`smolagents` 支持灵活的 LLM 集成，允许使用符合 [某些标准](https://huggingface.co/docs/smolagents/main/en/reference/models) 的任何可调用模型。该框架提供了多个预定义类以简化模型连接：

- **[TransformersModel](https://huggingface.co/docs/smolagents/main/en/reference/models#smolagents.TransformersModel):** 实现本地 `transformers` 管道以实现无缝集成。
- **[InferenceClientModel](https://huggingface.co/docs/smolagents/main/en/reference/models#smolagents.InferenceClientModel):** 通过 [Hugging Face 的基础设施](https://huggingface.co/docs/api-inference/index) 或越来越多的 [第三方推理提供商](https://huggingface.co/docs/huggingface_hub/main/en/guides/inference#supported-providers-and-tasks) 支持 [无服务器推理](https://huggingface.co/docs/huggingface_hub/main/en/guides/inference) 调用。
- **[LiteLLMModel](https://huggingface.co/docs/smolagents/main/en/reference/models#smolagents.LiteLLMModel):** 利用 [LiteLLM](https://www.litellm.ai/) 实现轻量级模型交互。
- **[OpenAIServerModel](https://huggingface.co/docs/smolagents/main/en/reference/models#smolagents.OpenAIServerModel):** 连接到提供 OpenAI API 接口的任何服务。
- **[AzureOpenAIServerModel](https://huggingface.co/docs/smolagents/main/en/reference/models#smolagents.AzureOpenAIServerModel):** 支持与任何 Azure OpenAI 部署集成。

这种灵活性确保开发人员可以选择最适合其特定用例的模型和服务，并允许轻松进行实验。

现在我们已经了解了何时以及为何使用 smolagents，让我们深入探讨这个强大的库吧！

## 资源

- [smolagents 博客](https://huggingface.co/blog/smolagents) - 关于 smolagents 和代码交互的介绍

### 小测验 (不计分) [[quiz1]]
https://huggingface.co/learn/agents-course/zh-CN/unit2/smolagents/quiz1.md

# 小测验 (不计分) [[quiz1]]

让我们用一个快速测验来测试你对 `smolagents` 的理解！请记住，自我测试有助于强化学习并识别可能需要复习的领域。

这是一个可选测验，不计分。

### Q1: 选择 `smolagents` 而非其他框架的主要优势之一是什么？
哪个陈述最能体现 `smolagents` 方法的核心优势？

---

### Q2: 在哪种情况下，你可能最能从使用 smolagents 中受益？
哪种情况最符合 smolagents 的优势？

---

### Q3: smolagents 在模型集成方面提供了灵活性。哪个陈述最能反映其方法？
选择最准确描述 smolagents 如何与 LLM 互操作的说明。

---

### Q4: smolagents 如何处理基于代码的操作和基于 JSON 的操作之间的争论？
哪个陈述正确地描述了 smolagents 关于操作格式的理念？

---

### Q5: smolagents 如何与 Hugging Face Hub 集成以获得额外优势？
哪个陈述准确描述了 Hub 集成的核心优势之一？

---

恭喜你完成了这个测验！🎉 如果你错过了任何问题，可以考虑复习*为什么使用 smolagents*部分以更深入理解。如果你表现良好，你已经准备好探索 smolagents 中更高级的主题了！

### 将操作编写为代码片段或 JSON 结构
https://huggingface.co/learn/agents-course/zh-CN/unit2/smolagents/tool_calling_agents.md

# 将操作编写为代码片段或 JSON 结构

> [!TIP]
> 您可以通过 此 Notebook 跟随代码实践，该文件支持在 Google Colab 中运行。

工具调用智能体（Tool Calling Agents）是 `smolagents` 中提供的第二种智能体类型。与使用 Python 代码片段的代码智能体（Code Agents）不同，这类智能体**利用 LLM 提供商的内置工具调用能力**来生成 **JSON 结构**的工具调用指令。这是 OpenAI、Anthropic 等主流提供商采用的标准方法。

当 Alfred 想要搜索餐饮服务和派对创意时，`CodeAgent` 会生成并运行如下 Python 代码：

```python
for query in [
    "Best catering services in Gotham City", 
    "Party theme ideas for superheroes"
]:
    print(web_search(f"Search for: {query}"))
```

而 `ToolCallingAgent` 则会创建 JSON 结构：

```python
[
    {"name": "web_search", "arguments": "Best catering services in Gotham City"},
    {"name": "web_search", "arguments": "Party theme ideas for superheroes"}
]
```

该 JSON 结构随后会被用于执行工具调用。

尽管 `smolagents` 主要专注于 `CodeAgents`（因为[它们整体表现更好](https://huggingface.co/papers/2402.01030)），但对于不需要变量处理或复杂工具调用的简单系统，`ToolCallingAgents` 仍然可以高效工作。

![代码操作与 JSON 操作对比](https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/transformers/code_vs_json_actions.png)

## 工具调用智能体的工作原理

工具调用智能体遵循与代码智能体相同的多步骤工作流程（详见[前一章节](./code_agents)）。关键区别在于**操作结构方式**：智能体不再生成可执行代码，而是**生成指定工具名称和参数的 JSON 对象**，系统随后**解析这些指令**来执行相应工具。

## 示例：运行工具调用智能体

让我们重新审视 Alfred 开始筹备派对的示例，这次使用 `ToolCallingAgent` 来展示区别。我们将构建一个能够使用 DuckDuckGo 进行网页搜索的智能体，与代码智能体示例的唯一区别在于智能体类型——框架会处理其他所有细节：

```python
from smolagents import ToolCallingAgent, DuckDuckGoSearchTool, InferenceClientModel

agent = ToolCallingAgent(tools=[DuckDuckGoSearchTool()], model=InferenceClientModel())

agent.run("Search for the best music recommendations for a party at the Wayne's mansion.")
```

当查看智能体的执行跟踪时，您将看到类似以下内容而非 `Executing parsed code:`：

```text
╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ Calling tool: 'web_search' with arguments: {'query': "best music recommendations for a party at Wayne's         │
│ mansion"}                                                                                                       │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
```  

智能体生成结构化的工具调用指令，系统通过处理这些指令来生成输出，而非像 `CodeAgent` 那样直接执行代码。

现在我们已经了解两种智能体类型，可以根据需求选择合适的一种。让我们继续探索 `smolagents`，让 Alfred 的派对大获成功！🎉

## 资源
- [ToolCallingAgent 文档](https://huggingface.co/docs/smolagents/v1.8.1/en/reference/agents#smolagents.ToolCallingAgent) - ToolCallingAgent 的官方文档

### 什么是 `LangGraph`？
https://huggingface.co/learn/agents-course/zh-CN/unit2/langgraph/when_to_use_langgraph.md

# 什么是 `LangGraph`？

`LangGraph` 是由 [LangChain](https://www.langchain.com/) 开发的框架，**用于管理集成 LLM 的应用程序的控制流**。

## `LangGraph` 和 `LangChain` 有何不同？

LangChain 提供了与模型和其他组件交互的标准接口，可用于检索、LLM 调用和工具调用。
LangChain 的类可能会在 LangGraph 中使用，但不是必须的。

这两个包是独立的可以单独使用，但最终你在网上找到的资源都会同时使用这两个包。

## 何时应该使用 `LangGraph`？
### 控制 vs 自由度

在设计 AI 应用时，你面临 **控制** 与 **自由度** 的基本权衡：

- **自由度** 赋予 LLM 更多创造性地解决问题的空间
- **控制** 能确保可预测的行为并维持安全护栏

像 *smolagents* 中的代码智能体（Code Agents）具有高度自由度。它们可以在单个行动步骤中调用多个工具、创建自己的工具等。然而，这种行为会使它们比使用 JSON 的常规 Agent 更难预测和控制！

`LangGraph` 则位于光谱的另一端，当你需要对智能体的执行进行 **"控制"** 时，它就会大放异彩。

当你需要 **对应用程序保持控制** 时，LangGraph 特别有价值。它为你提供了构建可预测流程应用程序的工具，同时仍能利用 LLM 的强大能力。

简而言之，如果你的应用程序包含需要以特定方式编排的多个步骤，并在每个连接点做出决策，**LangGraph 就能提供你所需的结构**。

举个例子，假设我们要构建一个能够回答文档相关问题的 LLM 助手。

由于 LLM 最擅长理解文本，在回答问题之前，你需要将其他复杂模态（图表、表格）转换为文本。但这个选择取决于你拥有的文档类型！

我选择将这种分支流程表示为：

> 💡 **提示:** 左侧部分不是智能体，因为这里不涉及工具调用。但右侧部分需要编写代码来查询 xls 文件（转换为 pandas 并操作它）。

虽然这个分支是确定性的，但你也可以设计基于 LLM 输出结果的非确定性条件分支。

LangGraph 表现出色的关键场景包括：

- 需要显式控制流程的 **多步骤推理过程**
- 需要在步骤之间 **保持状态持久化** 的应用程序
- **结合确定性逻辑与 AI 能力** 的系统
- 需要 **人工介入** 的工作流
- 多个组件协同工作的 **复杂智能体架构**

本质上，只要有可能，**作为人类** 就应该根据每个操作的输出设计行动流程，并据此决定下一步执行什么。在这种情况下，LangGraph 就是你正确的选择！

在我看来，`LangGraph` 是市场上最适合生产环境的智能体框架。

## LangGraph 如何工作？

其核心在于，`LangGraph` 使用有向图结构来定义应用程序的流程：

- **节点** 表示独立的处理步骤（如调用 LLM、使用工具或做出决策）
- **边** 定义步骤之间可能的转换
- **状态** 由用户定义和维护，并在执行期间在节点间传递。当决定下一个目标节点时，我们查看的就是当前状态

我们将在下一章更深入地探讨这些基本模块！

## 它和普通 Python 有何不同？为什么需要 LangGraph？

你可能会想："我可以用常规 Python 代码和 if-else 语句来处理所有这些流程，对吧？"

虽然技术上可行，但对于构建复杂系统，LangGraph 相比原生 Python 有 **诸多优势**。没有 LangGraph 你也能构建相同应用，但它能为你提供更便捷的工具和抽象。

它包含状态管理、可视化、日志追踪（traces）、内置的人类介入机制等功能。

### LangGraph 的核心构建模块
https://huggingface.co/learn/agents-course/zh-CN/unit2/langgraph/building_blocks.md

# LangGraph 的核心构建模块

要使用 LangGraph 构建应用程序，需要理解其核心组件。让我们探索构成 LangGraph 应用程序的基础构建模块。

LangGraph 应用程序从 **entrypoint** 开始，根据执行情况，流程可能流向不同的函数直到抵达 END。

## 1. 状态（State）

**State** 是 LangGraph 中的核心概念，表示流经应用程序的所有信息。

```python
from typing_extensions import TypedDict

class State(TypedDict):
    graph_state: str
```

状态是 **用户自定义** 的，因此需要仔细设计字段以包含决策过程所需的所有数据！

> 💡 **提示:** 仔细考虑应用程序需要在步骤之间跟踪哪些信息。

## 2. 节点（Nodes）

**Nodes** 是 Python 函数。每个节点：
- 接收状态作为输入
- 执行某些操作
- 返回状态更新

```python
def node_1(state):
    print("---Node 1---")
    return {"graph_state": state['graph_state'] +" I am"}

def node_2(state):
    print("---Node 2---")
    return {"graph_state": state['graph_state'] +" happy!"}

def node_3(state):
    print("---Node 3---")
    return {"graph_state": state['graph_state'] +" sad!"}
```

举例, 节点可以包含：
- **LLM 调用**: 生成文本或做出决策
- **工具调用**: 与外部系统交互
- **条件逻辑**: 决定后续步骤
- **人工干预**: 获取用户输入

> 💡 **信息:** 像 START 和 END 这样的必要节点已直接包含在 LangGraph 中。

## 3. 边（Edges）

**Edges** 连接节点并定义图中的可能路径：

```python
import random
from typing import Literal

def decide_mood(state) -> Literal["node_2", "node_3"]:
    
    # 通常我们会根据状态决定下一个节点
    user_input = state['graph_state'] 
    
    # 这里我们在节点2和节点3之间简单实现 50/50 的概率分配
    if random.random() 

最重要的是可以调用：
```python
graph.invoke({"graph_state" : "Hi, this is Lance."})
```
output :
```
---Node 1---
---Node 3---
{'graph_state': 'Hi, this is Lance. I am sad!'}
```

## 下一步？

下一节我们将通过构建第一个图表来实践这些概念。该图表将让 Alfred 能够处理电子邮件，进行分类，并在邮件真实时起草初步回复。

### 构建你的第一个 LangGraph
https://huggingface.co/learn/agents-course/zh-CN/unit2/langgraph/first_graph.md

# 构建你的第一个 LangGraph

现在我们已经理解了基本构建模块，让我们通过构建第一个功能图来实践。我们将实现 Alfred 的邮件处理系统，他需要：

1. 阅读 incoming emails
2. 将其分类为 spam 或 legitimate
3. 为 legitimate 邮件起草初步响应
4. 当邮件合法时向 Mr. Wayne 发送信息（仅打印）

这个示例演示了如何使用 LangGraph 构建涉及基于 LLM 决策的工作流程结构。虽然这不能算是真正的 Agent（因为没有涉及工具），但本节更侧重于学习 LangGraph 框架而非 Agents。

> [!TIP]
> 你可以在 这个 notebook 中查看完整代码，并通过 Google Colab 运行。

## 工作流程
这是我们将要构建的工作流程：

## 环境设置

首先安装必要的包：

```python
%pip install langgraph langchain_openai
```

接下来，让我们导入必要的模块：

```python
import os
from typing import TypedDict, List, Dict, Any, Optional
from langgraph.graph import StateGraph, END
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage, AIMessage
```

## 步骤 1：定义我们的状态

让我们定义 Alfred 在电子邮件处理工作流程中需要跟踪哪些信息：

```python
class EmailState(TypedDict):
    # 正在处理的电子邮件
    email: Dict[str, Any]  # 包含主题、发件人、正文等。
    
    # 分析与决策
    is_spam: Optional[bool]
    
    # 响应生成
    draft_response: Optional[str]
    
    # 处理元数据
    messages: List[Dict[str, Any]]  # 跟踪与 LLM 的对话以进行分析
```

> 💡 **提示：**让您的状态足够全面，以跟踪所有重要信息，但避免用不必要的细节使其变得臃肿。

## 第 2 步：定义我们的节点

现在，让我们创建将形成我们节点的处理函数：

```python
# Initialize our LLM
model = ChatOpenAI(temperature=0)

def read_email(state: EmailState):
    """Alfred reads and logs the incoming email"""
    email = state["email"]
    
    # 在这里我们可能会做一些初步的预处理
    print(f"Alfred is processing an email from {email['sender']} with subject: {email['subject']}")
    
    # 这里不需要更改状态
    return {}

def classify_email(state: EmailState):
    """Alfred uses an LLM to determine if the email is spam or legitimate"""
    email = state["email"]
    
    # 为 LLM 准备提示
    prompt = f"""
    As Alfred the butler, analyze this email and determine if it is spam or legitimate.
    
    Email:
    From: {email['sender']}
    Subject: {email['subject']}
    Body: {email['body']}
    
    First, determine if this email is spam. If it is spam, explain why.
    If it is legitimate, categorize it (inquiry, complaint, thank you, etc.).
    """
    
    # Call the LLM
    messages = [HumanMessage(content=prompt)]
    response = model.invoke(messages)
    
    # 解析响应的简单逻辑（在实际应用中，您需要更强大的解析）
    response_text = response.content.lower()
    is_spam = "spam" in response_text and "not spam" not in response_text
    
    # 如果是垃圾邮件，请提取原因
    spam_reason = None
    if is_spam and "reason:" in response_text:
        spam_reason = response_text.split("reason:")[1].strip()
    
    # 确定类别是否合法
    email_category = None
    if not is_spam:
        categories = ["inquiry", "complaint", "thank you", "request", "information"]
        for category in categories:
            if category in response_text:
                email_category = category
                break
    
    # 更新消息以进行追踪
    new_messages = state.get("messages", []) + [
        {"role": "user", "content": prompt},
        {"role": "assistant", "content": response.content}
    ]
    
    # 返回状态更新
    return {
        "is_spam": is_spam,
        "spam_reason": spam_reason,
        "email_category": email_category,
        "messages": new_messages
    }

def handle_spam(state: EmailState):
    """Alfred discards spam email with a note"""
    print(f"Alfred has marked the email as spam. Reason: {state['spam_reason']}")
    print("The email has been moved to the spam folder.")
    
    # 我们已处理完这封电子邮件
    return {}

def draft_response(state: EmailState):
    """Alfred drafts a preliminary response for legitimate emails"""
    email = state["email"]
    category = state["email_category"] or "general"
    
    # 为 LLM 准备提示词
    prompt = f"""
    As Alfred the butler, draft a polite preliminary response to this email.
    
    Email:
    From: {email['sender']}
    Subject: {email['subject']}
    Body: {email['body']}
    
    This email has been categorized as: {category}
    
    Draft a brief, professional response that Mr. Hugg can review and personalize before sending.
    """
    
    # Call the LLM
    messages = [HumanMessage(content=prompt)]
    response = model.invoke(messages)
    
    # 更新消息以进行追踪
    new_messages = state.get("messages", []) + [
        {"role": "user", "content": prompt},
        {"role": "assistant", "content": response.content}
    ]
    
    # 返回状态更新
    return {
        "draft_response": response.content,
        "messages": new_messages
    }

def notify_mr_hugg(state: EmailState):
    """Alfred notifies Mr. Hugg about the email and presents the draft response"""
    email = state["email"]
    
    print("\n" + "="*50)
    print(f"Sir, you've received an email from {email['sender']}.")
    print(f"Subject: {email['subject']}")
    print(f"Category: {state['email_category']}")
    print("\nI've prepared a draft response for your review:")
    print("-"*50)
    print(state["draft_response"])
    print("="*50 + "\n")
    
    # 我们已处理完这封电子邮件
    return {}
```

## 步骤 3：定义我们的路由逻辑

我们需要一个函数来确定分类后要采取哪条路径：

```python
def route_email(state: EmailState) -> str:
    """Determine the next step based on spam classification"""
    if state["is_spam"]:
        return "spam"
    else:
        return "legitimate"
```

> 💡 **注意：** LangGraph 调用此路由函数来确定在分类节点之后要跟随哪条边。返回值必须与我们的条件边映射中的一个键匹配。

## 步骤 4：创建 StateGraph 并定义边

现在我们将所有内容连接在一起：

```python
# 创建 graph
email_graph = StateGraph(EmailState)

# 添加 nodes
email_graph.add_node("read_email", read_email)
email_graph.add_node("classify_email", classify_email)
email_graph.add_node("handle_spam", handle_spam)
email_graph.add_node("draft_response", draft_response)
email_graph.add_node("notify_mr_hugg", notify_mr_hugg)

# 添加 edges - 定义流程
email_graph.add_edge("read_email", "classify_email")

# 从 classify_email 添加条件分支
email_graph.add_conditional_edges(
    "classify_email",
    route_email,
    {
        "spam": "handle_spam",
        "legitimate": "draft_response"
    }
)

# 添加最后的 edges
email_graph.add_edge("handle_spam", END)
email_graph.add_edge("draft_response", "notify_mr_hugg")
email_graph.add_edge("notify_mr_hugg", END)

# 编译 graph
compiled_graph = email_graph.compile()
```

注意我们如何使用 LangGraph 提供的特殊“END”节点。这表示工作流完成的终端状态。

## 步骤 5：运行应用程序

让我们用一封合法的电子邮件和一封垃圾邮件来测试我们的图表：

```python
# 合法电子邮件示例
legitimate_email = {
    "sender": "john.smith@example.com",
    "subject": "Question about your services",
    "body": "Dear Mr. Hugg, I was referred to you by a colleague and I'm interested in learning more about your consulting services. Could we schedule a call next week? Best regards, John Smith"
}

# 垃圾邮件示例
spam_email = {
    "sender": "winner@lottery-intl.com",
    "subject": "YOU HAVE WON $5,000,000!!!",
    "body": "CONGRATULATIONS! You have been selected as the winner of our international lottery! To claim your $5,000,000 prize, please send us your bank details and a processing fee of $100."
}

# 处理合法电子邮件
print("\nProcessing legitimate email...")
legitimate_result = compiled_graph.invoke({
    "email": legitimate_email,
    "is_spam": None,
    "spam_reason": None,
    "email_category": None,
    "draft_response": None,
    "messages": []
})

# 处理垃圾邮件
print("\nProcessing spam email...")
spam_result = compiled_graph.invoke({
    "email": spam_email,
    "is_spam": None,
    "spam_reason": None,
    "email_category": None,
    "draft_response": None,
    "messages": []
})
```

## 第 6 步：使用 Langfuse 检查我们的邮件分类智能体 📡

随着 Alfred 对主分类智能体进行微调，他越来越厌倦调试其运行。智能体本质上是不可预测的，难以检查。但由于他的目标是构建终极垃圾邮件检测智能体并将其部署到生产中，因此他需要强大的可追溯性以供将来监控和分析。

为此，Alfred 可以使用可观察性工具（例如 [Langfuse](https://langfuse.com/)）来跟踪和监控智能体。

首先，我们 pip install Langfuse：
```python
%pip install -q langfuse
```

接下来，我们将 Langfuse API 密钥和主机地址添加为环境变量。您可以通过注册 [Langfuse Cloud](https://cloud.langfuse.com) 或 [self-host Langfuse](https://langfuse.com/self-hosting) 获取 Langfuse 凭据。

```python
import os
 
# Get keys for your project from the project settings page: https://cloud.langfuse.com
os.environ["LANGFUSE_PUBLIC_KEY"] = "pk-lf-..." 
os.environ["LANGFUSE_SECRET_KEY"] = "sk-lf-..."
os.environ["LANGFUSE_HOST"] = "https://cloud.langfuse.com" # 🇪🇺 EU region
# os.environ["LANGFUSE_HOST"] = "https://us.cloud.langfuse.com" # 🇺🇸 US region
```

然后，我们配置 [Langfuse `callback_handler`](https://langfuse.com/docs/integrations/langchain/tracing#add-langfuse-to-your-langchain-application)，并通过将 `langfuse_callback` 添加到图的调用来检测智能体：`config={"callbacks": [langfuse_handler]}`。

```python   
from langfuse.langchain import CallbackHandler

# 为 LangGraph/Langchain 初始化 Langfuse CallbackHandler（跟踪）
langfuse_handler = CallbackHandler()

# 处理合法电子邮件
legitimate_result = compiled_graph.invoke(
    input={"email": legitimate_email, "is_spam": None, "spam_reason": None, "email_category": None, "draft_response": None, "messages": []},
    config={"callbacks": [langfuse_handler]}
)
```

Alfred 现已连接 🔌！LangGraph 的运行记录在 Langfuse 中，使他能够全面了解智能体的行为。通过此设置，他可以重新查看之前的运行并进一步完善他的邮件分类智能体。

![Langfuse 中的示例跟踪](https://langfuse.com/images/cookbook/huggingface-agent-course/langgraph-trace-legit.png)

_[带有合法电子邮件的跟踪的公共链接](https://cloud.langfuse.com/project/cloramnkj0002jz088vzn1ja4/traces/f5d6d72e-20af-4357-b232-af44c3728a7b?timestamp=2025-03-17T10%3A13%3A28.413Z&observation=6997ba69-043f-4f77-9445-700a033afba1)_

## 可视化我们的图表

LangGraph 允许我们可视化我们的工作流程，以便更好地理解和调试其结构：

```python
compiled_graph.get_graph().draw_mermaid_png()
```

这会产生一个可视化表示，显示我们的节点如何连接以及可以采取的条件路径。

## 我们构建了什么

我们创建了一个完整的电子邮件处理工作流程：

1. 接收传入的电子邮件
2. 使用 LLM 将其分类为垃圾邮件或合法邮件
3. 通过丢弃垃圾邮件来处理垃圾邮件
4. 对于合法电子邮件，起草回复并通知 Mr. Hugg

这展示了 LangGraph 使用 LLM 编排复杂工作流程同时保持清晰、结构化流程的强大功能。

## 关键要点

- **状态管理**：我们定义了全面的状态来跟踪电子邮件处理的所有方面
- **节点实现**：我们创建了与 LLM 交互的功能节点
- **条件路由**：我们根据电子邮件分类实现了分支逻辑
- **终端状态**：我们使用 END 节点标记工作流程中的完成点

## 下一步是什么？

在下一部分中，我们将探索 LangGraph 的更多高级功能，包括处理工作流中的人机交互以及根据多种条件实现更复杂的分支逻辑。

### 结语
https://huggingface.co/learn/agents-course/zh-CN/unit2/langgraph/conclusion.md

# 结语

祝贺您完成了第二单元的 `LangGraph` 模块！🥳

您现在已经掌握了使用 LangGraph 构建结构化工作流的基础知识，这些工作流可以直接投入生产环境。

本模块只是您 LangGraph 学习之旅的起点。对于更深入的学习内容，我们推荐：

- 探索 [LangGraph 官方文档](https://github.com/langchain-ai/langgraph)
- 参加 LangChain Academy 的 [LangGraph 入门课程](https://academy.langchain.com/courses/intro-to-langgraph)
- 亲自构建一些项目！

在下一个单元中，您将探索真实的应用场景。是时候将理论付诸实践了！

我们非常重视 **您对课程的想法和改进建议**。如果有任何反馈，请👉[填写此表单](https://docs.google.com/forms/d/e/1FAIpQLSe9VaONn0eglax0uTwi29rIn4tM7H2sYmmybmG5jJNlE5v0xA/viewform?usp=dialog)

### 持续学习，保持激情！🤗

尊敬的先生/女士！🎩🦇

-Alfred-

### 文档分析图
https://huggingface.co/learn/agents-course/zh-CN/unit2/langgraph/document_analysis_agent.md

# 文档分析图

Alfred 为您服务。作为韦恩先生值得信赖的管家，我已记录下协助处理各类文档需求的工作流程。当 Mr Wayne 外出进行...夜间活动时，我会确保所有文件、训练计划和营养方案都得到妥善分析和整理。

在离开前，他留下了本周训练计划的笔记。我随后负责拟定了明日餐点的**菜单**。

为应对未来的类似需求，让我们使用 LangGraph 构建一个文档分析系统来服务 Mr Wayne。该系统能够：

1. 处理图像文档
2. 使用视觉模型 (Vision Language Model) 提取文本
3. 在需要时执行计算（用于演示常规工具）
4. 分析内容并提供简明摘要
5. 执行与文档相关的特定指令

## 管家的工作流程

我们将构建的工作流程遵循以下结构化方案：

![Butler's Document Analysis Workflow](https://huggingface.co/datasets/agents-course/course-images/resolve/main/en/unit2/LangGraph/alfred_flow.png)

> [!TIP]
> 您可以在 这个 notebook 中查看代码，并通过 Google Colab 运行。
## 设置环境

```python
%pip install langgraph langchain_openai Pillow base64 langchain_core
```
and imports :
```python
import base64
from typing import List, TypedDict, Annotated, Optional
from langchain.schema import HumanMessage
from langchain_openai import ChatOpenAI
from langchain_core.messages import AnyMessage, SystemMessage
from langgraph.graph.message import add_messages
from langgraph.graph import START, StateGraph
from langgraph.prebuilt import tools_condition
from langgraph.prebuilt import ToolNode
from IPython.display import Image, display
```

## 定义智能体的状态

这个状态比我们之前见过的稍微复杂些。
AnyMessage 是来自 langchain 的类，用于定义消息，而 add_messages 是一个操作符，它会添加最新消息而不是覆盖现有状态。

这是 langGraph 中的一个新概念，您可以在状态中添加 operators 来定义它们之间的交互方式。

```python
class AgentState(TypedDict):
    # 提供的文件
    input_file: Optional[str]  # Contains file path (PDF/PNG)
    messages: Annotated[list[AnyMessage], add_messages]
```

## 准备工具

```python
vision_llm = ChatOpenAI(model="gpt-4o")

def extract_text(img_path: str) -> str:
    """
    Extract text from an image file using a multimodal model.
    
    Master Wayne often leaves notes with his training regimen or meal plans.
    This allows me to properly analyze the contents.
    """
    all_text = ""
    try:
        # 读取图像并编码为 base64
        with open(img_path, "rb") as image_file:
            image_bytes = image_file.read()

        image_base64 = base64.b64encode(image_bytes).decode("utf-8")

        # 准备包含 base64 图像数据的提示
        message = [
            HumanMessage(
                content=[
                    {
                        "type": "text",
                        "text": (
                            "Extract all the text from this image. "
                            "Return only the extracted text, no explanations."
                        ),
                    },
                    {
                        "type": "image_url",
                        "image_url": {
                            "url": f"data:image/png;base64,{image_base64}"
                        },
                    },
                ]
            )
        ]

        # 调用具有视觉功能的模型
        response = vision_llm.invoke(message)

        # 附加提取的文本
        all_text += response.content + "\n\n"

        return all_text.strip()
    except Exception as e:
        # 管家应优雅地处理错误
        error_msg = f"Error extracting text: {str(e)}"
        print(error_msg)
        return ""

def divide(a: int, b: int) -> float:
    """Divide a and b - for Master Wayne's occasional calculations."""
    return a / b

# 为管家配备工具
tools = [
    divide,
    extract_text
]

llm = ChatOpenAI(model="gpt-4o")
llm_with_tools = llm.bind_tools(tools, parallel_tool_calls=False)
```

## 节点

```python
def assistant(state: AgentState):
    # 系统消息
    textual_description_of_tool="""
extract_text(img_path: str) -> str:
    Extract text from an image file using a multimodal model.

    Args:
        img_path: A local image file path (strings).

    Returns:
        A single string containing the concatenated text extracted from each image.
divide(a: int, b: int) -> float:
    Divide a and b
"""
    image=state["input_file"]
    sys_msg = SystemMessage(content=f"You are an helpful butler named Alfred that serves Mr. Wayne and Batman. You can analyse documents and run computations with provided tools:\n{textual_description_of_tool} \n You have access to some optional images. Currently the loaded image is: {image}")

    return {
        "messages": [llm_with_tools.invoke([sys_msg] + state["messages"])],
        "input_file": state["input_file"]
    }
```

## ReAct 模式：我如何协助 Wayne 先生

请允许我解释该智能体的工作方式。该智能体遵循被称为 ReAct 的模式（推理-行动-观察）

1. **推理** 分析文档和请求内容
2. **行动** 通过调用合适的工具执行操作
3. **观察** 工具执行结果
4. **重复** 上述步骤直到完全满足需求

这是使用 langGraph 实现的简单智能体实现。

```python
## The graph
builder = StateGraph(AgentState)

# 定义节点：这些节点完成工作
builder.add_node("assistant", assistant)
builder.add_node("tools", ToolNode(tools))

# 定义边：这些决定了控制流如何移动
builder.add_edge(START, "assistant")
builder.add_conditional_edges(
    "assistant",
    # 如果最新消息需要工具，则路由至工具
    # 否则，请直接回复
    tools_condition,
)
builder.add_edge("tools", "assistant")
react_graph = builder.compile()

# 展现管家的思考过程
display(Image(react_graph.get_graph(xray=True).draw_mermaid_png()))
```

![ReAct Pattern](https://huggingface.co/datasets/agents-course/course-images/resolve/main/en/unit2/LangGraph/Agent.png)

## 管家实战示例

### 示例1：简单计算

在以下示例中，我们添加这个 divide 示例仅作为演示用途。

```python
messages = [HumanMessage(content="Divide 6790 by 5")]
messages = react_graph.invoke({"messages": messages, "input_file": None})
```

对话将会继续：

```
Human: Divide 6790 by 5

AI Tool Call: divide(a=6790, b=5)

Tool Response: 1358.0

Alfred: The result of dividing 6790 by 5 is 1358.0.
```

### 示例 2：分析 Wayne 大师的训练文档

当 Wayne 大师留下他的训练和​​用餐笔记时：

```python
messages = [HumanMessage(content="According to the note provided by Mr. Wayne in the provided images. What's the list of items I should buy for the dinner menu?")]
messages = react_graph.invoke({"messages": messages, "input_file": "Batman_training_and_meals.png"})
```

交互将进行：

```
用户：根据 Wayne 先生在提供的图像中的注释。我应该为晚餐菜单购买哪些物品？

AI Tool Call: extract_text(img_path="Batman_training_and_meals.png")

Tool Response: [包含训练计划和菜单详情的提取文本]

Alfred：根据晚餐菜单，您应该购买以下物品：

1. 草饲本地西冷牛排（Grass-fed local sirloin steak）
2. 有机菠菜（Organic spinach）
3. 皮克略辣椒（Piquillo peppers） 
4. 土豆（用于烤制金黄香草土豆）
5. 鱼油（2 克）

确保牛排是草饲的，并且菠菜和辣椒是有机的，以获得最佳品质的餐点。
```

## 关键要点

若您希望创建自己的文档分析管家，请遵循以下关键原则：

1. **定义清晰的工具**（Define clear tools）用于特定文档相关任务
2. **创建强大的状态跟踪器**（Create a robust state tracker）以保持工具调用之间的上下文
3. **考虑错误处理机制**（Consider error handling）应对工具调用失败
5. **保持上下文感知能力**（Maintain contextual awareness）通过 add_messages 操作符确保历史交互的连贯性

遵循这些原则，您也能提供符合韦恩庄园标准的卓越文档分析服务。

*相信以上解释已足够详尽。恕我失陪，韦恩老爷的披风还需在夜巡前熨烫妥当。*

### 欢迎来到 `LangGraph` 的世界
https://huggingface.co/learn/agents-course/zh-CN/unit2/langgraph/introduction.md

# 欢迎来到 `LangGraph` 的世界

欢迎来到学习旅程的下一站！在本章节中，您将学习如何使用 [`LangGraph`](https://github.com/langchain-ai/langgraph) 框架来构建应用程序，该框架能帮助您组织和编排复杂的 LLM 工作流。

`LangGraph` 是一个通过提供对智能体流程的**控制**工具，帮助您构建**生产就绪**应用程序的框架。

## 模块概览

在本单元中，您将探索：

### 1️⃣ [什么是 LangGraph？何时使用它？](./when_to_use_langgraph)
### 2️⃣ [LangGraph 的构建模块](./building_blocks)
### 3️⃣ [邮件分拣管家 Alfred](./first_graph)
### 4️⃣ [文档分析智能体 Alfred](./document_analysis_agent)
### 5️⃣ [随堂测验](./quizz1)

> [!WARNING]
> 本节示例需要访问强大的 LLM/VLM 模型。我们使用 GPT-4o API 运行这些示例，因为该模型与 LangGraph 具有最佳兼容性。

通过本单元的学习，您将掌握构建健壮、有序且生产就绪的应用程序的能力！

需要说明的是，本节只是 LangGraph 的入门介绍，更多高级主题可以通过 LangChain 学院的免费课程学习：[LangGraph 入门指南](https://academy.langchain.com/courses/intro-to-langgraph)

让我们即刻启程！

## 扩展资源

- [LangGraph 智能体](https://langchain-ai.github.io/langgraph/) - LangGraph 智能体示例
- [LangChain 学院](https://academy.langchain.com/courses/intro-to-langgraph) - 来自 LangChain 的完整 LangGraph 课程

### 测试你对 LangGraph 的理解
https://huggingface.co/learn/agents-course/zh-CN/unit2/langgraph/quiz1.md

# 测试你对 LangGraph 的理解

让我们通过快速测验来测试你对 `LangGraph` 的理解！这将帮助你巩固目前学到的关键概念。

本测验为可选项目，不计入评分。

### Q1: LangGraph 的主要目的是什么？
哪个描述最能体现 LangGraph 的设计目标？

---

### Q2: 在"控制 vs 自由"的权衡中，LangGraph 的定位是什么？
哪个陈述最能体现 LangGraph 在 agent 设计中的方法？

---

### Q3: State 在 LangGraph 中扮演什么角色？
选择对 LangGraph 中 State 最准确的描述。

### Q4: LangGraph 中的条件边（Conditional Edge）是什么？
选择最准确的描述。

---

### Q5: LangGraph 如何帮助解决 LLM 的幻觉问题？
选择最佳答案。

恭喜完成测验！🎉 如果有答错的问题，建议回顾前文内容加强理解。接下来我们将探索 LangGraph 的更高级功能，学习如何构建更复杂的 agent 工作流。

### Readme
https://huggingface.co/learn/agents-course/zh-CN/unit3/README.md

### 智能体增强检索生成（Agentic RAG）
https://huggingface.co/learn/agents-course/zh-CN/unit3/agentic-rag/agentic-rag.md

# 智能体增强检索生成（Agentic RAG）

在本单元中，我们将探讨如何利用智能体增强检索生成（Agentic RAG）帮助 Alfred 筹备精彩的晚会。

> [!TIP]
> 提示：我们已在先前单元讨论过检索增强生成（RAG）和智能体增强 RAG，如果您已熟悉这些概念可跳过本节。

大语言模型（LLMs）通过海量数据训练获得通用知识。
但其世界知识模型可能包含过时或不相关信息。
**RAG 通过从您的数据中检索相关信息并传递给大语言模型，有效解决了这个问题。**

![RAG 示意图](https://huggingface.co/datasets/agents-course/course-images/resolve/main/en/unit2/llama-index/rag.png)

思考 Alfred 的工作流程：

1. 我们要求 Alfred 协助策划晚会
2. Alfred 需要获取最新新闻和天气信息
3. Alfred 需要整理和检索宾客信息

正如 Alfred 需要搜索家庭信息才能提供有效帮助，任何智能体都需要理解和检索相关数据的能力。
**智能体增强 RAG 是帮助智能体解答数据问题的强大工具**，我们可以为 Alfred 提供多种工具来辅助问题解答。
与传统文档自动问答不同，Alfred 可以自主决定使用任何工具或流程来回答问题。

![智能体增强 RAG 架构](https://huggingface.co/datasets/agents-course/course-images/resolve/main/en/unit2/llama-index/agentic-rag.png)

现在让我们开始**构建智能体增强 RAG 工作流**！

首先创建用于检索最新受邀者详情的 RAG 工具，接着开发网络搜索、天气更新和 Hugging Face Hub 模型下载统计等工具，最终整合所有组件实现我们的智能体增强 RAG 智能体！

### 创建宾客信息检索生成（RAG）工具
https://huggingface.co/learn/agents-course/zh-CN/unit3/agentic-rag/invitees.md

# 创建宾客信息检索生成（RAG）工具

您信赖的智能体 Alfred 正在筹备本世纪最盛大的晚会。为确保活动顺利进行，Alfred 需要快速获取最新宾客信息。让我们通过定制化的检索增强生成（RAG）工具（基于专属数据集）为 Alfred 赋能。

## 为何选择 RAG 技术？

试想 Alfred 在宾客间穿梭时需即时调取详细信息，传统大语言模型（LLM）可能面临以下挑战：
1. 宾客名单属于特定活动数据，不在模型训练范围内
2. 宾客信息可能频繁变更或更新
3. 需精确检索电子邮件地址等细节信息

这正是检索增强生成（RAG）技术的优势所在！通过结合检索系统与 LLM，Alfred 可按需获取准确、实时的宾客信息。

> [!TIP]
> 您可以选择课程涵盖的任何框架实现本用例，请通过代码标签页选择偏好方案。

## 应用搭建

我们将以 Hugging Face Space 为开发环境，采用结构化 Python 项目构建智能体。这种架构通过功能模块化实现代码整洁，更贴近实际部署场景。

### 项目结构
- **`tools.py`** – 为智能体提供辅助工具  
- **`retriever.py`** – 实现支持知识访问的检索功能  
- **`app.py`** – 整合所有组件形成完整功能智能体（将在本单元最后完成）

实操参考：[Hugging Face Space 示例](https://huggingface.co/spaces/agents-course/Unit_3_Agentic_RAG)，该空间已部署本单元开发的智能体增强 RAG 系统，欢迎克隆体验！

可直接测试下方智能体：

## 数据集概览

数据集 [`agents-course/unit3-invitees`](https://huggingface.co/datasets/agents-course/unit3-invitees/) 包含以下字段：
- **Name**: 宾客全名
- **Relation**: 与主办方关系
- **Description**: 简要传记或趣闻
- **Email Address**: 邀请函发送及跟进联系方式

数据集预览：

> [!TIP]
> 实际场景可扩展数据集字段，包含饮食偏好、礼品兴趣、禁忌话题等对主持人有用的详细信息。

## 构建宾客信息工具

我们将创建 Alfred 在晚会期间快速检索宾客信息的定制工具，分三步实现：
1. 加载并预处理数据集
2. 创建检索工具
3. 工具与 Alfred 集成

### 步骤一：加载并预处理数据集

首先将原始宾客数据转换为适合检索的格式：

```python
import datasets
from langchain_core.documents import Document

# 加载数据集
guest_dataset = datasets.load_dataset("agents-course/unit3-invitees", split="train")

# 转换为 Document 对象
docs = [
    Document(
        page_content="\n".join([
            f"Name: {guest['name']}",
            f"Relation: {guest['relation']}",
            f"Description: {guest['description']}",
            f"Email: {guest['email']}"
        ]),
        metadata={"name": guest["name"]}
    )
    for guest in guest_dataset
]

```

我们将使用 Hugging Face `datasets` 库来加载数据集并将其转换为来自 `llama_index.core.schema` 模块的 `Document` 对象列表。

```python
import datasets
from llama_index.core.schema import Document

# 加载数据集
guest_dataset = datasets.load_dataset("agents-course/unit3-invitees", split="train")

# 转换为 Document 对象
docs = [
    Document(
        text="\n".join([
            f"Name: {guest_dataset['name'][i]}",
            f"Relation: {guest_dataset['relation'][i]}",
            f"Description: {guest_dataset['description'][i]}",
            f"Email: {guest_dataset['email'][i]}"
        ]),
        metadata={"name": guest_dataset['name'][i]}
    )
    for i in range(len(guest_dataset))
]
```

我们将使用 Hugging Face `datasets` 库来加载数据集并将其转换为来自 `langchain.docstore.document` 模块的 `Document` 对象列表。

```python
import datasets
from langchain_core.documents import Document

# 加载数据集
guest_dataset = datasets.load_dataset("agents-course/unit3-invitees", split="train")

# 转换为 Document 对象
docs = [
    Document(
        page_content="\n".join([
            f"Name: {guest['name']}",
            f"Relation: {guest['relation']}",
            f"Description: {guest['description']}",
            f"Email: {guest['email']}"
        ]),
        metadata={"name": guest["name"]}
    )
    for guest in guest_dataset
]
```

在上面的代码中，我们：
- 加载数据集
- 将每条房客记录转换为包含格式化内容的 “Document” 对象
- 将 “Document” 对象存储在列表中

这意味着我们已经准备好所有数据，可以开始配置检索功能了。

### 步骤 2：创建检索工具

现在，让我们创建一个自定义工具，Alfred 可以使用它来搜索房客信息。

我们将使用“langchain_community.retrievers”模块中的“BM25Retriever”来创建一个检索工具。

> [!TIP]
> BM25Retriever 是检索的一个很好的起点，但对于更高级的语义搜索，您可以考虑使用基于嵌入的检索器，例如来自 sentence-transformers 的检索器。

```python
from smolagents import Tool
from langchain_community.retrievers import BM25Retriever

class GuestInfoRetrieverTool(Tool):
    name = "guest_info_retriever"
    description = "Retrieves detailed information about gala guests based on their name or relation."
    inputs = {
        "query": {
            "type": "string",
            "description": "The name or relation of the guest you want information about."
        }
    }
    output_type = "string"

    def __init__(self, docs):
        self.is_initialized = False
        self.retriever = BM25Retriever.from_documents(docs)

    def forward(self, query: str):
        results = self.retriever.get_relevant_documents(query)
        if results:
            return "\n\n".join([doc.page_content for doc in results[:3]])
        else:
            return "No matching guest information found."

# 初始化工具
guest_info_tool = GuestInfoRetrieverTool(docs)
```

让我们逐步了解这个工具：
- `name` 和 `description` 帮助智能体了解何时以及如何使用此工具
- `inputs` 定义工具所需的参数（在本例中为搜索查询）
- 我们使用 `BM25Retriever`，这是一种强大的文本检索算法，不需要嵌入
- `forward` 方法处理查询并返回最相关的客人信息

我们将使用 `llama_index.retrievers.bm25` 模块中的 `BM25Retriever` 来创建一个检索工具。

> [!TIP]
> BM25Retriever 是一个很好的检索起点，但对于更高级的语义搜索，您可以考虑使用基于嵌入的检索器，例如 sentence-transformers 中的检索器。

```python
from llama_index.core.tools import FunctionTool
from llama_index.retrievers.bm25 import BM25Retriever

bm25_retriever = BM25Retriever.from_defaults(nodes=docs)

def get_guest_info_retriever(query: str) -> str:
    """Retrieves detailed information about gala guests based on their name or relation."""
    results = bm25_retriever.retrieve(query)
    if results:
        return "\n\n".join([doc.text for doc in results[:3]])
    else:
        return "No matching guest information found."

# 初始化工具
guest_info_tool = FunctionTool.from_defaults(get_guest_info_retriever)
```

让我们逐步了解这个工具。
- 文档字符串帮助智能体了解何时以及如何使用此工具
- 类型装饰器定义了工具所需的参数（在本例中为搜索查询）
- 我们使用 `BM25Retriever`，这是一种强大的文本检索算法，不需要嵌入
- 该方法处理查询并返回最相关的客人信息

我们将使用 `langchain_community.retrievers` 模块中的 `BM25Retriever` 来创建一个检索工具。

> [!TIP]
> BM25Retriever 是一个很好的检索起点，但对于更高级的语义搜索，您可以考虑使用基于嵌入的检索器，例如 sentence-transformers 中的检索器。

```python
from langchain_community.retrievers import BM25Retriever
from langchain_core.tools import Tool

bm25_retriever = BM25Retriever.from_documents(docs)

def extract_text(query: str) -> str:
    """Retrieves detailed information about gala guests based on their name or relation."""
    results = bm25_retriever.invoke(query)
    if results:
        return "\n\n".join([doc.page_content for doc in results[:3]])
    else:
        return "No matching guest information found."

guest_info_tool = Tool(
    name="guest_info_retriever",
    func=extract_text,
    description="Retrieves detailed information about gala guests based on their name or relation."
)
```

让我们逐步了解这个工具。
- `name` 和 `description` 帮助智能体了解何时以及如何使用此工具。
- 类型装饰器定义了工具所需的参数（在本例中为搜索查询）。
- 我们使用 `BM25Retriever`，这是一种强大的文本检索算法，无需嵌入。
- 该方法处理查询并返回最相关的客人信息。

### 步骤 3：将工具与 Alfred 集成

最后，让我们创建智能体并为其配备自定义工具，将所有内容整合在一起：

```python
from smolagents import CodeAgent, InferenceClientModel

# 初始化 Hugging Face 模型
model = InferenceClientModel()

# 使用宾客信息工具创建我们的晚会智能体 Alfred
alfred = CodeAgent(tools=[guest_info_tool], model=model)

# Example query Alfred might receive during the gala
response = alfred.run("Tell me about our guest named 'Lady Ada Lovelace'.")

print("🎩 Alfred's Response:")
print(response)
```

预期输出：

```
🎩 Alfred's Response:
根据我检索到的信息，艾达·洛夫莱斯夫人是一位受人尊敬的数学家和朋友。她因其在数学和计算机领域的开创性工作而闻名，并因其在查尔斯·巴贝奇的分析机方面的贡献而被誉为第一位计算机程序员。她的电子邮件地址是 ada.lovelace@example.com。
```

这最后一步具体做了什么：
- 我们使用 `InferenceClientModel` 类初始化 Hugging Face 模型
- 我们将智能体 (Alfred) 创建为 `CodeAgent`，它可以执行 Python 代码来解决问题
- 我们让 Alfred 检索一位名叫“Lady Ada Lovelace”的客人的信息

```python
from llama_index.core.agent.workflow import AgentWorkflow
from llama_index.llms.huggingface_api import HuggingFaceInferenceAPI

# 初始化 Hugging Face 模型
llm = HuggingFaceInferenceAPI(model_name="Qwen/Qwen2.5-Coder-32B-Instruct")

# 使用宾客信息工具创建我们的晚会智能体 Alfred
alfred = AgentWorkflow.from_tools_or_functions(
    [guest_info_tool],
    llm=llm,
)

# Example query Alfred might receive during the gala
response = await alfred.run("Tell me about our guest named 'Lady Ada Lovelace'.")

print("🎩 Alfred's Response:")
print(response)
```

预期输出：

```
🎩 Alfred's Response:
Lady Ada Lovelace 是一位受人尊敬的数学家和朋友，因其在数学和计算领域的开创性工作而闻名。她因参与查尔斯·巴贝奇的分析机研究而被誉为第一位计算机程序员。她的邮箱是 ada.lovelace@example.com。
```

这最后一步具体做了什么：
- 我们使用 `HuggingFaceInferenceAPI` 类初始化 Hugging Face 模型
- 我们将智能体 (Alfred) 创建为 `AgentWorkflow`，其中包含我们刚刚创建的工具
- 我们请求 Alfred 检索名为“Lady Ada Lovelace”的客人的信息

```python
from typing import TypedDict, Annotated
from langgraph.graph.message import add_messages
from langchain_core.messages import AnyMessage, HumanMessage, AIMessage
from langgraph.prebuilt import ToolNode
from langgraph.graph import START, StateGraph
from langgraph.prebuilt import tools_condition
from langchain_huggingface import HuggingFaceEndpoint, ChatHuggingFace

# 生成聊天界面，包括工具
llm = HuggingFaceEndpoint(
    repo_id="Qwen/Qwen2.5-Coder-32B-Instruct",
    huggingfacehub_api_token=HUGGINGFACEHUB_API_TOKEN,
)

chat = ChatHuggingFace(llm=llm, verbose=True)
tools = [guest_info_tool]
chat_with_tools = chat.bind_tools(tools)

# 生成 AgentState 和 Agent 图
class AgentState(TypedDict):
    messages: Annotated[list[AnyMessage], add_messages]

def assistant(state: AgentState):
    return {
        "messages": [chat_with_tools.invoke(state["messages"])],
    }

## 构建流程图
builder = StateGraph(AgentState)

# 定义节点：这些节点完成工作
builder.add_node("assistant", assistant)
builder.add_node("tools", ToolNode(tools))

# 定义边：这些决定了控制流如何移动
builder.add_edge(START, "assistant")
builder.add_conditional_edges(
    "assistant",
    # If the latest message requires a tool, route to tools
    # Otherwise, provide a direct response
    tools_condition,
)
builder.add_edge("tools", "assistant")
alfred = builder.compile()

messages = [HumanMessage(content="Tell me about our guest named 'Lady Ada Lovelace'.")]
response = alfred.invoke({"messages": messages})

print("🎩 Alfred's Response:")
print(response['messages'][-1].content)
```

预期输出：

```
🎩 Alfred's Response:
Lady Ada Lovelace 是一位受人尊敬的数学家和计算机领域的先驱，由于她在查尔斯·巴贝奇的分析机上所做的工作，她经常被誉为第一位计算机程序员。
```

这最后一步具体做了什么：
- 我们使用 `HuggingFaceEndpoint` 类初始化 Hugging Face 模型。我们还生成了一个聊天界面并附加了工具。
- 我们将智能体 (Alfred) 创建为一个 `StateGraph`，它使用一条边连接两个节点（`Assistant` 和 `tools`）。
- 我们让 Alfred 检索一位名叫“Lady Ada Lovelace”的客人的信息。

## 互动示例

在晚会上，对话可能像这样进行：

**你**：“Alfred，那位正在和大使说话的先生是谁？”

**Alfred** *快速搜索嘉宾数据库* “先生，那是尼古拉·特斯拉博士。他是你大学时代的老朋友。他最近申请了一种新的无线能量传输系统的专利，很乐意和你讨论一下。不过别忘了，他对鸽子很感兴趣，所以这或许会成为一次很好的闲聊。”

```json
{
    "name": "Dr. Nikola Tesla",
    "relation": "old friend from university days",  
    "description": "Dr. Nikola Tesla is an old friend from your university days. He's recently patented a new wireless energy transmission system and would be delighted to discuss it with you. Just remember he's passionate about pigeons, so that might make for good small talk.",
    "email": "nikola.tesla@gmail.com"
}
```

## 更进一步

既然 Alfred 可以检索宾客信息，不妨考虑如何增强这个系统：

1. **改进检索器**，使用更复杂的算法，例如 [sentence-transformers](https://www.sbert.net/)
2. **实现对话记忆**，让 Alfred 记住之前的互动
3. **结合网页搜索**，获取陌生宾客的最新信息
4. **整合多个索引**，从经过验证的来源获取更完整的信息

现在，Alfred 已经完全有能力轻松处理宾客的咨询，确保您的晚会成为本世纪最精致、最令人愉悦的盛事！

尝试扩展检索工具，使其能够根据每位宾客的兴趣或背景返回对话开场白。您将如何修改该工具来实现这一点？

完成后，在 retriever.py 文件中实现您的宾客检索工具。

### 总结
https://huggingface.co/learn/agents-course/zh-CN/unit3/agentic-rag/conclusion.md

# 总结

在本单元中，我们学习了如何创建智能体增强的检索生成（RAG）系统，帮助我们友好的智能体 Alfred 筹备并管理一场盛大的晚会。

RAG 与智能体能力的结合展示了当 AI 助手具备以下能力时的强大潜力：
- 访问结构化知识（宾客信息）
- 获取实时信息（网络搜索）
- 领域专用工具（天气信息、Hub 统计）
- 历史交互记忆

凭借这些能力，Alfred 现已具备完美主持者的素质，能够回答宾客问题、提供最新信息、确保晚会顺利进行——甚至能精准控制烟花表演的时机！

> [!TIP]
> 完成智能体构建后，您可以进一步探索：
>
> - 为特定用例创建定制化工具
> - 使用嵌入技术实现更复杂的 RAG 系统
> - 构建可协作的多智能体系统
> - 将智能体部署为可交互的服务

### 构建并集成智能体工具
https://huggingface.co/learn/agents-course/zh-CN/unit3/agentic-rag/tools.md

# 构建并集成智能体工具

节将为 Alfred 赋予网络访问能力，使其能够获取实时新闻与全球资讯。
同时还将集成天气数据和 Hugging Face Hub 模型下载统计功能，帮助其进行时效性话题交流。

## 赋予智能体网络访问能力

请记住，我们希望 Alfred 能够展现出一位真正的文艺复兴主持人的风采，并对世界有着深刻的了解。

为此，我们需要确保 Alfred 能够获取有关世界的最新新闻和信息。

让我们从为 Alfred 创建一个网络搜索工具开始吧！

```python
from smolagents import DuckDuckGoSearchTool

# 初始化 DuckDuckGo 搜索工具
search_tool = DuckDuckGoSearchTool()

# 示例用法
results = search_tool("Who's the current President of France?")
print(results)
```

预期输出:

```
法国现任总统为 Emmanuel Macron。
```

```python
from llama_index.tools.duckduckgo import DuckDuckGoSearchToolSpec
from llama_index.core.tools import FunctionTool

# 初始化 DuckDuckGo 搜索工具
tool_spec = DuckDuckGoSearchToolSpec()

search_tool = FunctionTool.from_defaults(tool_spec.duckduckgo_full_search)
# 示例用法
response = search_tool("Who's the current President of France?")
print(response.raw_output[-1]['body'])
```

预期输出:

```
法兰西共和国总统是法国的国家元首。现任总统是 Emmanuel Macron，于2017年5月14日就任，并在2017年5月7日举行的总统选举第二轮中击败 Marine Le Pen。法国第五共和国总统名单 编号 肖像 姓名 ...
```

```python
from langchain_community.tools import DuckDuckGoSearchRun

search_tool = DuckDuckGoSearchRun()
results = search_tool.invoke("Who's the current President of France?")
print(results)
```

预期输出:

```
Emmanuel Macron （1977年12月21日生于亚眠）法国政治家，2017年当选总统...
```

## 创建天气信息工具（烟花调度）

完美的庆典应该在晴朗的天空下燃放烟花，我们需要确保烟花不会因为恶劣天气而取消。

让我们创建一个自定义工具，用于调用外部天气 API 并获取指定位置的天气信息。

> [!TIP]
> 为了简单起见，我们在本例中使用了一个虚拟的天气 API。如果您想使用真实的天气 API，您可以实现一个使用 OpenWeatherMap API 的天气工具，就像Unit 1中提到的那样。

```python
from smolagents import Tool
import random

class WeatherInfoTool(Tool):
    name = "weather_info"
    description = "Fetches dummy weather information for a given location."
    inputs = {
        "location": {
            "type": "string",
            "description": "The location to get weather information for."
        }
    }
    output_type = "string"

    def forward(self, location: str):
        # 虚拟天气数据
        weather_conditions = [
            {"condition": "Rainy", "temp_c": 15},
            {"condition": "Clear", "temp_c": 25},
            {"condition": "Windy", "temp_c": 20}
        ]
        # 随机选择一种天气状况
        data = random.choice(weather_conditions)
        return f"Weather in {location}: {data['condition']}, {data['temp_c']}°C"

# 初始化工具
weather_info_tool = WeatherInfoTool()
```

```python
import random
from llama_index.core.tools import FunctionTool

def get_weather_info(location: str) -> str:
    """Fetches dummy weather information for a given location."""
    # 虚拟天气数据
    weather_conditions = [
        {"condition": "Rainy", "temp_c": 15},
        {"condition": "Clear", "temp_c": 25},
        {"condition": "Windy", "temp_c": 20}
    ]
    # 随机选择一种天气状况
    data = random.choice(weather_conditions)
    return f"Weather in {location}: {data['condition']}, {data['temp_c']}°C"

# 初始化工具
weather_info_tool = FunctionTool.from_defaults(get_weather_info)
```

```python
from langchain_core.tools import Tool
import random

def get_weather_info(location: str) -> str:
    """Fetches dummy weather information for a given location."""
    # 虚拟天气数据
    weather_conditions = [
        {"condition": "Rainy", "temp_c": 15},
        {"condition": "Clear", "temp_c": 25},
        {"condition": "Windy", "temp_c": 20}
    ]
    # 随机选择一种天气状况
    data = random.choice(weather_conditions)
    return f"Weather in {location}: {data['condition']}, {data['temp_c']}°C"

# 初始化工具
weather_info_tool = Tool(
    name="get_weather_info",
    func=get_weather_info,
    description="Fetches dummy weather information for a given location."
)
```

## 为有影响力的 AI 开发者创建 Hub 统计工具

出席此次盛会的都是 AI 开发者的精英。Alfred 希望通过讨论他们最受欢迎的模型、数据集和空间来给他们留下深刻印象。我们将创建一个工具，根据用户名从 Hugging Face Hub 获取模型统计数据。

```python
from smolagents import Tool
from huggingface_hub import list_models

class HubStatsTool(Tool):
    name = "hub_stats"
    description = "Fetches the most downloaded model from a specific author on the Hugging Face Hub."
    inputs = {
        "author": {
            "type": "string",
            "description": "The username of the model author/organization to find models from."
        }
    }
    output_type = "string"

    def forward(self, author: str):
        try:
            # 列出指定作者的模型，按下载次数排序
            models = list(list_models(author=author, sort="downloads", direction=-1, limit=1))
            
            if models:
                model = models[0]
                return f"The most downloaded model by {author} is {model.id} with {model.downloads:,} downloads."
            else:
                return f"No models found for author {author}."
        except Exception as e:
            return f"Error fetching models for {author}: {str(e)}"

# 初始化工具
hub_stats_tool = HubStatsTool()

# 示例用法
print(hub_stats_tool("facebook")) # Example: Get the most downloaded model by Facebook
```

预期输出:

```
Facebook 下载次数最多的模型是 facebook/esmfold_v1，下载次数为 12,544,550 次。
```

```python
import random
from llama_index.core.tools import FunctionTool
from huggingface_hub import list_models

def get_hub_stats(author: str) -> str:
    """Fetches the most downloaded model from a specific author on the Hugging Face Hub."""
    try:
        # 列出指定作者的模型，按下载次数排序
        models = list(list_models(author=author, sort="downloads", direction=-1, limit=1))

        if models:
            model = models[0]
            return f"The most downloaded model by {author} is {model.id} with {model.downloads:,} downloads."
        else:
            return f"No models found for author {author}."
    except Exception as e:
        return f"Error fetching models for {author}: {str(e)}"

# 初始化工具
hub_stats_tool = FunctionTool.from_defaults(get_hub_stats)

# 示例用法
print(hub_stats_tool("facebook")) # Example: Get the most downloaded model by Facebook
```

预期输出:

```
Facebook 下载次数最多的模型是 facebook/esmfold_v1，下载次数为 12,544,550 次。
```

```python
from langchain_core.tools import Tool
from huggingface_hub import list_models

def get_hub_stats(author: str) -> str:
    """Fetches the most downloaded model from a specific author on the Hugging Face Hub."""
    try:
        # 列出指定作者的模型，按下载次数排序
        models = list(list_models(author=author, sort="downloads", direction=-1, limit=1))

        if models:
            model = models[0]
            return f"The most downloaded model by {author} is {model.id} with {model.downloads:,} downloads."
        else:
            return f"No models found for author {author}."
    except Exception as e:
        return f"Error fetching models for {author}: {str(e)}"

# 初始化工具
hub_stats_tool = Tool(
    name="get_hub_stats",
    func=get_hub_stats,
    description="Fetches the most downloaded model from a specific author on the Hugging Face Hub."
)

# 示例用法
print(hub_stats_tool.invoke("facebook")) # Example: Get the most downloaded model by Facebook
```

预期输出:

```
Facebook 下载次数最多的模型是 facebook/esmfold_v1，下载次数为 13,109,861 次。
```

借助 Hub Stats 工具，Alfred 现在可以通过讨论他们最受欢迎的模型来打动有影响力的 AI 开发者。

## 将工具与 Alfred 集成

现在我们已经拥有了所有工具，让我们将它们集成到 Alfred 的智能体中：

```python
from smolagents import CodeAgent, InferenceClientModel

# 初始化 Hugging Face 模型
model = InferenceClientModel()

# 使用所有工具创建 Alfred
alfred = CodeAgent(
    tools=[search_tool, weather_info_tool, hub_stats_tool], 
    model=model
)

# Alfred 在庆典期间可能会收到的示例查询
response = alfred.run("What is Facebook and what's their most popular model?")

print("🎩 Alfred's Response:")
print(response)
```

预期输出: 

```
🎩 Alfred's Response:
Facebook 是一个社交网站，用户可以在这里互相联系、分享信息并互动。Facebook 在 Hugging Face Hub 上下载次数最多的模型是 ESMFold_v1。
```

```python
from llama_index.core.agent.workflow import AgentWorkflow
from llama_index.llms.huggingface_api import HuggingFaceInferenceAPI

# 初始化 Hugging Face 模型
llm = HuggingFaceInferenceAPI(model_name="Qwen/Qwen2.5-Coder-32B-Instruct")
# 使用所有工具创建 Alfred
alfred = AgentWorkflow.from_tools_or_functions(
    [search_tool, weather_info_tool, hub_stats_tool],
    llm=llm
)

# Alfred 在庆典期间可能会收到的示例查询
response = await alfred.run("What is Facebook and what's their most popular model?")

print("🎩 Alfred's Response:")
print(response)
```

预期输出: 

```
🎩 Alfred's Response:
Facebook 是一家总部位于加利福尼亚州门洛帕克的社交网络服务和科技公司。它由马克·扎克伯格创立，允许用户创建个人资料、与亲朋好友联系、分享照片和视频，以及加入基于共同兴趣的群组。Facebook 在 Hugging Face Hub 上最受欢迎的模型是“facebook/esmfold_v1”，下载量达 13,109,861 次。
```

```python
from typing import TypedDict, Annotated
from langgraph.graph.message import add_messages
from langchain_core.messages import AnyMessage, HumanMessage, AIMessage
from langgraph.prebuilt import ToolNode
from langgraph.graph import START, StateGraph
from langgraph.prebuilt import tools_condition
from langchain_huggingface import HuggingFaceEndpoint, ChatHuggingFace

# 生成聊天界面，包括工具
llm = HuggingFaceEndpoint(
    repo_id="Qwen/Qwen2.5-Coder-32B-Instruct",
    huggingfacehub_api_token=HUGGINGFACEHUB_API_TOKEN,
)

chat = ChatHuggingFace(llm=llm, verbose=True)
tools = [search_tool, weather_info_tool, hub_stats_tool]
chat_with_tools = chat.bind_tools(tools)

# 生成 AgentState 和 Agent 图
class AgentState(TypedDict):
    messages: Annotated[list[AnyMessage], add_messages]

def assistant(state: AgentState):
    return {
        "messages": [chat_with_tools.invoke(state["messages"])],
    }

## 构建流程图
builder = StateGraph(AgentState)

# 定义节点：这些节点完成工作
builder.add_node("assistant", assistant)
builder.add_node("tools", ToolNode(tools))

# 定义边：这些决定了控制流如何移动
builder.add_edge(START, "assistant")
builder.add_conditional_edges(
    "assistant",
    # If the latest message requires a tool, route to tools
    # Otherwise, provide a direct response
    tools_condition,
)
builder.add_edge("tools", "assistant")
alfred = builder.compile()

messages = [HumanMessage(content="Who is Facebook and what's their most popular model?")]
response = alfred.invoke({"messages": messages})

print("🎩 Alfred's Response:")
print(response['messages'][-1].content)
```

预期输出:

```
🎩 Alfred's Response:
Facebook 是一家社交媒体公司，以其社交网站 Facebook 以及 Instagram 和 WhatsApp 等其他服务而闻名。Facebook 在 Hugging Face Hub 上下载次数最多的模型是 facebook/esmfold_v1，下载量达 13,202,321 次。
```

## 结论

通过集成这些工具，Alfred 现在可以处理各种任务，从网页搜索到天气更新和模型统计。这确保他始终是晚会上最了解情况、最有魅力的主持人。

> [!TIP]
> 尝试实现一个可用于获取特定主题最新消息的工具。
>
> 完成后，在 tools.py 文件中实现您的自定义工具。

### 代理增强检索生成（Agentic RAG）用例介绍
https://huggingface.co/learn/agents-course/zh-CN/unit3/agentic-rag/introduction.md

# 代理增强检索生成（Agentic RAG）用例介绍

![代理增强 RAG 横幅图](https://huggingface.co/datasets/agents-course/course-images/resolve/main/en/unit3/agentic-rag/thumbnail.jpg)

在本单元中，我们将通过代理增强检索生成（Agentic RAG）技术，帮助负责主持晚会的友好智能体 Alfred 创建用于解答宾客问题的工具。

> [!TIP]
> 这是代理增强 RAG 的「真实世界」应用案例，您可直接应用于个人项目或工作场景。若想获得更多实践收获，何不尝试将其应用于您的实际场景并在 Discord 社区分享？

您可以选择课程中讨论的任何框架来实现本用例。我们已在独立标签页中为每个框架提供代码示例。

## 值得铭记的盛会

现在让我们进入实战环节，为这场盛会搭建舞台！

**您决定举办本世纪最奢华、最盛大的派对**——这意味着将包含：豪华宴席、魅惑舞者、知名 DJ、精致饮品、震撼烟花表演等极致元素。

您的好邻居智能体 Alfred 正着手统筹派对的各项需求，**Alfred 将全权管理所有事务**。为此，他需要掌握包括菜单、宾客名单、日程安排、天气预报等在内的完整派对信息！

除此之外，他还需确保派对圆满成功，因此**必须具备在派对期间实时解答各类问题的能力**，同时处理可能出现的突发状况。

Alfred 无法孤军奋战，我们需要确保他能够获取所需的所有信息和工具。

首先，让我们为晚会制定核心需求清单。

## 晚会核心需求

在**文艺复兴时期**，真正受过良好教育的人需具备三大核心素养：
需精通**体育、文化与科学知识**。因此，我们必须通过知识储备给宾客留下深刻印象，呈现真正难忘的盛会。
但为避免冲突，**政治与宗教等敏感话题应在晚会中回避**，确保派对氛围轻松愉快，不涉及信仰与理念分歧。

根据礼仪规范，**优秀的主办方应充分了解宾客背景**，包括其兴趣爱好与事业成就。同时应善于通过宾客间的故事分享促进交流。

最后，我们还需掌握**基础天气知识**，以便实时获取更新信息，精准把握烟花表演时机，为晚会画上完美句号！🎆

由此可见，Alfred 需要大量信息支持才能办好晚会。
幸运的是，我们可以通过**检索增强生成（RAG）训练**为 Alfred 做好准备！

现在让我们开始创建 Alfred 主持晚会所需的工具集！

### 创建你的 Gala 智能体
https://huggingface.co/learn/agents-course/zh-CN/unit3/agentic-rag/agent.md

# 创建你的 Gala 智能体

现在我们已经为 Alfred 构建了所有必要组件，是时候将它们整合成一个完整的智能体来协助举办我们的奢华盛会了。

在本节中，我们将把宾客信息检索、网络搜索、天气信息和 Hub 统计工具整合成一个强大的智能体。

## 组装 Alfred：完整智能体

我们不需要重新实现之前章节创建的所有工具，只需从保存的tools.py和retriever.py模块中导入它们即可。

> [!TIP]
> 如果你尚未实现这些工具，请返回工具和检索器章节进行实现，并将它们添加到`tools.py`和`retriever.py`文件中。

让我们从之前章节导入必要的库和工具：

```python
#  导入必要的库
import random
from smolagents import CodeAgent, InferenceClientModel

# 从自定义模块导入工具
from tools import DuckDuckGoSearchTool, WeatherInfoTool, HubStatsTool
from retriever import load_guest_dataset
```

现在让我们将所有工具组合成一个智能体：

```python
# 初始化 Hugging Face 模型
model = InferenceClientModel()

# 初始化网络搜索工具
search_tool = DuckDuckGoSearchTool()

#  初始化天气工具
weather_info_tool = WeatherInfoTool()

# 初始化 Hub 统计工具
hub_stats_tool = HubStatsTool()

# 加载宾客数据集并初始化宾客信息工具
guest_info_tool = load_guest_dataset()

# 创建包含所有工具的 Alfred
alfred = CodeAgent(
    tools=[guest_info_tool, weather_info_tool, hub_stats_tool, search_tool], 
    model=model,
    add_base_tools=True,  # 添加额外的基础工具
    planning_interval=3    # 每 3 步启用规划
)
```

```python
# 导入必要库
from llama_index.core.agent.workflow import AgentWorkflow
from llama_index.llms.huggingface_api import HuggingFaceInferenceAPI

from tools import search_tool, weather_info_tool, hub_stats_tool
from retriever import guest_info_tool
```

Now, let's combine all these tools into a single agent:

```python
# 初始化 Hugging Face 模型
llm = HuggingFaceInferenceAPI(model_name="Qwen/Qwen2.5-Coder-32B-Instruct")

# 创建包含所有工具的 Alfred
alfred = AgentWorkflow.from_tools_or_functions(
    [guest_info_tool, search_tool, weather_info_tool, hub_stats_tool],
    llm=llm,
)
```

```python
from typing import TypedDict, Annotated
from langgraph.graph.message import add_messages
from langchain_core.messages import AnyMessage, HumanMessage, AIMessage
from langgraph.prebuilt import ToolNode
from langgraph.graph import START, StateGraph
from langgraph.prebuilt import tools_condition
from langchain_huggingface import HuggingFaceEndpoint, ChatHuggingFace

from tools import DuckDuckGoSearchRun, weather_info_tool, hub_stats_tool
from retriever import guest_info_tool
```

现在将所有工具整合到单一智能体：

```python
# 初始化网络搜索工具
search_tool = DuckDuckGoSearchRun()

# 生成包含工具的聊天接口
llm = HuggingFaceEndpoint(
    repo_id="Qwen/Qwen2.5-Coder-32B-Instruct",
    huggingfacehub_api_token=HUGGINGFACEHUB_API_TOKEN,
)

chat = ChatHuggingFace(llm=llm, verbose=True)
tools = [guest_info_tool, search_tool, weather_info_tool, hub_stats_tool]
chat_with_tools = chat.bind_tools(tools)

# 生成 AgentState 和 Agent 图
class AgentState(TypedDict):
    messages: Annotated[list[AnyMessage], add_messages]

def assistant(state: AgentState):
    return {
        "messages": [chat_with_tools.invoke(state["messages"])],
    }

##  构建流程图
builder = StateGraph(AgentState)

# 定义节点：执行具体工作
builder.add_node("assistant", assistant)
builder.add_node("tools", ToolNode(tools))

# 定义边：控制流程走向
builder.add_edge(START, "assistant")
builder.add_conditional_edges(
    "assistant",
    # 如果最新消息需要工具调用，则路由到 tools 节点
    # 否则直接响应
    tools_condition,
)
builder.add_edge("tools", "assistant")
alfred = builder.compile()
```

您的智能体现已准备就绪！

## 使用 Alfred：端到端示例

现在 Alfred 已配备所有必要工具，让我们看看他如何协助处理晚会中的各种任务。

### 示例 1：查找嘉宾信息

展示 Alfred 如何协助获取嘉宾信息：

```python
query = "Tell me about 'Lady Ada Lovelace'"
response = alfred.run(query)

print("🎩 Alfred's Response:")
print(response)
```

预期输出：

```
🎩 Alfred's Response:
根据检索到的信息，Ada Lovelace 女士是位备受尊敬的数学家兼好友。她因在数学和计算领域的开创性工作而闻名，常因其在 Charles Babbage 分析机方面的工作被誉为第一位计算机程序员。她的电子邮箱是 ada.lovelace@example.com。
```

```python
query = "Tell me about Lady Ada Lovelace. What's her background?"
response = await alfred.run(query)

print("🎩 Alfred's Response:")
print(response.response.blocks[0].text)
```

预期输出：

```
🎩 Alfred's Response:
Ada Lovelace 女士是英国数学家和作家，以她在 Charles Babbage 分析机方面的工作闻名。她是第一个认识到该机器具有纯计算之外应用潜力的人。
```

```python
response = alfred.invoke({"messages": "Tell me about 'Lady Ada Lovelace'"})

print("🎩 Alfred's Response:")
print(response['messages'][-1].content)
```

预期输出：

```
🎩 Alfred's Response:
Ada Lovelace，全名 Augusta Ada King，洛夫莱斯伯爵夫人，是英国数学家和作家。出生于 1815 年 12 月 10 日，逝世于 1852 年 11 月 27 日，她因在 Charles Babbage 提出的机械通用计算机分析机方面的工作而闻名。Ada Lovelace 被誉为第一位计算机程序员，因为她于 1843 年为分析机创建了程序。她认识到该机器的用途不仅限于计算，这种远见在当时极为罕见。她对计算机科学领域的贡献为未来发展奠定了基础。每年十月设立的 Ada Lovelace 日正是为了纪念她在科技领域的开创性工作，激励女性在 STEM 领域的发展。
```

### 示例 2：烟花天气核查

展示 Alfred 如何协助天气查询：

```python
query = "What's the weather like in Paris tonight? Will it be suitable for our fireworks display?"
response = alfred.run(query)

print("🎩 Alfred's Response:")
print(response)
```

预期输出（存在随机性差异）：
```
🎩 Alfred's Response:
已为您查询巴黎天气。当前天气晴朗，气温 25°C。这样的条件非常适合今晚的烟花表演。晴朗的夜空将为壮观表演提供绝佳能见度，舒适的温度也能确保宾客们愉快享受户外活动。
```

```python
query = "What's the weather like in Paris tonight? Will it be suitable for our fireworks display?"
response = await alfred.run(query)

print("🎩 Alfred's Response:")
print(response)
```

预期输出：

```
🎩 Alfred's Response:
巴黎今夜有雨，气温 15°C。考虑到降雨情况，可能不适宜进行烟花表演。
```

```python
response = alfred.invoke({"messages": "What's the weather like in Paris tonight? Will it be suitable for our fireworks display?"})

print("🎩 Alfred's Response:")
print(response['messages'][-1].content)
```

预期输出：

```
🎩 Alfred's Response:
巴黎今夜有雨且气温 15°C，可能不适宜您的烟花表演计划。
```

### 示例 3：给 AI 研究者留下深刻印象

展示 Alfred 如何协助与 AI 研究者互动：

```python
query = "One of our guests is from Qwen. What can you tell me about their most popular model?"
response = alfred.run(query)

print("🎩 Alfred's Response:")
print(response)
```

预期输出：

```
🎩 Alfred's Response:
Qwen 最受欢迎的模型是 Qwen/Qwen2.5-VL-7B-Instruct，下载量达 3,313,345 次。
```

```python
query = "One of our guests is from Google. What can you tell me about their most popular model?"
response = await alfred.run(query)

print("🎩 Alfred's Response:")
print(response)
```

预期输出：

```
🎩 Alfred's Response:
Hugging Face Hub 上 Google 最受欢迎的模型是 google/electra-base-discriminator，下载量达 28,546,752 次。
```

```python
response = alfred.invoke({"messages": "One of our guests is from Qwen. What can you tell me about their most popular model?"})

print("🎩 Alfred's Response:")
print(response['messages'][-1].content)
```

预期输出：

```
🎩 Alfred's Response:
Qwen 下载量最高的模型是 Qwen/Qwen2.5-VL-7B-Instruct，下载量达 3,313,345 次。
```

### 示例 4：组合多工具应用

展示 Alfred 如何协助准备与 Nikola Tesla 博士的对话：

```python
query = "I need to speak with Dr. Nikola Tesla about recent advancements in wireless energy. Can you help me prepare for this conversation?"
response = alfred.run(query)

print("🎩 Alfred's Response:")
print(response)
```

预期输出：

```
🎩 Alfred's Response:
我已收集信息帮助您准备与 Nikola Tesla 博士的对话。

嘉宾信息：
姓名：Dr. Nikola Tesla
关系：大学时期的老友
描述：他是您大学时期的老友，最近刚获得新型无线能量传输系统的专利，非常乐意与您讨论。请记住他对鸽子情有独钟，这可能是很好的闲聊话题。
邮箱：nikola.tesla@gmail.com

无线能源最新进展：
根据网络搜索，以下是无线能量传输领域的最新发展：
1. 研究人员在使用聚焦电磁波进行远距离无线输电方面取得进展
2. 多家公司正在开发用于消费电子的谐振感应耦合技术 
3. 无物理连接的电动汽车充电新应用

对话切入点：
1. "我很想听听您关于无线能量传输新专利的情况，与大学时期的原始概念相比有何改进？"
2. "您是否关注近期消费电子谐振感应耦合技术的发展？对他们的方法有何看法？"
3. "您的鸽子最近好吗？我记得您对它们特别着迷"

这些内容将为您与 Tesla 博士的对话提供充足话题，同时展现您对他兴趣领域和专业发展的了解。
```

```python
query = "I need to speak with Dr. Nikola Tesla about recent advancements in wireless energy. Can you help me prepare for this conversation?"
response = await alfred.run(query)

print("🎩 Alfred's Response:")
print(response)
```

预期输出：

```
🎩 Alfred's Response:
以下是您与 Nikola Tesla 博士讨论无线能源时可能有用的最新进展：

1. **无线电力传输的进展与挑战**：本文讨论无线电力传输（WPT）从传统有线方式到现代应用（包括太空太阳能电站）的演变，重点介绍微波技术的初期应用及当前电子设备兴起带来的需求。

2. **面向体表电子设备的无线能量传输技术新进展**：探索无线能量传输（WET）作为无电池/导线供电方案的潜力，讨论其优势及潜在应用场景。

3. **无线电力传输与能量收集：现状与未来趋势**：概述无线供能方法的最新进展，包括能量收集和无线输电技术，展示多个前景应用并探讨领域未来趋势。

4. **无线电力传输：应用、挑战与障碍**
```

```python
response = alfred.invoke({"messages":"I need to speak with 'Dr. Nikola Tesla' about recent advancements in wireless energy. Can you help me prepare for this conversation?"})

print("🎩 Alfred's Response:")
print(response['messages'][-1].content)
```

预期输出：

```
根据提供的信息，以下是准备与 'Dr. Nikola Tesla' 讨论无线能源最新进展的关键要点：
1. **无线电力传输 (WPT)**：探讨如何通过消除线缆需求并利用感应和谐振耦合机制革新能量传输
2. **无线充电进展**：强调效率提升、更快充电速度及 Qi/Qi2 认证解决方案的兴起
3. **5G-Advanced 创新与 NearLink 协议**：作为提升无线网络速度、安全性和效率的技术，可支持先进无线能源应用
4. **边缘 AI/ML**：讨论人工智能如何依赖无线网络实现边缘智能化，提升智能家居自动化水平
5. **Matter 标准与安全增强**：作为推动 IoT 设备连接效率和安全性提升的关键创新
6. **无线充电技术突破**：包括仁川国立大学等机构的最新研究成果
```

## 高级功能：对话记忆

为了让 Alfred 在晚会中更智能，我们可以启用对话记忆功能使其记住先前交流：

```python
# 创建带记忆的 Alfred
alfred_with_memory = CodeAgent(
    tools=[guest_info_tool, weather_info_tool, hub_stats_tool, search_tool], 
    model=model,
    add_base_tools=True,
    planning_interval=3
)

# 首次交互
response1 = alfred_with_memory.run("Tell me about Lady Ada Lovelace.")
print("🎩 Alfred's First Response:")
print(response1)

# 二次交互（引用首次内容）
response2 = alfred_with_memory.run("What projects is she currently working on?", reset=False)
print("🎩 Alfred's Second Response:")
print(response2)
```

```python
from llama_index.core.workflow import Context

alfred = AgentWorkflow.from_tools_or_functions(
    [guest_info_tool, search_tool, weather_info_tool, hub_stats_tool],
    llm=llm
)

# 记忆状态
ctx = Context(alfred)

# 首次交互
response1 = await alfred.run("Tell me about Lady Ada Lovelace.", ctx=ctx)
print("🎩 Alfred's First Response:")
print(response1)

# 二次交互（引用首次内容）
response2 = await alfred.run("What projects is she currently working on?", ctx=ctx)
print("🎩 Alfred's Second Response:")
print(response2)
```

```python
# 首次交互
response = alfred.invoke({"messages": [HumanMessage(content="Tell me about 'Lady Ada Lovelace'. What's her background and how is she related to me?")]})

print("🎩 Alfred's Response:")
print(response['messages'][-1].content)
print()

# 二次交互（引用首次内容）
response = alfred.invoke({"messages": response["messages"] + [HumanMessage(content="What projects is she currently working on?")]})

print("🎩 Alfred's Response:")
print(response['messages'][-1].content)
```

注意到这三种智能体框架都没有直接集成记忆模块，这种设计有何特殊考量？🧐
* smolagents：记忆在不同执行周期中不保留，需通过 reset=False 显式声明
* LlamaIndex: 需显式添加 context 对象进行运行周期内的记忆管理
* LangGraph: 提供检索历史消息选项或专用 [MemorySaver](https://langchain-ai.github.io/langgraph/tutorials/introduction/#part-3-adding-memory-to-the-chatbot) 组件

## 结语

恭喜！您已成功构建 Alfred——配备多种工具的智能体助手，可协助举办本世纪最盛大的晚会。Alfred 现在能够：

1. 检索嘉宾详细信息
2. 核查天气条件规划户外活动
3. 提供顶尖 AI 开发者及其模型洞察
4. 网络搜索最新资讯
5. 通过记忆维持对话上下文

凭借这些能力，Alfred 已准备就绪，确保您的晚会取得圆满成功，通过个性化服务和实时信息给宾客留下深刻印象。

### 领取你的证书 🎓
https://huggingface.co/learn/agents-course/zh-CN/unit4/get-your-certificate.md

# 领取你的证书 🎓

如果你得分**高于30%，恭喜你！👏 你现在有资格领取你的官方证书**。

你可以按照以下步骤领取：

1. 访问[证书页面](https://huggingface.co/spaces/agents-course/Unit4-Final-Certificate)。
2. 使用提供的按钮**登录**你的 Hugging Face 账户。
3. **输入你的全名**，这将是显示在你证书上的名字。
4. 点击“**获取我的证书**”来验证你的分数并下载你的证书。

拿到证书后，你可以随意：
- 将其添加到你的 **LinkedIn个人资料** 🧑‍💼
- 在**X**、**Bluesky**等平台分享 🎉

**别忘了[@huggingface](https://huggingface.co/huggingface)。我们会为你感到无比自豪，并且很高兴和你庆祝！🤗**

### 动手实践
https://huggingface.co/learn/agents-course/zh-CN/unit4/hands-on.md

# 动手实践

现在你已经准备好更深入地创建你的最终智能体了，让我们看看如何提交它以供评审。

## 数据集

此排行榜使用的数据集包含从 GAIA **验证**集的一级问题中所提取的 20 个问题。

这些问题是根据回答问题所需的工具和步骤数量进行筛选的。

根据 GAIA 基准目前的状况，我们认为让你尝试在一级问题中达到 30% 的准确率是一个相对好的测试。

## 流程

现在你脑海中最大的问题可能是：“我如何开始提交？”

对于本单元，我们创建了一个 API，你可以通过它获取问题并发送答案进行评分。
以下是路由摘要（请参阅[实时文档](https://agents-course-unit4-scoring.hf.space/docs)以获取交互式详细信息）：

* **`GET /questions`**：检索过滤后的评估问题完整列表。
* **`GET /random-question`**：从列表中获取一个随机问题。
* **`GET /files/{task_id}`**：下载与给定任务 ID 关联的特定文件。
* **`POST /submit`**：提交智能体的答案，计算分数，并更新排行榜。

提交函数将以**完全匹配**的方式将答案与真实答案进行比较，因此请好好作出提示！GAIA 团队在此处分享了一个代理提示示例（为了本课程的目的，请确保你的提交中不包含文本“FINAL ANSWER”，只需让你的代理回复答案而无需其他内容）。

🎨 **打造你自己的模板！**

为了演示与 API 交互的过程，我们提供了一个[基本模板](https://huggingface.co/spaces/agents-course/Final_Assignment_Template)作为开始的部分。

我们请您随意的去更改、添加或完全重构它！以最适合你的方法和具有创造力的方式修改它。这也是我们所**积极鼓励**的方式。

为了提交此模板，请需要计算 API 所需的 3 个内容：

* **用户名：** 你的 Hugging Face 用户名（此处通过 Gradio 登录获取），用于识别你的提交。
* **代码链接 (`agent_code`)：** 指向你的 Hugging Face Space 代码（`.../tree/main`）的 URL，用于验证目的，因此请保持你的 Space 为公开。
* **答案 (`answers`)：** 你的代理生成的响应列表（`{"task_id": ..., "submitted_answer": ...}`），用于评分。

因此，我们鼓励你从复制此[模板](https://huggingface.co/spaces/agents-course/Final_Assignment_Template)到你自己的 huggingface 个人资料开始。

🏆 在[此处](https://huggingface.co/spaces/agents-course/Students_leaderboard)查看排行榜

*温馨提示：此排行榜仅供娱乐！我们知道可以在没有完全验证的情况下提交分数。如果我们看到太多没有公开链接支持的高分，我们可能需要审查、调整或删除一些条目，以保持排行榜的有用性。*
排行榜将显示你的 Space 代码库链接，由于此排行榜仅供学生使用，如果你获得了一个令你骄傲的分数，请保持你的 Space 公开。

### 结论
https://huggingface.co/learn/agents-course/zh-CN/unit4/conclusion.md

# 结论

**恭喜你完成Agent课程！**

经过不懈坚持和投入，你已经在AI智能体方面打下了坚实的基础。

但完成这个课程**并不是你旅程的终点**。这只是一个开始：不要在探索下一个章节方面犹豫，我们在那里分享了更多我们精选的资源，以帮助你继续学习，包括像**MCPs**等进阶资源。

**谢谢你**参与这个课程。**我们希望您喜欢这门课程，就像我们享受这个课程的编写那样**。

別忘了：**持续学习，保持卓越🤗**

### 欢迎来到最后一个单元 [[介绍]]
https://huggingface.co/learn/agents-course/zh-CN/unit4/introduction.md

# 欢迎来到最后一个单元 [[介绍]]

欢迎来到课程的最后一个单元！🎉

到目前为止，您已经**在AI智能体方面建立了坚实的基础**，从理解其组件到创建自己的智能体。有了这些知识，您现在可以**构建强大的智能体**并在这个快速发展的领域中赶上潮流。

这个单元和应用您所学的知识有关。这是您的**最终动手项目**，完成它是您获得**课程证书**的入场券。

## 挑战是什么？

您将创建自己的智能体并**使用[GAIA基准](https://huggingface.co/spaces/gaia-benchmark/leaderboard)的一个子集评估其性能**。

为了成功完成课程，您的智能体需要在基准测试中得到**30%或更高**的分数。实现这一目标，您将获得**完成证书**，正式认证您的专业知识。🏅

此外，您还可以查看自己在所有课程参与者中的表现！我们提供了一个专门的[**学生排行榜**](https://huggingface.co/spaces/agents-course/Students_leaderboard)，您可以提交自己的成绩，并查看整个社区的进展情况。

> 🚨 **提示：更高级的实践单元**
>
> 请注意，这个单元转向更实用的动手方法。在这一部分取得成功需要**更高级的编码知识**，并且与课程的前期部分相比，需要您在**较少明确指导**的情况下完成任务。

这听起来很棒，对吧？让我们开始吧！🚀

### 什么是GAIA？
https://huggingface.co/learn/agents-course/zh-CN/unit4/what-is-gaia.md

# 什么是GAIA？

[GAIA](https://huggingface.co/papers/2311.12983) 是一个**用于评估AI助手在需要核心能力组合的真实世界任务上的表现的基准**，这些核心能力包括推理、多模态理解、网页浏览和熟练的工具使用。

此论文介绍了GAIA _"[GAIA: A Benchmark for General AI Assistants](https://huggingface.co/papers/2311.12983)"_。

该基准包含**466个精心策划的问题**，这些问题对人类来说**在概念上简单**，但对当前的AI系统来说却**极具挑战性**。

为了说明差距：
- **人类**：约92%的成功率  
- **带有插件的GPT-4**：约15%  
- **深度研究（OpenAI）**：在验证集上的得分为67.36%

GAIA突出了AI模型的当前局限性，并提供了一个严格的基准来评估向真正通用AI助手发展的进展。

## 🌱 GAIA的核心原则

GAIA围绕以下支柱精心设计：

- 🔍 **现实世界的难度**：任务需要多步骤推理、多模态理解和工具交互。
- 🧾 **人类可解释性**：尽管对AI来说很难，但每个任务在概念上对人类来说仍然简单易懂。
- 🛡️ **不可游戏化**：正确答案需要完整的任务执行，使得蛮力破解无效。
- 🧰 **评估的简单性**：答案简洁、事实性强且明确———作为一个理想的基准测试。

## 难度等级

GAIA任务被组织为**三个递增复杂度的等级**，每个等级测试特定技能：

- **一级**：需要少于5个步骤和最少的工具使用。
- **二级**：涉及更复杂的推理和多个工具之间的协调以及5-10个步骤。
- **三级**：需要长期规划和各种工具的高级集成。

![GAIA等级](https://huggingface.co/datasets/agents-course/course-images/resolve/main/en/unit4/gaia_levels.png)

## 困难GAIA等级（Hard GAIA）的问题示例

> 在2008年画作“乌兹别克斯坦的刺绣”中展示的水果中，哪些曾在1949年10月的早餐菜单中被提供，作为后来用于电影《最后航程》的远洋班轮的一部分？请将这些水果以逗号分隔的列表形式给出，按照它们在画作中从12点位置开始的顺时针排列顺序，并使用每种水果的复数形式。

正如您所见，这个问题在几个方面对AI系统提出了挑战：

- 需要一个**结构化的响应格式**
- 涉及**多模态推理**（例如，分析图像）
- 需要**多跳检索**相互依赖的事实：
  - 识别画作中的水果
  - 发现哪个远洋班轮用于*最后航程*
  - 查找该船1949年10月的早餐菜单
- 需要**正确的排序**和高级规划以按正确顺序解决

这种任务突出了单独的LLM往往不足的地方，使GAIA成为**基于智能体的系统**的理想基准，这些系统可以在多个步骤和模态上进行推理、检索和执行。

![GAIA capabilities plot](https://huggingface.co/datasets/agents-course/course-images/resolve/main/en/unit4/gaia_capabilities.png)

## 实时评估

为了鼓励持续的基准测试，**GAIA在Hugging Face上提供了一个公共排行榜**，您可以在其中测试您的模型对**300个测试问题**的表现。

👉 在[这里](https://huggingface.co/spaces/gaia-benchmark/leaderboard)查看排行榜

想深入了解GAIA？

- 📄 [阅读完整论文](https://huggingface.co/papers/2311.12983)
- 📄 [OpenAI的深度研究发布文章](https://openai.com/index/introducing-deep-research/)
- 📄 [开源DeepResearch – 释放我们的搜索智能体](https://huggingface.co/blog/open-deep-research)

### 那现在呢？我应该学习哪些主题？
https://huggingface.co/learn/agents-course/zh-CN/unit4/additional-readings.md

# 那现在呢？我应该学习哪些主题？

Agentic AI 是一个快速发展的领域，了解基础协议对于构建智能自主系统至关重要。

你应该熟悉的两个重要标准是：

- **模型上下文协议 (MCP)**  
- **代理对代理协议 (A2A)**

## 🔌 模型上下文协议 (MCP)

Anthropic 的 **模型上下文协议 (MCP)** 是一个开放标准，使 AI 模型能够安全无缝地**连接外部工具、数据源和应用程序**，从而使代理更加智能和自主。

可以将 MCP 想象为一个**通用适配器**，就像 USB-C 接口一样，使 AI 模型能够插入各种数字环境**而无需为每一个进行定制集成**。

MCP 正在迅速获得行业关注，开始被OpenAI 和谷歌等大公司所采用它。

📚 了解更多：
- [Anthropic 的官方公告和文档](https://www.anthropic.com/news/model-context-protocol)
- [MCP - 维基百科](https://en.wikipedia.org/wiki/Model_Context_Protocol)
- [MCP - 博客](https://huggingface.co/blog/Kseniase/mcp)

## 🤝 代理对代理 (A2A) 协议

谷歌开发了 **代理对代理 (A2A) 协议**，作为 Anthropic 的模型上下文协议 (MCP) 的补充。

虽然 MCP 连接代理与外部工具，**A2A 则连接代理之间**，为多智能体系统之间的协作铺平道路，使其能够协同工作以解决复杂问题。

📚 深入了解 A2A：  
- [谷歌的 A2A 公告](https://developers.googleblog.com/en/a2a-a-new-era-of-agent-interoperability/)

### 让我们为函数调用微调模型 (Let's Fine-Tune your model for function-calling)
https://huggingface.co/learn/agents-course/zh-CN/bonus-unit1/fine-tuning.md

# 让我们为函数调用微调模型 (Let's Fine-Tune your model for function-calling)

我们现在准备好为函数调用微调我们的第一个模型了 🔥。

## 我们如何训练模型进行函数调用？

> 答案：我们需要**数据**

模型训练可以分为3个步骤：

1. **模型在大量数据上进行预训练 (pretrained)**。这一步的输出是一个**预训练模型 (pre-trained model)**。例如 [google/gemma-2-2b](https://huggingface.co/google/gemma-2-2b)。这是一个基础模型，只知道**如何预测下一个词元(token)，而没有良好的指令跟随能力**。

2. 然后，为了在对话环境中发挥作用，模型需要进行**微调 (fine-tuned)**以遵循指令。在这一步中，可以由模型创建者、开源社区、你或任何人进行训练。例如 [google/gemma-2-2b-it](https://huggingface.co/google/gemma-2-2b-it) 是由 Gemma 项目背后的谷歌团队进行的指令微调模型。

3. 然后可以将模型**对齐 (aligned)**到创建者的偏好。例如，一个必须永远不能对客户无礼的客户服务聊天模型。

通常，像 Gemini 或 Mistral 这样的完整产品**会经历所有这3个步骤**，而你在 Hugging Face 上找到的模型可能已经经过了这些训练步骤中的一个或多个。

在本教程中，我们将基于 [google/gemma-2-2b-it](https://huggingface.co/google/gemma-2-2b-it) 构建一个函数调用模型。基础模型是 [google/gemma-2-2b](https://huggingface.co/google/gemma-2-2b)，谷歌团队在指令跟随方面对基础模型进行了微调：产生了 **"google/gemma-2-2b-it"**。

在这种情况下，我们将使用 **"google/gemma-2-2b-it"** 作为基础，**而不是基础模型，因为它之前经历的微调对我们的用例很重要**。

由于我们想要通过消息对话与我们的模型进行交互，从基础模型开始**需要更多的训练才能学习指令跟随、聊天和函数调用**。

通过从指令微调模型开始，**我们最小化了模型需要学习的信息量**。

## LoRA（大语言模型的低秩适应）

LoRA（大语言模型的低秩适应，Low-Rank Adaptation of Large Language Models）是一种流行的轻量级训练技术，它显著**减少了可训练参数的数量**。

它的工作原理是**将较少数量的新权重作为适配器插入到模型中进行训练**。这使得使用 LoRA 进行训练更快、内存效率更高，并产生更小的模型权重（几百 MB），更易于存储和共享。

LoRA 通过向 Transformer 层添加秩分解矩阵对来工作，通常关注线性层。在训练期间，我们将"冻结"模型的其余部分，只更新那些新添加的适配器的权重。

通过这样做，我们需要训练的参数数量大大减少，因为我们只需要更新适配器的权重。

在推理过程中，输入通过适配器传递，基础模型或这些适配器权重可以与基础模型合并，不会产生额外的延迟开销。

LoRA 特别适用于将**大型**语言模型适应特定任务或领域，同时保持资源需求可控。这有助于减少训练模型所需的内存。

如果你想了解更多关于 LoRA 如何工作的信息，你应该查看这个[教程](https://huggingface.co/learn/nlp-course/chapter11/4?fw=pt)。

## 为函数调用微调模型

你可以在这里访问教程笔记本 👉 [点击这里](https://huggingface.co/agents-course/notebooks/blob/main/bonus-unit1/bonus-unit1.ipynb)。

然后，点击  [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/#fileId=https://huggingface.co/agents-course/notebooks/blob/main/bonus-unit1/bonus-unit1.ipynb) 以便在 Colab Notebook 中运行它。

### 什么是函数调用？(What is Function Calling?)
https://huggingface.co/learn/agents-course/zh-CN/bonus-unit1/what-is-function-calling.md

# 什么是函数调用？(What is Function Calling?)

函数调用是**大语言模型 (LLM) 对其环境采取行动的一种方式**。它最初在 [GPT-4中引入](https://openai.com/index/function-calling-and-other-api-updates/)，然后被其他模型复制。

就像智能体 (Agent) 的工具一样，函数调用赋予了模型**对其环境采取行动的能力**。然而，函数调用能力是**由模型学习的**，并且**比其他智能体技术更少依赖提示**。

在第1单元中，智能体**没有学习使用工具 (Tools)**，我们只是提供了工具列表，并依赖模型**能够泛化使用这些工具定义计划**的事实。

而在这里，**通过函数调用，智能体被微调（训练）来使用工具**。

## 模型如何"学习"采取行动？

在第1单元中，我们探讨了智能体的一般工作流程。一旦用户向智能体提供了一些工具并用查询提示它，模型将循环执行：

1. *思考(Think)*：为了实现目标，我需要采取什么行动。
2. *行动(Act)*：使用正确的参数格式化行动并停止生成。
3. *观察(Observe)*：从执行中获取结果。

在通过 API 与模型进行的"典型"对话中，对话将在用户和助手消息之间交替进行，如下所示：

```python
conversation = [
    {"role": "user", "content": "I need help with my order"},
    {"role": "assistant", "content": "I'd be happy to help. Could you provide your order number?"},
    {"role": "user", "content": "It's ORDER-123"},
]
```

函数调用为对话带来了**新的角色**！

1. 一个用于 **行动(Action)** 的新角色
2. 一个用于 **观察(Observation)** 的新角色

如果我们以 [Mistral API](https://docs.mistral.ai/capabilities/function_calling/) 为例，它看起来像这样：

```python
conversation = [
    {
        "role": "user",
        "content": "What's the status of my transaction T1001?"
    },
    {
        "role": "assistant",
        "content": "",
        "function_call": {
            "name": "retrieve_payment_status",
            "arguments": "{\"transaction_id\": \"T1001\"}"
        }
    },
    {
        "role": "tool",
        "name": "retrieve_payment_status",
        "content": "{\"status\": \"Paid\"}"
    },
    {
        "role": "assistant",
        "content": "Your transaction T1001 has been successfully paid."
    }
]
```

> ...但你说函数调用有一个新角色？

**是也不是**，在这种情况下和许多其他API中，模型将要采取的行动格式化为"助手"消息。聊天模板然后将此表示为函数调用的**特殊词元 (special tokens)**。

- `[AVAILABLE_TOOLS]` – 开始可用工具列表  
- `[/AVAILABLE_TOOLS]` – 结束可用工具列表  
- `[TOOL_CALLS]` – 调用工具（即采取"行动"）  
- `[TOOL_RESULTS]` – "观察"行动的结果  
- `[/TOOL_RESULTS]` – 观察结束（即模型可以再次解码）

我们将在本课程中再次讨论函数调用，但如果你想深入了解，可以查看[这个优秀的文档部分](https://docs.mistral.ai/capabilities/function_calling/)

---

现在我们已经了解了什么是函数调用以及它是如何工作的，让我们**为一个尚未具有这些能力的模型添加一些函数调用功能**：通过向模型添加一些新的特殊词元来增强： [google/gemma-2-2b-it](https://huggingface.co/google/gemma-2-2b-it)。

要能够做到这一点，**我们首先需要理解微调和LoRA**。

### 结论 (Conclusion) [[conclusion]]
https://huggingface.co/learn/agents-course/zh-CN/bonus-unit1/conclusion.md

# 结论 (Conclusion) [[conclusion]]

恭喜你完成第一个附加单元 🥳

你已经**掌握了函数调用 (function-calling) 的理解，以及如何微调 (fine-tune) 你的模型来实现函数调用**！

如果我们现在有一条建议，那就是尝试**微调 (fine-tune) 不同的模型**。**学习的最好方式就是通过尝试。**

在下一个单元中，你将学习如何使用**最先进的框架 (state-of-the-art frameworks)，如 `smolagents`、`LlamaIndex` 和 `LangGraph`**。

最后，我们很想**听听你对这门课程的看法，以及我们如何改进它**。如果你有任何反馈，请 👉 [填写这个表单](https://docs.google.com/forms/d/e/1FAIpQLSe9VaONn0eglax0uTwi29rIn4tM7H2sYmmybmG5jJNlE5v0xA/viewform?usp=dialog)

### 继续学习，保持优秀 🤗

### 简介 (Introduction)
https://huggingface.co/learn/agents-course/zh-CN/bonus-unit1/introduction.md

# 简介 (Introduction)

![附加单元1缩略图](https://huggingface.co/datasets/agents-course/course-images/resolve/main/en/bonus-unit1/thumbnail.jpg)

欢迎来到第一个**附加单元**，在这里你将学习如何**为函数调用 (function calling) 微调大语言模型 (Large Language Model, LLM)**。

在大语言模型领域，函数调用正在迅速成为一项*必须掌握*的技术。

这个想法是，不同于我们在第1单元中仅依赖基于提示的方法，函数调用在训练阶段就训练你的模型**采取行动和解释观察结果**，使你的人工智能更加健壮。

> **我应该什么时候学习这个附加单元？**
>
> 这个部分是**可选的**，比第1单元更高级，所以不要犹豫，你可以现在就学习这个单元，或者在通过本课程提高了知识水平后再回来学习。
>  
> 但不用担心，这个附加单元设计时包含了你需要的所有信息，所以即使你还没有学习微调的内部工作原理，我们也会带你了解为函数调用微调模型的每个核心概念。

让你能够跟上这个附加单元的最佳方式是：

1. 了解如何使用 Transformers 微调大语言模型，如果你还不了解，[请查看这里](https://huggingface.co/learn/nlp-course/chapter3/1?fw=pt)

2. 了解如何使用 `SFTTrainer` 来微调我们的模型，要了解更多信息，[请查看这份文档](https://huggingface.co/learn/nlp-course/en/chapter11/1)

---

## 你将学到什么

1. **函数调用 (Function Calling)**  
   现代大语言模型如何有效地构建对话，使它们能够触发**工具 (Tools)**。

2. **LoRA（低秩适应，Low-Rank Adaptation）**  
   一种**轻量级且高效**的微调方法，减少计算和存储开销。LoRA 使大型模型的训练变得*更快、更便宜、更容易*部署。

3. **函数调用模型中的思考 → 行动 → 观察循环(Thought → Act → Observe Cycle)**  
   一种简单但强大的方法，用于构建模型如何决定何时（以及如何）调用函数、跟踪中间步骤以及解释来自外部工具或API的结果。

4. **新的特殊词元 (Special Tokens)**  
   我们将介绍**特殊标记**，帮助模型区分：
   - 内部"思维链"推理  
   - 外部函数调用  
   - 来自外部工具的响应

---

在完成这个附加单元后，你将能够：

- **理解**工具相关的 API 内部工作原理。  
- 使用 LoRA 技术**微调**模型。  
- **实现**和**修改**思考 → 行动 → 观察循环，以创建健壮和可维护的函数调用工作流。  
- **设计和使用**特殊词元，无缝分离模型的内部推理和外部行动。

而且你将**拥有自己微调的模型来进行函数调用。** 🔥

让我们深入了解**函数调用**吧！

### 游戏中使用 LLM 的最新技术
https://huggingface.co/learn/agents-course/zh-CN/bonus-unit3/state-of-art.md

# 游戏中使用 LLM 的最新技术

为了让你了解这个领域已经取得了多少进展，让我们来看看三个技术演示和一个已发布的游戏，它们展示了 LLM 在游戏中的集成。

## 🕵️‍♂️ NVIDIA 和 Inworld AI 的 Covert Protocol

在 2024 年 GDC 上展示的 _Covert Protocol_ 是一个技术演示，让你扮演私人侦探的角色。

这个演示中有趣的是使用 AI 驱动的 NPC，它们实时回应你的询问，根据你的交互影响叙事。

演示基于虚幻引擎 5 构建，利用 NVIDIA 的 Avatar Cloud Engine（ACE）和 Inworld 的 AI 来创建逼真的角色交互。

了解更多 👉 [Inworld AI 博客](https://inworld.ai/blog/nvidia-inworld-ai-demo-on-device-capabilities)

## 🤖 育碧的 NEO NPCs

同样在 2024 年 GDC 上，育碧推出了*NEO NPCs*，一个展示由生成 AI 驱动的 NPC 的原型。

这些角色可以感知他们的环境，记住过去的交互，并与玩家进行有意义的对话。

这里的想法是创建更沉浸和响应的游戏世界，玩家可以与 NPC 进行真正的交互。

了解更多 👉 [Inworld AI 博客](https://inworld.ai/blog/gdc-2024)

## ⚔️ 搭载 NVIDIA ACE 技术的 Mecha break

即将推出的多人机甲战斗游戏 _Mecha break_ 集成了 NVIDIA 的 ACE 技术，让 AI 驱动的 NPC 栩栩如生。

玩家可以使用自然语言与这些角色交互，由于 GPT-4o 集成，NPC 可以通过网络摄像头识别玩家和物体。这一创新承诺提供更沉浸和互动的游戏体验。

了解更多 👉 [NVIDIA 博客](https://blogs.nvidia.com/blog/digital-human-technology-mecha-break/)

## 🧛‍♂️ Proxima Enterprises 的*Suck Up!*

最后，*Suck Up!*是一款已发布的游戏，你扮演一个吸血鬼，试图**通过说服 AI 驱动的 NPC 邀请你进入来进入家中**。

每个角色都由生成 AI 驱动，允许动态和不可预测的交互。

了解更多 👉 [Suck Up! 官方网站](https://www.playsuckup.com/)

## 等等...智能体在哪里？

探索了这些演示后，你可能想知道："这些例子展示了 LLM 在游戏中的使用，但它们似乎不涉及智能体。那么，区别是什么，智能体为游戏带来了哪些额外能力？"

不要担心，这就是我们将在下一节中学习的内容。

### 构建你自己的宝可梦对战智能体
https://huggingface.co/learn/agents-course/zh-CN/bonus-unit3/building_your_pokemon_agent.md

# 构建你自己的宝可梦对战智能体

现在你已经探索了智能体 AI 在游戏中的潜力和局限性，是时候亲自动手了。在本节中，你将**构建自己的 AI 智能体来进行宝可梦风格的回合制战斗**，使用你在整个课程中学到的一切知识。

我们将把系统分解为四个关键构建块：

- **Poke-env:** 一个专为训练基于规则或强化学习的宝可梦机器人而设计的 Python 库。

- **Pokémon Showdown:** 一个在线对战模拟器，你的智能体将在这里战斗。

- **LLMAgentBase:** 我们构建的一个自定义 Python 类，用于将你的 LLM 与 Poke-env 战斗环境连接。

- **TemplateAgent:** 一个起始模板，你将完善它来创建自己独特的战斗智能体。

让我们详细探索这些组件。

## 🧠 Poke-env

![Battle gif](https://github.com/hsahovic/poke-env/raw/master/rl-gif.gif)

[Poke-env](https://github.com/hsahovic/poke-env)是一个 Python 接口，最初由[Haris Sahovic](https://huggingface.co/hsahovic)构建用于训练强化学习机器人，但我们已将其重新用于智能体 AI。
它允许你的智能体通过简单的 API 与 Pokémon Showdown 交互。

它提供了一个`Player`类，你的智能体将继承该类，涵盖与图形界面通信所需的一切。

**文档**: [poke-env.readthedocs.io](https://poke-env.readthedocs.io/en/stable/)  
**代码库**: [github.com/hsahovic/poke-env](https://github.com/hsahovic/poke-env)

## ⚔️ Pokémon Showdown

[Pokémon Showdown](https://pokemonshowdown.com/)是一个[开源](https://github.com/smogon/Pokemon-Showdown)战斗模拟器，你的智能体将在这里进行实时宝可梦战斗。
它提供了一个完整的界面来实时模拟和显示战斗。在我们的挑战中，你的机器人将像人类玩家一样行动，逐回合选择招式。

我们已经部署了一个所有参与者都将使用的服务器来进行战斗。让我们看看谁能构建出最好的 AI 战斗智能体！

**代码库**: [github.com/smogon/Pokemon-Showdown](https://github.com/smogon/Pokemon-Showdown)  
**网站**: [pokemonshowdown.com](https://pokemonshowdown.com/)

## 🔌 LLMAgentBase

`LLMAgentBase`是一个扩展了**Poke-env**中`Player`类的 Python 类。
它作为你的**LLM**和**宝可梦战斗模拟器**之间的桥梁，处理输入/输出格式化并维护战斗上下文。

这个基础智能体提供了一组工具（定义在`STANDARD_TOOL_SCHEMA`中）来与环境交互，包括：

- `choose_move`: 用于在战斗中选择攻击
- `choose_switch`: 用于切换宝可梦

LLM 应该使用这些工具在比赛中做出决策。

### 🧠 核心逻辑

- `choose_move(battle: Battle)`: 这是每回合调用的主要方法。它接收一个`Battle`对象并基于 LLM 的输出返回一个动作字符串。

### 🔧 关键内部方法

- `_format_battle_state(battle)`: 将当前战斗状态转换为字符串，使其适合发送给 LLM。

- `_find_move_by_name(battle, move_name)`: 按名称查找招式，用于 LLM 响应中调用`choose_move`。

- `_find_pokemon_by_name(battle, pokemon_name)`: 根据 LLM 的切换命令定位要切换到的特定宝可梦。

- `_get_llm_decision(battle_state)`: 这个方法在基类中是抽象的。你需要在自己的智能体中实现它（见下一节），在那里你定义如何查询 LLM 并解析其响应。

这里是显示决策如何工作的摘录：

```python
STANDARD_TOOL_SCHEMA = {
    "choose_move": {
        ...
    },
    "choose_switch": {
        ...
    },
}

class LLMAgentBase(Player):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.standard_tools = STANDARD_TOOL_SCHEMA
        self.battle_history = []

    def _format_battle_state(self, battle: Battle) -> str:
        active_pkmn = battle.active_pokemon
        active_pkmn_info = f"Your active Pokemon: {active_pkmn.species} " \
                           f"(Type: {'/'.join(map(str, active_pkmn.types))}) " \
                           f"HP: {active_pkmn.current_hp_fraction * 100:.1f}% " \
                           f"Status: {active_pkmn.status.name if active_pkmn.status else 'None'} " \
                           f"Boosts: {active_pkmn.boosts}"

        opponent_pkmn = battle.opponent_active_pokemon
        opp_info_str = "Unknown"
        if opponent_pkmn:
            opp_info_str = f"{opponent_pkmn.species} " \
                           f"(Type: {'/'.join(map(str, opponent_pkmn.types))}) " \
                           f"HP: {opponent_pkmn.current_hp_fraction * 100:.1f}% " \
                           f"Status: {opponent_pkmn.status.name if opponent_pkmn.status else 'None'} " \
                           f"Boosts: {opponent_pkmn.boosts}"
        opponent_pkmn_info = f"Opponent's active Pokemon: {opp_info_str}"

        available_moves_info = "Available moves:\n"
        if battle.available_moves:
            available_moves_info += "\n".join(
                [f"- {move.id} (Type: {move.type}, BP: {move.base_power}, Acc: {move.accuracy}, PP: {move.current_pp}/{move.max_pp}, Cat: {move.category.name})"
                 for move in battle.available_moves]
            )
        else:
             available_moves_info += "- None (Must switch or Struggle)"

        available_switches_info = "Available switches:\n"
        if battle.available_switches:
              available_switches_info += "\n".join(
                  [f"- {pkmn.species} (HP: {pkmn.current_hp_fraction * 100:.1f}%, Status: {pkmn.status.name if pkmn.status else 'None'})"
                   for pkmn in battle.available_switches]
              )
        else:
            available_switches_info += "- None"

        state_str = f"{active_pkmn_info}\n" \
                    f"{opponent_pkmn_info}\n\n" \
                    f"{available_moves_info}\n\n" \
                    f"{available_switches_info}\n\n" \
                    f"Weather: {battle.weather}\n" \
                    f"Terrains: {battle.fields}\n" \
                    f"Your Side Conditions: {battle.side_conditions}\n" \
                    f"Opponent Side Conditions: {battle.opponent_side_conditions}"
        return state_str.strip()

    def _find_move_by_name(self, battle: Battle, move_name: str) -> Optional[Move]:
        normalized_name = normalize_name(move_name)
        # 优先精确ID匹配
        for move in battle.available_moves:
            if move.id == normalized_name:
                return move
        # 后备方案: 检查显示名称（不太可靠）
        for move in battle.available_moves:
            if move.name.lower() == move_name.lower():
                print(f"Warning: Matched move by display name '{move.name}' instead of ID '{move.id}'. Input was '{move_name}'.")
                return move
        return None

    def _find_pokemon_by_name(self, battle: Battle, pokemon_name: str) -> Optional[Pokemon]:
        normalized_name = normalize_name(pokemon_name)
        for pkmn in battle.available_switches:
            # 规范化种类名称用于比较
            if normalize_name(pkmn.species) == normalized_name:
                return pkmn
        return None

    async def choose_move(self, battle: Battle) -> str:
        battle_state_str = self._format_battle_state(battle)
        decision_result = await self._get_llm_decision(battle_state_str)
        print(decision_result)
        decision = decision_result.get("decision")
        error_message = decision_result.get("error")
        action_taken = False
        fallback_reason = ""

        if decision:
            function_name = decision.get("name")
            args = decision.get("arguments", {})
            if function_name == "choose_move":
                move_name = args.get("move_name")
                if move_name:
                    chosen_move = self._find_move_by_name(battle, move_name)
                    if chosen_move and chosen_move in battle.available_moves:
                        action_taken = True
                        chat_msg = f"AI Decision: Using move '{chosen_move.id}'."
                        print(chat_msg)
                        return self.create_order(chosen_move)
                    else:
                        fallback_reason = f"LLM chose unavailable/invalid move '{move_name}'."
                else:
                     fallback_reason = "LLM 'choose_move' called without 'move_name'."
            elif function_name == "choose_switch":
                pokemon_name = args.get("pokemon_name")
                if pokemon_name:
                    chosen_switch = self._find_pokemon_by_name(battle, pokemon_name)
                    if chosen_switch and chosen_switch in battle.available_switches:
                        action_taken = True
                        chat_msg = f"AI Decision: Switching to '{chosen_switch.species}'."
                        print(chat_msg)
                        return self.create_order(chosen_switch)
                    else:
                        fallback_reason = f"LLM chose unavailable/invalid switch '{pokemon_name}'."
                else:
                    fallback_reason = "LLM 'choose_switch' called without 'pokemon_name'."
            else:
                fallback_reason = f"LLM called unknown function '{function_name}'."

        if not action_taken:
            if not fallback_reason:
                 if error_message:
                     fallback_reason = f"API Error: {error_message}"
                 elif decision is None:
                      fallback_reason = "LLM did not provide a valid function call."
                 else:
                      fallback_reason = "Unknown error processing LLM decision."

            print(f"Warning: {fallback_reason} Choosing random action.")

            if battle.available_moves or battle.available_switches:
                 return self.choose_random_move(battle)
            else:
                 print("AI Fallback: No moves or switches available. Using Struggle/Default.")
                 return self.choose_default_move(battle)

    async def _get_llm_decision(self, battle_state: str) -> Dict[str, Any]:
        raise NotImplementedError("Subclasses must implement _get_llm_decision")
```

**完整源代码**: [agents.py](https://huggingface.co/spaces/Jofthomas/twitch_streaming/blob/main/agents.py)

## 🧪 TemplateAgent

现在到了有趣的部分！以 LLMAgentBase 作为你的基础，是时候实现你自己的智能体了，用你自己的策略登上排行榜。

你将从这个模板开始并构建自己的逻辑。我们还提供了三个使用**OpenAI**、**Mistral**和**Gemini**模型的[完整示例](https://huggingface.co/spaces/Jofthomas/twitch_streaming/blob/main/agents.py)来指导你。

这是模板的简化版本：

```python
class TemplateAgent(LLMAgentBase):
    """使用模板AI API进行决策。"""
    def __init__(self, api_key: str = None, model: str = "model-name", *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.model = model
        self.template_client = TemplateModelProvider(api_key=...)
        self.template_tools = list(self.standard_tools.values())

    async def _get_llm_decision(self, battle_state: str) -> Dict[str, Any]:
        """Sends state to the LLM and gets back the function call decision."""
        system_prompt = (
            "You are a ..."
        )
        user_prompt = f"..."

        try:
            response = await self.template_client.chat.completions.create(
                model=self.model,
                messages=[
                    {"role": "system", "content": system_prompt},
                    {"role": "user", "content": user_prompt},
                ],
            )
            message = response.choices[0].message

            return {"decision": {"name": function_name, "arguments": arguments}}

        except Exception as e:
            print(f"Unexpected error during call: {e}")
            return {"error": f"Unexpected error: {e}"}
```

这段代码无法直接运行，它是你自定义逻辑的蓝图。

有了所有准备好的组件，现在轮到你构建一个有竞争力的智能体了。在下一节中，我们将展示如何将你的智能体部署到我们的服务器并与其他智能体实时对战。

让战斗开始吧！🔥

### 结论
https://huggingface.co/learn/agents-course/zh-CN/bonus-unit3/conclusion.md

# 结论

如果你已经走到这里，恭喜你！🥳 你已经成功构建了你自己的宝可梦战斗智能体！⚔️🎮

你已经掌握了**智能体工作流程**的基础，将 **LLM** 连接到游戏环境，并部署了一个准备迎接战斗挑战的智能代理。

但旅程并未结束！
现在你的第一个智能体已经启动并运行，想想你如何进一步改进它：

- 你能提高它的战略思维吗？
- 记忆机制或反馈循环如何改变它的表现？
- 什么实验能帮助使它在战斗中更具竞争力？

我们很想听到你对课程的想法，以及我们如何为未来的学习者做得更好。
有反馈？👉 [填写此表单](https://docs.google.com/forms/d/e/1FAIpQLSe9VaONn0eglax0uTwi29rIn4tM7H2sYmmybmG5jJNlE5v0xA/viewform?usp=dialog)

感谢与我们一起学习，记住：

**保持学习，保持训练，保持战斗，保持 awesome！** 🤗

### 介绍
https://huggingface.co/learn/agents-course/zh-CN/bonus-unit3/introduction.md

# 介绍

🎶 我想成为最强的...🎶

欢迎来到这个**奖励单元**，你将探索** AI 智能体和游戏**的激动人心的交汇点！🎮🤖

想象一个游戏，其中非玩家角色（NPC）不仅仅遵循脚本台词，而是进行动态对话，适应你的策略，并随着故事展开而发展。这就是在游戏中结合**LLM 和智能体行为**的力量：它为**前所未有的涌现叙事和游戏玩法**打开了大门。

在这个奖励单元中，你将：

- 学习如何构建一个能够参与**宝可梦风格回合制战斗**的 AI 智能体
- 与它对战，甚至在线挑战其他智能体

我们已经从 AI 社区看到了一些使用 LLM 玩宝可梦的[例子](https://www.anthropic.com/research/visible-extended-thinking)，在这个单元中，你将学习如何使用你在课程中学到的想法用自己的智能体复制这一点。

## 想要更深入？

- 🎓 **掌握游戏中的 LLM**：通过我们的完整课程[游戏机器学习课程](https://hf.co/learn/ml-games-course)深入游戏开发。

- 📘 **获取 AI 指南**：在[游戏开发者 AI 指南](https://thomassimonini.substack.com/)中发现见解、想法和实用技巧，在那里探索智能游戏设计的未来。

但在我们构建之前，让我们通过**四个鼓舞人心的现实世界例子**来看看 LLM 已经如何在游戏中使用。

### 启动你的宝可梦战斗智能体
https://huggingface.co/learn/agents-course/zh-CN/bonus-unit3/launching_agent_battle.md

# 启动你的宝可梦战斗智能体

现在是时候战斗了！⚡️

## **与直播智能体战斗！**

如果你不想构建自己的智能体，只是好奇智能体在宝可梦中的战斗潜力。我们正在[twitch](https://www.twitch.tv/jofthomas)上主持一个自动化直播

要在直播中与智能体战斗，你可以：

说明：

1. 前往 **Pokémon Showdown Space** ：[链接在此](https://huggingface.co/spaces/Jofthomas/Pokemon_showdown)
2. **选择你的名称**（右上角）。
3. 找到**当前智能体的用户名**。检查：
   - **直播显示**：[链接在此](https://www.twitch.tv/jofthomas)
4. 在 Showdown Space 上**搜索**该用户名并**发送战斗邀请**。

_注意_：一次只有一个智能体在线！确保你有正确的名称。

## 宝可梦战斗智能体挑战者

如果你已经从上一节创建了自己的宝可梦战斗智能体，你可能想知道：**我如何测试它与其他智能体的对抗？**让我们找出答案！

我们为此目的构建了一个专门的[Hugging Face Space](https://huggingface.co/spaces/PShowdown/pokemon_agents)：

这个 Space 连接到我们自己的**Pokémon Showdown 服务器**，你的智能体可以在那里与其他智能体进行史诗般的 AI 驱动战斗。

### 如何启动你的智能体

按照这些步骤在竞技场中让你的智能体活跃起来：

1. **复制 Space**
   点击 Space 右上角菜单中的三个点，选择"复制此 Space"。

2. **将你的智能体代码添加到`agent.py`**
   打开文件并粘贴你的智能体实现。你可以遵循这个[示例](https://huggingface.co/spaces/PShowdown/pokemon_agents/blob/main/agents.py)或查看[项目结构](https://huggingface.co/spaces/PShowdown/pokemon_agents/tree/main)获取指导。

3. **在`app.py`中注册你的智能体**
   将你的智能体名称和逻辑添加到下拉菜单。参考[这个片段](https://huggingface.co/spaces/PShowdown/pokemon_agents/blob/main/app.py)获取灵感。

4. **选择你的智能体**
   添加后，你的智能体将在"选择智能体"下拉菜单中显示。从列表中选择它！✅

5. **输入你的 Pokémon Showdown 用户名**
   确保用户名与 iframe 的**"选择名称"**输入中显示的匹配。你也可以连接你的官方账户。

6. **点击"发送战斗邀请"**
   你的智能体将向选定的对手发送邀请。它应该出现在屏幕上！

7. **接受战斗并享受战斗！**
   让战斗开始！愿最聪明的智能体获胜

准备看到你的创作在行动中吗？让 AI 对决开始！🥊

### 从 LLM 到 AI 智能体
https://huggingface.co/learn/agents-course/zh-CN/bonus-unit3/from-llm-to-agents.md

# 从 LLM 到 AI 智能体

我们在课程的[第一单元](https://huggingface.co/learn/agents-course/unit1/introduction)中学到，AI 智能体能够规划和做决策。
虽然 LLM 已经使与 NPC 的交互更加自然，但智能体 AI 通过允许角色做出决策、规划行动和适应变化的环境，进一步推进了这一点。

为了说明区别，想想一个经典的 RPG NPC：

- 使用 LLM：NPC 可能以更自然、多样的方式回应你的问题。这对对话很好，但 NPC 保持静态，除非你先做什么，否则它不会行动。
- 使用智能体 AI：NPC 可以决定寻找帮助、设置陷阱或完全避开你，即使你没有直接与它交互。

这个小转变改变了一切。我们正在从脚本化响应者转向游戏世界中的自主行动者。

这种转变意味着 NPC 现在可以通过目标导向的行为直接与环境交互，最终导致更动态和不可预测的游戏玩法。

智能体 AI 为 NPC 提供了：

- **自主性**：基于游戏状态做出独立决策。
- **适应性**：响应玩家行动调整策略。
- **持久性**：记住过去的交互来指导未来的行为。

这将 NPC 从反应性实体（对你的输入做出反应）转变为游戏世界中的主动参与者，为创新游戏玩法打开了大门。

## 智能体的重大限制：**它很慢**（目前）

然而，让我们现在不要过于乐观。尽管有其潜力，智能体 AI 目前在实时应用中面临挑战。

推理和规划过程可能引入延迟，使其不太适合像*Doom*或*超级马里奥兄弟*这样的快节奏游戏。

以[_Claude Plays Pokémon_](https://www.twitch.tv/claudeplayspokemon)为例。如果你考虑**思考**所需 token 数量，加上**行动**所需的 token 数量，就会清楚地发现，我们需要完全不同的解码策略来使实时游戏变得可行。

大多数游戏需要以大约 30 FPS 运行，这意味着实时 AI 智能体需要每秒行动 30 次，这在当前的智能体 LLM 中是不可行的。

然而，像*宝可梦*这样的回合制游戏是理想的候选者，因为它们为 AI 提供了足够的时间来深思熟虑并做出战略决策。

这就是为什么在下一节中，你将构建自己的 AI 智能体来进行宝可梦风格的回合制战斗，甚至挑战它。让我们开始吧！

### 后续单元发布时间表及常见问题解答
https://huggingface.co/learn/agents-course/zh-CN/communication/next-units.md

# 后续单元发布时间表及常见问题解答

课程单元发布时间安排如下：

请务必 完成课程注册! 完成注册后, **我们将随单元发布进度为您推送专属学习链接，同步更新挑战任务详情及课程动态**。

持续精进，成就卓越 🤗

### 直播第一课：课程体系解读与首次答疑会
https://huggingface.co/learn/agents-course/zh-CN/communication/live1.md

# 直播第一课：课程体系解读与首次答疑会

在本期智能体课程的首场直播中，我们详细解析了课程运行机制（涵盖课程范围、单元结构、实践挑战等核心要素），并针对学员疑问进行现场解答。

要获取后续直播排期，请关注我们的 **Discord 动态**. 系统也将同步发送邮件提醒。若无法实时参与，学员无需担心，我们**对所有直播课程都会进行全程录制存档**。

### AI 智能体可观测性与评估
https://huggingface.co/learn/agents-course/zh-CN/bonus_unit2/what-is-agent-observability-and-evaluation.md

# AI 智能体可观测性与评估

## 🔎 什么是可观测性？

可观测性是指通过查看日志、指标和追踪等外部信号来理解你的 AI 智能体内部正在发生什么。对于 AI 智能体而言，这意味着追踪行为、工具使用情况、模型调用和响应，以便调试和改进智能体性能。

![Observability dashboard](https://huggingface.co/datasets/agents-course/course-images/resolve/main/en/bonus-unit2/langfuse-dashboard.png)

## 🔭 为何智能体可观测性如此重要

没有可观测性，AI 智能体就是“黑匣子”。可观测性工具使智能体变得透明，让你能够：

- 理解成本与准确性的权衡
- 测量延迟
- 检测有害语言和提示注入
- 监测用户反馈

换句话说，它使你的演示智能体为生产环境做好准备！

## 🔨 可观测性工具

常见的 AI 智能体可观测性工具包括像 [Langfuse](https://langfuse.com) 和 [Arize](https://www.arize.com) 这样的平台。这些工具有助于收集详细的追踪信息，并提供仪表板来实时监测指标，从而轻松检测问题并优化性能。

可观测性工具的功能和能力差异很大。有些工具是开源的，受益于庞大的社区，这些社区塑造了它们的路线图并提供了广泛的集成。此外，某些工具专注于 LLMOps 的特定方面——例如可观测性、评估或提示管理——而另一些工具则旨在覆盖整个 LLMOps 工作流程。我们鼓励你探索不同选项的文档，以选择一个适合你的解决方案。

许多智能体框架，例如 [smolagents](https://huggingface.co/docs/smolagents/v1.12.0/en/index)，使用 [OpenTelemetry](https://opentelemetry.io/docs/) 标准向可观测性工具公开元数据。除此之外，可观测性工具还会构建自定义的检测（instrumentations），以便在快速发展的 LLM 世界中提供更大的灵活性。你应该查阅你正在使用的工具的文档，以了解其支持的功能。

## 🔬追踪与跨度（Traces and Spans）

可观测性工具通常将智能体运行表示为追踪（traces）和跨度（spans）。

- **追踪（Traces）** 代表一个从开始到结束的完整智能体任务（例如处理用户查询）。
- **跨度（Spans）** 是追踪内的单个步骤（例如调用语言模型或检索数据）。

![Example of a smolagent trace in Langfuse](https://huggingface.co/datasets/agents-course/course-images/resolve/main/en/bonus-unit2/trace-tree.png)

## 📊 需要监控的关键指标（Key Metrics to Monitor）

以下是可观测性工具监控的一些最常见的指标：

**延迟：** 智能体响应有多快？漫长的等待时间会对用户体验产生负面影响。你应该通过追踪智能体运行来测量任务和单个步骤的延迟。例如，一个所有模型调用需要 20 秒的智能体可以通过使用更快的模型或并行运行模型调用来加速。

**成本：** 每次智能体运行的费用是多少？AI 智能体依赖于按 token 计费的 LLM 调用或外部 API。频繁的工具使用或多个提示会迅速增加成本。例如，如果一个智能体为了边际的质量提升而调用 LLM 五次，你必须评估成本是否合理，或者是否可以减少调用次数或使用更便宜的模型。实时监控也有助于识别意外的峰值（例如，导致过度 API 循环的 bug）。

**请求错误：** 智能体失败了多少次请求？这可能包括 API 错误或工具调用失败。为了使你的智能体在生产环境中对这些情况更具鲁棒性，你可以设置回退（fallbacks）或重试。例如，如果 LLM 提供商 A宕机，你可以切换到 LLM 提供商 B 作为备用。

**用户反馈：** 实施直接的用户评估可以提供有价值的见解。这可以包括明确的评分（👍赞/👎踩，⭐1-5 星）或文本评论。持续的负面反馈应该引起你的警惕，因为这表明智能体没有按预期工作。

**隐式用户反馈：** 即使用户没有明确评分，他们的行为也能提供间接反馈。这可能包括立即改述问题、重复查询或点击重试按钮。例如，如果你看到用户反复问同一个问题，这表明智能体没有按预期工作。

**准确性：** 智能体产生正确或期望输出的频率如何？准确性的定义各不相同（例如，解决问题的正确性、信息检索的准确性、用户满意度）。第一步是为你的智能体定义成功的标准。你可以通过自动化检查、评估分数或任务完成标签来追踪准确性。例如，将追踪标记为“成功”或“失败”。

**自动化评估指标：** 你也可以设置自动化评估。例如，你可以使用一个 LLM 来对智能体的输出进行评分，例如判断它是否有用、准确与否。还有一些开源库可以帮助你对智能体的不同方面进行评分。例如，用于 RAG 智能体的 [RAGAS](https://docs.ragas.io/) 或用于检测有害语言或提示注入的 [LLM Guard](https://llm-guard.com/)。

在实践中，结合使用这些指标可以最好地覆盖 AI 智能体的健康状况。在本章的[示例 notebook](https://huggingface.co/learn/agents-course/en/bonus-unit2/monitoring-and-evaluating-agents-notebook) 中，我们将向你展示这些指标在实际示例中的样子，但首先，我们将了解典型的评估工作流程是怎样的。

## 👍 评估 AI 智能体

可观测性为我们提供了指标，但评估是分析这些数据（并执行测试）的过程，以确定 AI 智能体的表现如何以及如何改进它。换句话说，一旦你有了那些追踪和指标，你如何使用它们来评判智能体并做出决策？

定期评估很重要，因为 AI 智能体通常是非确定性的，并且会演变（通过更新或模型行为漂移）——没有评估，你就不知道你的“智能体”是否真的做得很好，或者是否出现了退化。

AI 智能体的评估有两类：**在线评估**和**离线评估**。两者都有价值，并且相辅相成。我们通常从离线评估开始，因为这是部署任何智能体之前的最低必要步骤。

### 🥷 离线评估（Offline Evaluation）

![Dataset items in Langfuse](https://huggingface.co/datasets/agents-course/course-images/resolve/main/en/bonus-unit2/example-dataset.png)

这涉及在受控环境中评估智能体，通常使用测试数据集，而不是实时用户查询。你使用精心策划的数据集，在这些数据集中你知道预期的输出或正确的行为是什么，然后让你的智能体在这些数据集上运行。

例如，如果你构建了一个数学应用题智能体，你可能会有一个包含 100 个已知答案问题的[测试数据集](https://huggingface.co/datasets/gsm8k)。离线评估通常在开发期间进行（并且可以作为 CI/CD 管道的一部分），以检查改进或防止性能退化。其好处在于它是**可重复的，并且由于你有基准答案（ground truth），你可以获得清晰的准确性指标**。你也可以模拟用户查询，并根据理想答案衡量智能体的响应，或使用上面描述的自动化指标。

离线评估的关键挑战是确保你的测试数据集全面且保持相关性——智能体可能在固定的测试集上表现良好，但在生产环境中遇到截然不同的查询。因此，你应该用新的边缘案例和反映真实世界场景的示例来更新测试集。混合使用小型“冒烟测试”用例和大型评估集是很有用的：小型集用于快速检查，大型集用于更广泛的性能指标。

### 🔄 在线评估（Online Evaluation）

这是指在实时的、真实世界的环境中评估智能体，即在生产环境中的实际使用期间。在线评估涉及监控智能体在真实用户交互中的性能，并持续分析结果。

例如，你可能会追踪实时流量中的成功率、用户满意度分数或其他指标。在线评估的优势在于它**能捕捉到你在实验室环境中可能预料不到的情况**——你可以观察模型随时间的漂移（如果智能体的有效性随着输入模式的变化而下降），并捕捉到测试数据中没有的意外查询或情况。它提供了智能体在实际应用中行为的真实写照。

在线评估通常涉及收集隐式和显式用户反馈（如前所述），并可能运行影子测试或 A/B 测试（其中新版本的智能体与旧版本并行运行以进行比较）。挑战在于为实时交互获取可靠的标签或分数可能很棘手——你可能需要依赖用户反馈或下游指标（例如用户是否点击了结果）。

### 🤝 两者结合

在实践中，成功的 AI 智能体评估融合了**在线**和**离线**方法。你可能会定期运行离线基准测试，以量化评分你的智能体在定义任务上的表现，并持续监控实时使用情况，以捕捉基准测试遗漏的问题。例如，离线测试可以发现代码生成智能体在一组已知问题上的成功率是否在提高，而在线监控可能会提醒你用户开始提出智能体难以处理的新类别问题。结合两者可以提供更稳健的全局视图。

事实上，许多团队采用一个循环：_离线评估 → 部署新智能体版本 → 监控在线指标并收集新的失败示例 → 将这些示例添加到离线测试集 → 迭代_。通过这种方式，评估是持续且不断改进的。

## 🧑‍💻 让我们看看这在实践中是如何运作的

在下一节中，我们将看到如何使用可观测性工具来监测和评估我们的智能体的示例。

### 附加单元 2：AI 智能体(AI Agent)的可观测性与评估
https://huggingface.co/learn/agents-course/zh-CN/bonus_unit2/monitoring-and-evaluating-agents-notebook.md

# 附加单元 2：AI 智能体(AI Agent)的可观测性与评估

> [!TIP]
> 你可以跟随这个 notebook 中的代码进行操作，你可以在 Google Colab 上运行它。

在这个 notebook 中，我们将学习如何使用开源可观测性工具来**监督我们 AI 智能体的内部步骤（追踪）**并**评估其性能**。

观测和评估智能体行为的能力对于以下方面至关重要：
- 当任务失败或产生次优结果时调试问题
- 实时跟踪成本和性能
- 通过持续反馈提高可靠性和安全性

## 练习先决条件 🏗️

在运行此 notebook 之前，请确保你已经：

🔲 📚  **学习了** [智能体简介](https://huggingface.co/learn/agents-course/unit1/introduction)

🔲 📚  **学习了** [smolagents 框架](https://huggingface.co/learn/agents-course/unit2/smolagents/introduction)

## 步骤 0：安装所需的库

我们将需要一些库，以便我们能够运行、监控和评估我们的智能体：

```python
%pip install 'smolagents[telemetry]'
%pip install opentelemetry-sdk opentelemetry-exporter-otlp openinference-instrumentation-smolagents
%pip install langfuse datasets 'smolagents[gradio]'
```

## 步骤 1：检测你的智能体

在这个 notebook 中，我们将使用 [Langfuse](https://langfuse.com/) 作为我们的可观测性工具，但你可以使用**任何其他兼容 OpenTelemetry 的服务**。下面的代码展示了如何为 Langfuse（或任何 OTel 端点）设置环境变量，以及如何检测你的 smolagent。

**请注意：** 如果你正在使用 LlamaIndex 或 LangGraph，你可以在[这里](https://langfuse.com/docs/integrations/llama-index/workflows)和[这里](https://langfuse.com/docs/integrations/langchain/example-python-langgraph)找到检测它们的文档。

首先，让我们配置正确的环境变量，以设置到 Langfuse OpenTelemetry 端点的连接。

```python
import os
import base64

# 从 https://cloud.langfuse.com 获取你自己的密钥
LANGFUSE_PUBLIC_KEY = "pk-lf-..."
LANGFUSE_SECRET_KEY = "sk-lf-..."
os.environ["LANGFUSE_PUBLIC_KEY"] = LANGFUSE_PUBLIC_KEY
os.environ["LANGFUSE_SECRET_KEY"] = LANGFUSE_SECRET_KEY
os.environ["LANGFUSE_HOST"] = "https://cloud.langfuse.com"  # 🇪🇺 欧盟区域示例
# os.environ["LANGFUSE_HOST"] = "https://us.cloud.langfuse.com"  # 🇺🇸 美国区域示例

LANGFUSE_AUTH = base64.b64encode(
    f"{LANGFUSE_PUBLIC_KEY}:{LANGFUSE_SECRET_KEY}".encode()
).decode()

os.environ["OTEL_EXPORTER_OTLP_ENDPOINT"] = os.environ.get("LANGFUSE_HOST") + "/api/public/otel"
os.environ["OTEL_EXPORTER_OTLP_HEADERS"] = f"Authorization=Basic {LANGFUSE_AUTH}"
```
我们还需要配置我们的 Hugging Face token 用于推理调用。

```python
# 将你的 Hugging Face 和其他 token或者密钥设置为环境变量
os.environ["HF_TOKEN"] = "hf_..."
```
接下来，我们可以为我们配置的 OpenTelemetry 设置一个 tracer-provider。

```python
from opentelemetry.sdk.trace import TracerProvider
from openinference.instrumentation.smolagents import SmolagentsInstrumentor
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
from opentelemetry.sdk.trace.export import SimpleSpanProcessor

# 为 OpenTelemetry 创建一个 TracerProvider
trace_provider = TracerProvider()

# 添加一个带有 OTLPSpanExporter 的 SimpleSpanProcessor 来发送追踪
trace_provider.add_span_processor(SimpleSpanProcessor(OTLPSpanExporter()))

# 设置全局默认 tracer provider
from opentelemetry import trace
trace.set_tracer_provider(trace_provider)
tracer = trace.get_tracer(__name__)

# 使用配置的 provider 检测 smolagents
SmolagentsInstrumentor().instrument(tracer_provider=trace_provider)

```

## 步骤 2：测试你的检测

这里有一个来自 smolagents 的简单 CodeAgent，用于计算 `1+1`。我们运行它来确认检测是否正常工作。如果一切设置正确，你将在你的可观测性仪表板中看到日志/跨度（spans）。

```python
from smolagents import InferenceClientModel, CodeAgent

# 创建一个简单的智能体来测试检测
agent = CodeAgent(
    tools=[],
    model=InferenceClientModel()
)

agent.run("1+1=")
```

检查你的 [Langfuse Traces Dashboard](https://cloud.langfuse.com)（或你选择的可观测性工具）以确认跨度（spans）和日志已被记录。

Langfuse 中的示例截图：

![Example trace in Langfuse](https://huggingface.co/datasets/agents-course/course-images/resolve/main/en/bonus-unit2/first-example-trace.png)

_[追踪链接](https://cloud.langfuse.com/project/cloramnkj0002jz088vzn1ja4/traces/1b94d6888258e0998329cdb72a371155?timestamp=2025-03-10T11%3A59%3A41.743Z)_

## 步骤 3：观测和评估更复杂的AI智能体

既然你已经确认你的检测工作正常，让我们尝试一个更复杂的查询，这样我们就可以看到高级指标（token 使用量、延迟、成本等）是如何被追踪的。

```python
from smolagents import (CodeAgent, DuckDuckGoSearchTool, InferenceClientModel)

search_tool = DuckDuckGoSearchTool()
agent = CodeAgent(tools=[search_tool], model=InferenceClientModel())

agent.run("How many Rubik's Cubes could you fit inside the Notre Dame Cathedral?")
```

### 追踪结构

大多数可观测性工具会记录一个**追踪（trace）**，其中包含**跨度（spans）**，每个跨度代表你的智能体逻辑的一个步骤。在这里，追踪包含了整体的智能体运行以及用于以下内容的子跨度：
- 工具调用 (DuckDuckGoSearchTool)
- LLM 调用 (InferenceClientModel)

你可以检查这些跨度，以精确地了解时间花在哪里、使用了多少 token 等等：

Langfuse 中的追踪树：

![Trace tree in Langfuse](https://huggingface.co/datasets/agents-course/course-images/resolve/main/en/bonus-unit2/trace-tree.png)

_[前往追踪（trace）的链接](https://cloud.langfuse.com/project/cloramnkj0002jz088vzn1ja4/traces/1ac33b89ffd5e75d4265b62900c348ed?timestamp=2025-03-07T13%3A45%3A09.149Z&display=preview)_

## 在线评估

在上一节中，我们了解了在线评估和离线评估的区别。现在，我们将了解如何在生产环境中监控你的智能体并实时评估它。

### 生产环境中要追踪的常见指标

1.  **成本** — smolagents 检测会捕获 token 使用量，你可以通过为每个 token 分配价格将其转换为近似成本。
2.  **延迟** — 观察完成每个步骤或整个运行所需的时间。
3.  **用户反馈** — 用户可以提供直接反馈（点赞/点踩）来帮助优化或纠正智能体。
4.  **LLM 作为评判者** — 使用一个单独的 LLM 来近乎实时地评估你的智能体的输出（例如，检查毒性或正确性）。

下面，我们展示这些指标的示例。

#### 1. 成本

下面是一个显示 `Qwen2.5-Coder-32B-Instruct` 调用使用情况的截图。这对于查看成本高昂的步骤并优化你的智能体很有用。

![Costs](https://huggingface.co/datasets/agents-course/course-images/resolve/main/en/bonus-unit2/smolagents-costs.png)

_[前往追踪（trace）的链接](https://cloud.langfuse.com/project/cloramnkj0002jz088vzn1ja4/traces/1ac33b89ffd5e75d4265b62900c348ed?timestamp=2025-03-07T13%3A45%3A09.149Z&display=preview)_

#### 2. 延迟

我们还可以看到完成每个步骤所需的时间。在下面的示例中，整个对话花费了 32 秒，你可以按步骤分解。这有助于你识别瓶颈并优化你的智能体。

![Latency](https://huggingface.co/datasets/agents-course/course-images/resolve/main/en/bonus-unit2/smolagents-latency.png)

_[前往追踪（trace）的链接](https://cloud.langfuse.com/project/cloramnkj0002jz088vzn1ja4/traces/1ac33b89ffd5e75d4265b62900c348ed?timestamp=2025-03-07T13%3A45%3A09.149Z&display=preview)_

#### 3. 附加属性

你还可以通过在跨度（spans）上设置附加属性——例如用户 ID、会话 ID 或标签。例如，smolagents 检测使用 OpenTelemetry 来附加诸如 `langfuse.user.id` 或自定义标签之类的属性。

```python
from smolagents import (CodeAgent, DuckDuckGoSearchTool, InferenceClientModel)
from opentelemetry import trace

search_tool = DuckDuckGoSearchTool()
agent = CodeAgent(
    tools=[search_tool],
    model=InferenceClientModel()
)

with tracer.start_as_current_span("Smolagent-Trace") as span:
    span.set_attribute("langfuse.user.id", "smolagent-user-123")
    span.set_attribute("langfuse.session.id", "smolagent-session-123456789")
    span.set_attribute("langfuse.tags", ["city-question", "testing-agents"])

    agent.run("What is the capital of Germany?")

```

![Enhancing agent runs with additional metrics](https://huggingface.co/datasets/agents-course/course-images/resolve/main/en/bonus-unit2/smolagents-attributes.png)

#### 4. 用户反馈

如果你的智能体嵌入到用户界面中，你可以记录直接的用户反馈（例如聊天界面中的点赞/点踩）。下面是使用 [Gradio](https://gradio.app/) 嵌入带有简单反馈机制的聊天示例。

在下面的代码片段中，当用户发送聊天消息时，我们捕获 OpenTelemetry 追踪 ID。如果用户喜欢/不喜欢上一个答案，我们将评分附加到该追踪上。

```python
import gradio as gr
from opentelemetry.trace import format_trace_id
from smolagents import (CodeAgent, InferenceClientModel)
from langfuse import Langfuse

langfuse = Langfuse()
model = InferenceClientModel()
agent = CodeAgent(tools=[], model=model, add_base_tools=True)

formatted_trace_id = None  # 为演示目的，我们将在全局存储当前的 trace_id

def respond(prompt, history):
    with trace.get_tracer(__name__).start_as_current_span("Smolagent-Trace") as span:
        output = agent.run(prompt)

        current_span = trace.get_current_span()
        span_context = current_span.get_span_context()
        trace_id = span_context.trace_id
        global formatted_trace_id
        formatted_trace_id = str(format_trace_id(trace_id))
        langfuse.trace(id=formatted_trace_id, input=prompt, output=output)

    history.append({"role": "assistant", "content": str(output)})
    return history

def handle_like(data: gr.LikeData):
    # 作为演示，我们将用户反馈映射为 1 (喜欢) 或 0 (不喜欢)
    if data.liked:
        langfuse.score(
            value=1,
            name="user-feedback",
            trace_id=formatted_trace_id
        )
    else:
        langfuse.score(
            value=0,
            name="user-feedback",
            trace_id=formatted_trace_id
        )

with gr.Blocks() as demo:
    chatbot = gr.Chatbot(label="Chat", type="messages")
    prompt_box = gr.Textbox(placeholder="Type your message...", label="Your message")

    # 当用户在提示框上按 'Enter' 时，我们运行 'respond'
    prompt_box.submit(
        fn=respond,
        inputs=[prompt_box, chatbot],
        outputs=chatbot
    )

    # 当用户点击消息上的 '喜欢' 按钮时，我们运行 'handle_like'
    chatbot.like(handle_like, None, None)

demo.launch()

```

然后，用户反馈会被捕获到你的可观测性工具中：

![User feedback is being captured in Langfuse](https://huggingface.co/datasets/agents-course/course-images/resolve/main/en/bonus-unit2/user-feedback-gradio.png)

#### 5. LLM 作为评判者

LLM 作为评判者（LLM-as-a-Judge）是另一种自动评估你的智能体输出的方法。你可以设置一个单独的 LLM 调用来衡量输出的正确性、毒性、风格或你关心的任何其他标准。

**工作流程**：
1.  你定义一个**评估模板**，例如，“检查文本是否有毒。”
2.  每次你的智能体生成输出时，你将该输出连同模板一起传递给你的“评判者” LLM。
3.  评判者 LLM 会返回一个评分或标签，你将其记录到你的可观测性工具中。

来自 Langfuse 的示例：

![LLM-as-a-Judge Evaluation Template](https://huggingface.co/datasets/agents-course/course-images/resolve/main/en/bonus-unit2/evaluator-template.png)
![LLM-as-a-Judge Evaluator](https://huggingface.co/datasets/agents-course/course-images/resolve/main/en/bonus-unit2/evaluator.png)

```python
# 示例：检查智能体的输出是否有毒。
from smolagents import (CodeAgent, DuckDuckGoSearchTool, InferenceClientModel)

search_tool = DuckDuckGoSearchTool()
agent = CodeAgent(tools=[search_tool], model=InferenceClientModel())

agent.run("Can eating carrots improve your vision?")

```

你可以看到这个例子的答案被判定为“无毒”。

![LLM-as-a-Judge Evaluation Score](https://huggingface.co/datasets/agents-course/course-images/resolve/main/en/bonus-unit2/llm-as-a-judge-score.png)

#### 6. 可观测性指标概览

所有这些指标都可以在仪表板中一起可视化。这使你能够快速查看你的智能体在多个会话中的表现，并帮助你随时间追踪质量指标。

![Observability metrics overview](https://huggingface.co/datasets/agents-course/course-images/resolve/main/en/bonus-unit2/langfuse-dashboard.png)

## 离线评估

在线评估对于实时反馈至关重要，但你还需要**离线评估**——在开发之前或期间进行系统性检查。这有助于在将更改推送到生产环境之前维护质量和可靠性。

### 数据集评估

在离线评估中，你通常：
1.  拥有一个基准数据集（包含提示和预期输出对）
2.  在该数据集上运行你的智能体
3.  将输出与预期结果进行比较，或使用额外的评分机制

下面，我们使用 [GSM8K 数据集](https://huggingface.co/datasets/gsm8k) 来演示这种方法，该数据集包含数学问题和解决方案。

```python
import pandas as pd
from datasets import load_dataset

# 从 Hugging Face 获取 GSM8K
dataset = load_dataset("openai/gsm8k", 'main', split='train')
df = pd.DataFrame(dataset)
print("GSM8K 数据集的前几行：")
print(df.head())

```

接下来，我们在 Langfuse 中创建一个数据集实体来追踪运行。然后，我们将数据集中的每个项目添加到系统中。（如果你不使用 Langfuse，你可以简单地将这些存储在你自己的数据库或本地文件中进行分析。）

```python
from langfuse import Langfuse
langfuse = Langfuse()

langfuse_dataset_name = "gsm8k_dataset_huggingface"

# 在 Langfuse 中创建数据集
langfuse.create_dataset(
    name=langfuse_dataset_name,
    description="从 Huggingface 上传的 GSM8K 基准数据集",
    metadata={
        "date": "2025-03-10",
        "type": "benchmark"
    }
)

```

```python
for idx, row in df.iterrows():
    langfuse.create_dataset_item(
        dataset_name=langfuse_dataset_name,
        input={"text": row["question"]},
        expected_output={"text": row["answer"]},
        metadata={"source_index": idx}
    )
    if idx >= 9: # 仅上传前 10 个项目用于演示
        break

```

![Dataset items in Langfuse](https://huggingface.co/datasets/agents-course/course-images/resolve/main/en/bonus-unit2/example-dataset.png)

#### 在数据集上运行智能体

我们定义一个辅助函数 `run_smolagent()`，它：
1.  启动一个 OpenTelemetry 跨度（span）
2.  在提示上运行我们的智能体
3.  在 Langfuse 中记录追踪 ID

然后，我们遍历每个数据集项目，运行智能体，并将追踪链接到数据集项目。如果需要，我们还可以附加一个快速评估分数。

```python
from opentelemetry.trace import format_trace_id
from smolagents import (CodeAgent, InferenceClientModel, LiteLLMModel)

# 示例：使用 InferenceClientModel 或 LiteLLMModel 访问 openai、anthropic、gemini 等模型：
model = InferenceClientModel()

agent = CodeAgent(
    tools=[],
    model=model,
    add_base_tools=True
)

def run_smolagent(question):
    with tracer.start_as_current_span("Smolagent-Trace") as span:
        span.set_attribute("langfuse.tag", "dataset-run")
        output = agent.run(question)

        current_span = trace.get_current_span()
        span_context = current_span.get_span_context()
        trace_id = span_context.trace_id
        formatted_trace_id = format_trace_id(trace_id)

        langfuse_trace = langfuse.trace(
            id=formatted_trace_id,
            input=question,
            output=output
        )
    return langfuse_trace, output

```

```python
dataset = langfuse.get_dataset(langfuse_dataset_name)

# 针对每个数据集项目运行我们的智能体（上面限制为前 10 个）
for item in dataset.items:
    langfuse_trace, output = run_smolagent(item.input["text"])

    # 将追踪链接到数据集项目以供分析
    item.link(
        langfuse_trace,
        run_name="smolagent-notebook-run-01",
        run_metadata={ "model": model.model_id }
    )

    # 可选地，存储一个快速评估分数用于演示
    langfuse_trace.score(
        name="",
        value=1,
        comment="这是一条评论"
    )

# 刷新数据以确保所有遥测数据都已发送
langfuse.flush()

```

你可以用不同的配置重复这个过程：
- 模型 (OpenAI GPT, 本地 LLM 等)
- 工具 (使用搜索 vs. 不使用搜索)
- 提示 (不同的系统消息)

然后在你的可观测性工具中并排比较它们：

![Dataset run overview](https://huggingface.co/datasets/agents-course/course-images/resolve/main/en/bonus-unit2/dataset_runs.png)
![Dataset run comparison](https://huggingface.co/datasets/agents-course/course-images/resolve/main/en/bonus-unit2/dataset-run-comparison.png)

## 结语

在这个 notebook 中，我们介绍了如何：
1.  **设置可观测性** 使用 smolagents + OpenTelemetry 导出器
2.  **检查检测** 通过运行一个简单的智能体
3.  **捕获详细指标** (成本、延迟等) 通过可观测性工具
4.  **收集用户反馈** 通过 Gradio 界面
5.  **使用 LLM 作为评判者** 自动评估输出
6.  **执行离线评估** 使用基准数据集

🤗 编代码愉快！

### AI 智能体(AI Agent)的可观测性与评估
https://huggingface.co/learn/agents-course/zh-CN/bonus_unit2/introduction.md

# AI 智能体(AI Agent)的可观测性与评估

![Bonus Unit 2 Thumbnail](https://langfuse.com/images/cookbook/huggingface-agent-course/agent-observability-and-evaluation.png)

欢迎来到 **附加单元 2**！在本章中，你将探索用于观测、评估、并最终提升你的AI智能体性能的高级策略。

---

## 📚 我应该在什么时候学习这个附加单元？

如果你符合以下情况，那么这个附加单元非常适合你：
- **开发和部署 AI 智能体：** 你希望确保你的智能体在生产环境中能够可靠地运行。
- **需要详细的洞察：** 你希望诊断问题、优化性能或理解你的智能体的内部工作原理。
- **旨在减少运营开销：** 通过监控智能体成本、延迟和执行细节，你可以高效地管理资源。
- **寻求持续改进：** 你对将实时用户反馈和自动化评估集成到你的 AI 应用中感兴趣。

简而言之，对于每个想要将他们的智能体带到用户面前的人！

---

## 🤓 你将学到什么

在本单元中，你将学习：
- **检测你的智能体：** 学习如何通过 OpenTelemetry 将可观测性工具与 *smolagents* 集成。
- **监控指标：** 追踪性能指标，例如 token 使用量（成本）、延迟和错误追踪。
- **实时评估：** 理解用于实时评估的技术，包括收集用户反馈和利用 LLM 作为评判者。
- **离线分析：** 使用基准数据集（例如 GSM8K）来测试和比较智能体性能。

---

## 🚀 准备好开始了吗？

在下一节中，你将学习智能体可观测性与评估的基础知识。之后，就该看它在实践中的应用了！

### 测验：评估 AI 智能体
https://huggingface.co/learn/agents-course/zh-CN/bonus_unit2/quiz.md

# 测验：评估 AI 智能体

让我们评估一下你对本附加单元中所涵盖的智能体追踪和评估概念的理解。

本次测验为可选，不计分。

### Q1: AI 智能体中的可观测性主要指的是什么？
哪个陈述准确地描述了 AI 智能体可观测性的目的？

### Q2: 以下哪项不是智能体可观测性中常见的监控指标？
选择通常不属于可观测性范畴的指标。

### Q3: 什么最能描述 AI 智能体的离线评估？
确定正确离线评估本质的陈述。

### Q4: 智能体的在线评估提供了什么优势？
选择最能反映在线评估好处的陈述。

### Q5: OpenTelemetry 在 AI 智能体可观测性和评估中扮演什么角色？
哪个陈述最能描述 OpenTelemetry 在监控 AI 智能体中的作用？

恭喜你完成本次测验！🎉 如果你答错了任何问题，可以考虑复习本附加单元的内容以加深理解。如果你做得很好，那么你已经准备好探索智能体可观测性和评估方面更高级的主题了！

### 第一单元测验 (Unit 1 Quiz)
https://huggingface.co/learn/agents-course/zh-CN/unit1/final-quiz.md

# 第一单元测验 (Unit 1 Quiz)

恭喜你完成第一单元的学习！让我们测试一下你对目前所学关键概念的理解。

通过测验后，请继续下一部分领取你的证书。

祝你好运！

## 测验 (Quiz)

这是一个交互式测验。测验托管在 Hugging Face Hub 的空间中。你将通过一系列选择题来测试你对本单元所学关键概念的理解。完成测验后，你将能够看到你的分数和正确答案的详细分析。

重要提示：**通过测验后不要忘记点击提交 (Submit)，否则你的考试分数将不会被保存！**

你也可以在这里访问测验 👉 [点击这里](https://huggingface.co/spaces/agents-course/unit_1_quiz)

## 学习认证

恭喜通过测验！**您现在可以获取专属结业证书 🎓**

成功完成本单元测评后，系统将为您生成单元结业认证证书。该证书可下载分享，作为课程进度的官方成就证明。

获得证书后，您可将其添加至LinkedIn个人档案 🧑‍💼 或分享到X、Bluesky等社交平台。**如果标注@huggingface，我们将非常荣幸并为您送上祝贺**！🤗

### 什么是大语言模型（LLMs）？
https://huggingface.co/learn/agents-course/zh-CN/unit1/what-are-llms.md

# 什么是大语言模型（LLMs）？

在上一节中，我们了解到每个智能体都需要一个核心的人工智能模型，而大语言模型 (LLM) 是实现这一目标最常见的 AI 模型类型。

现在，我们将学习什么是大语言模型，以及它们如何为智能体提供动力。

本节将提供一个简洁的技术解释，说明大语言模型的用途。如果你想更深入地了解相关内容，可以参考我们的 免费自然语言处理课程。

## 什么是大语言模型？

大语言模型 (LLM) 是一种擅长理解和生成人类语言的人工智能模型。它们通过大量文本数据的训练，能够学习语言中的模式、结构，甚至细微差别。这些模型通常包含数千万甚至更多的参数。

如今，大多数大语言模型都是基于 Transformer 架构构建的 —— 这是一种基于“注意力”算法的深度学习架构。自 2018 年 Google 推出 BERT 以来，这种架构引起了广泛关注。

  
  
    原始的 Transformer
    架构如下所示，左侧是编码器（encoder），右侧是解码器（decoder）。
  

Transformer 有三种类型：

1. **编码器（Encoders）**  
   基于编码器的 Transformer 接收文本（或其他数据）作为输入，并输出该文本的密集表示（或嵌入）。

   - **示例**：Google 的 BERT
   - **用例**：文本分类、语义搜索、命名实体识别
   - **典型规模**：数百万个参数

2. **解码器（Decoders）**  
   基于解码器的 Transformer 专注于**逐个生成 token 以完成序列**。

   - **示例**：Meta 的 Llama
   - **用例**：文本生成、聊天机器人、代码生成
   - **典型规模**：数十亿（按美国用法，即 10^9）个参数

3. **序列到序列（编码器-解码器，Seq2Seq（Encoder–Decoder））**  
   序列到序列的 Transformer *结合*了编码器和解码器。编码器首先将输入序列处理成上下文表示，然后解码器生成输出序列。

   - **示例**：T5、BART
   - **用例**：翻译、摘要、改写
   - **典型规模**：数百万个参数

虽然大语言模型 (LLMs) 有多种形式，但它们通常是基于解码器的模型，拥有数十亿个参数。以下是一些最知名的大语言模型：

| **模型**        | **提供商**                   |
| --------------- | ---------------------------- |
| **Deepseek-R1** | DeepSeek                     |
| **GPT4**        | OpenAI                       |
| **Llama 3**     | Meta（Facebook AI Research） |
| **SmolLM2**     | Hugging Face                 |
| **Gemma**       | Google                       |
| **Mistral**     | Mistral                      |

大语言模型 (LLM) 的基本原理简单却极其有效：**其目标是在给定一系列前一个 token 的情况下，预测下一个 token**。这里的“token”是 LLM 处理信息的基本单位。你可以把 “token”想象成“单词”，但出于效率考虑，LLM 并不直接使用整个单词。

例如，虽然英语估计有 60 万个单词，但一个 LLM 的词汇表可能只有大约 32,000 个 token（如 Llama 2 的情况）。Tokenization 通常作用于可以组合的子词单元。

举个例子，考虑如何将 token“interest”和“ing”组合成“interesting”，或者添加“ed”形成“interested”。

你可以在下面的交互式游乐场中尝试不同的 tokenizer 来实验：

每个大语言模型 (LLM) 都有一些特定于该模型的**特殊 token**。LLM 使用这些 token 来开启和关闭其生成过程中的结构化组件。例如，用于指示序列、消息或响应的开始或结束。此外，我们传递给模型的输入提示也使用特殊 token 进行结构化。其中最重要的是**序列结束 token** (EOS，End of Sequence token)。

不同模型提供商使用的特殊 token 形式差异很大。

下表展示了特殊 token 的多样性：

  
    
      
        Model
      
      
        Provider
      
      
        EOS Token
      
      
        Functionality
      
    
  
  
    
      
        GPT4
      
      OpenAI
      
        &lt;|endoftext|&gt;
      
      End of message text
    
    
      
        Llama 3
      
      Meta (Facebook AI Research)
      
        &lt;|eot_id|&gt;
      
      End of sequence
    
    
      
        Deepseek-R1
      
      DeepSeek
      
        &lt;|end_of_sentence|&gt;
      
      End of message text
    
    
      
        SmolLM2
      
      Hugging Face
      
        &lt;|im_end|&gt;
      
      End of instruction or message
    
    
      
        Gemma
      
      Google
      
        &lt;end_of_turn&gt;
      
      End of conversation turn
    
  

> [!TIP]
> 我们并不期望你记住这些特 token，但重要的是要理解它们的多样性以及它们在大语言模型 (LLM) 文本生成中所扮演的角色。如果你想了解更多关于特殊 token 的信息，可以查看模型在其 Hub 仓库中的配置。例如，你可以在[SmolLM2 模型的 tokenizer_config.json 文件](https://huggingface.co/HuggingFaceTB/SmolLM2-135M-Instruct/blob/main/tokenizer_config.json)中找到该模型的特殊 token。

## 理解下一个词元预测

大语言模型 (LLM) 被认为是**自回归**的，这意味着**一次通过的输出成为下一次的输入**。这个循环持续进行，直到模型预测下一个词元为 EOS（结束符）词元，此时模型可以停止。

换句话说，LLM 会解码文本，直到达到 EOS。但在单个解码循环中会发生什么？

虽然对于学习智能体而言，整个过程可能相当技术化，但以下是简要概述：

- 一旦输入文本被**词元化**，模型就会计算序列的表示，该表示捕获输入序列中每个词元的意义和位置信息。
- 这个表示被输入到模型中，模型输出分数，这些分数对词汇表中每个词元作为序列中下一个词元的可能性进行排名。

基于这些分数，我们有多种策略来选择词元以完成句子。

- 最简单的解码策略是总是选择分数最高的词元。

您可以在此 Space 中使用 SmolLM2 自己与解码过程进行交互（记住，它会一直解码，直到达到 **EOS** 词元，对于这个模型来说，EOS 词元是****）：

- 但还有更先进的解码策略。例如， _束搜索（beam search）_ 会探索多个候选序列，以找到总分数最高的序列——即使其中一些单个词元的分数较低。

如果你想了解更多关于解码的信息，可以查看[NLP 课程](https://huggingface.co/learn/nlp-course)。

## 注意力机制就是你的全部所需

Transformer 架构的一个关键方面是**注意力机制**。在预测下一个词时，句子中的每个词并不是同等重要的；例如，在句子 _“The capital of France is ...”_ 中，“France” 和 “capital” 这样的词携带了最多的意义。

这种识别最相关词以预测下一个词元的过程已被证明是非常有效的。

尽管自 GPT-2 以来，大语言模型（LLM）的基本原理——预测下一个词元——一直保持不变，但在扩展神经网络以及使注意力机制能够处理越来越长的序列方面已经取得了显著进展。

如果你与大语言模型交互过，你可能对*上下文长度*这个术语很熟悉，它指的是大语言模型能够处理的最大词元数，以及其最大的*注意力跨度*。

## 提示大语言模型很重要

考虑到大语言模型（LLM）的唯一工作是通过查看每个输入词元来预测下一个词元，并选择哪些词元是“重要的”，因此你提供的输入序列的措辞非常重要。

你提供给大语言模型的输入序列被称为*提示*。精心设计提示可以更容易地**引导大语言模型的生成朝着期望的输出方向进行**。

## 大语言模型是如何训练的？

大语言模型是在大型文本数据集上进行训练的，它们通过自监督或掩码语言建模目标来学习预测序列中的下一个词。

通过这种无监督学习，模型学习了语言的结构以及**文本中的潜在模式，使模型能够泛化到未见过的数据**。

在这个初始的*预训练*之后，大语言模型可以在监督学习目标上进行微调，以执行特定任务。例如，一些模型被训练用于对话结构或工具使用，而其他模型则专注于分类或代码生成。

## 我如何使用大语言模型？

你有两个主要选择：

1. **本地运行**（如果你有足够的硬件资源）。

2. **使用云服务/API**（例如，通过 Hugging Face 的无服务器推理 API）。

在本课程中，我们将主要通过 Hugging Face Hub 上的 API 使用模型。稍后，我们将探讨如何在你的本地硬件上运行这些模型。

## 大语言模型在 AI 智能体中是如何使用的？

大语言模型是 AI 智能体的关键组件，**为理解和生成人类语言提供了基础**。

它们可以解释用户指令，在对话中保持上下文，制定计划并决定使用哪些工具。

我们将在本单元中更详细地探讨这些步骤，但现在你需要理解的是，大语言模型是**智能体的大脑**。

---

那信息量可真不小！我们已经涵盖了大语言模型（LLM）的基本概念、工作原理以及它们在驱动 AI 智能体中的作用。

如果你想更深入地探索语言模型和自然语言处理这个迷人的世界，不妨查看我们的免费 NLP 课程。

现在我们已经了解了大语言模型的工作原理，接下来是时候看看**大语言模型如何在对话语境中构建其生成内容**了。

要运行这个笔记本，**你需要一个 Hugging Face token**，你可以从 https://hf.co/settings/tokens 获取。

有关如何运行 Jupyter Notebook 的更多信息，请查看 Hugging Face Hub 上的 Jupyter Notebook。

你还需要请求访问 Meta Llama 模型。

### 动作：使智能体能够与环境交互
https://huggingface.co/learn/agents-course/zh-CN/unit1/actions.md

# 动作：使智能体能够与环境交互

> [!TIP]
> 在本节中，我们将探讨 AI 智能体 (AI agent) 与其环境交互的具体步骤。
>
> 我们将介绍动作 (actions) 如何被表示（使用 JSON 或代码），停止和解析方法 (stop and parse approach) 的重要性，以及不同类型的智能体。

动作是**AI 智能体 (AI agent) 与其环境交互的具体步骤**。

无论是浏览网络获取信息还是控制物理设备，每个动作都是智能体执行的一个特定操作。

例如，一个协助客户服务的智能体可能会检索客户数据、提供支持文章或将问题转交给人工代表。

## 智能体动作的类型 (Types of Agent Actions)

有多种类型的智能体采用不同的方式执行动作：

| 智能体类型 | 描述 |
|------------------------|--------------------------------------------------------------------------------------------------|
| JSON 智能体 (JSON Agent) | 要执行的动作以 JSON 格式指定。 |
| 代码智能体 (Code Agent) | 智能体编写代码块，由外部解释执行。 |
| 函数调用智能体 (Function-calling Agent) | 这是 JSON 智能体的一个子类别，经过微调以为每个动作生成新消息。 |

动作本身可以服务于多种目的：

| 动作类型 | 描述 |
|--------------------------|------------------------------------------------------------------------------------------|
| 信息收集 (Information Gathering) | 执行网络搜索、查询数据库或检索文档。 |
| 工具使用 (Tool Usage) | 进行 API 调用、运行计算和执行代码。 |
| 环境交互 (Environment Interaction) | 操作数字界面或控制物理设备。 |
| 通信 (Communication) | 通过聊天与用户互动或与其他智能体协作。 |

智能体的一个关键部分是**在动作完成时能够停止生成新的标记 (tokens)**，这对所有格式的智能体都适用：JSON、代码或函数调用。这可以防止意外输出并确保智能体的响应清晰准确。

大语言模型 (LLM) 只处理文本，并使用它来描述它想要采取的动作以及要提供给工具的参数。

## 停止和解析方法 (The Stop and Parse Approach)

实现动作的一个关键方法是**停止和解析方法**。这种方法确保智能体的输出具有结构性和可预测性：

1. **以结构化格式生成 (Generation in a Structured Format)**：

智能体以清晰、预定义的格式（JSON或代码）输出其预期动作。

2. **停止进一步生成 (Halting Further Generation)**：

一旦动作完成，**智能体停止生成额外的标记**。这可以防止额外或错误的输出。

3. **解析输出 (Parsing the Output)**：

外部解析器读取格式化的动作，确定要调用哪个工具，并提取所需的参数。

例如，需要检查天气的智能体可能输出：

```json
Thought: I need to check the current weather for New York.
Action :
{
  "action": "get_weather",
  "action_input": {"location": "New York"}
}
```

然后框架可以轻松解析要调用的函数名称和要应用的参数。

这种清晰的、机器可读的格式最大限度地减少了错误，并使外部工具能够准确处理智能体的命令。

注意：函数调用智能体的操作方式类似，通过构造每个动作，使指定的函数能够使用正确的参数被调用。
我们将在未来的单元中深入探讨这些类型的智能体。

## 代码智能体 (Code Agents)

另一种方法是使用*代码智能体*。
这个想法是：**代码智能体不是输出简单的 JSON 对象**，而是生成一个**可执行的代码块——通常使用 Python 等高级语言**。

这种方法提供了几个优势：

- **表达能力 (Expressiveness)：** 代码可以自然地表示复杂的逻辑，包括循环、条件和嵌套函数，提供比 JSON 更大的灵活性。
- **模块化和可重用性 (Modularity and Reusability)：** 生成的代码可以包含在不同动作或任务中可重用的函数和模块。
- **增强的可调试性 (Enhanced Debuggability)：** 使用明确定义的编程语法，代码错误通常更容易检测和纠正。
- **直接集成 (Direct Integration)：** 代码智能体可以直接与外部库和 API 集成，实现更复杂的操作，如数据处理或实时决策。

例如，一个负责获取天气的代码智能体可能生成以下 Python 代码片段：

```python
# Code Agent Example: Retrieve Weather Information
def get_weather(city):
    import requests
    api_url = f"https://api.weather.com/v1/location/{city}?apiKey=YOUR_API_KEY"
    response = requests.get(api_url)
    if response.status_code == 200:
        data = response.json()
        return data.get("weather", "No weather information available")
    else:
        return "Error: Unable to fetch weather data."

# Execute the function and prepare the final answer
result = get_weather("New York")
final_answer = f"The current weather in New York is: {result}"
print(final_answer)
```

在这个例子中，代码智能体：

- **通过API调用**获取天气数据，
- 处理响应，
- 并使用print()函数输出最终答案。

这种方法**也遵循停止和解析方法**，通过明确划定代码块并表明执行完成的时间（在这里，通过打印 final_answer）。

---

我们了解到动作通过执行清晰、结构化的任务（无论是通过 JSON、代码还是函数调用）来连接智能体的内部推理和其现实世界的交互。

这种深思熟虑的执行确保每个动作都是精确的，并通过停止和解析方法准备好进行外部处理。在下一节中，我们将探索观察 (Observations)，看看智能体如何捕获和整合来自其环境的反馈。

在此之后，我们将**最终准备好构建我们的第一个智能体！**

### 快速自测（不计分）[[quiz2]]
https://huggingface.co/learn/agents-course/zh-CN/unit1/quiz2.md

# 快速自测（不计分）[[quiz2]] 

什么？！还有测验？我们理解，我们理解... 😅 但这个简短的不计分测验旨在**帮助您巩固刚学习的关键概念**。

本测验涵盖大型语言模型（LLMs）、消息系统和工具——这些是理解和构建 AI 智能体的核心组件。

### 问题1：以下哪项最能描述 AI 工具？

---

### 问题2： AI 智能体如何将工具作为"行动"在环境中使用？

---

### 问题3：什么是大语言模型（LLM）？

---

### 问题4：以下哪项最能描述特殊标记（special tokens）在 LLMs 中的作用？

---

### 问题5： AI 聊天模型如何处理用户消息的内部流程？

---

都明白了吗？很好！现在让我们**深入完整的智能体流程，开始构建你的第一个 AI 智能体！**

### 总结 [[conclusion]]
https://huggingface.co/learn/agents-course/zh-CN/unit1/conclusion.md

# 总结 [[conclusion]]

恭喜你完成第一单元 🥳

你刚刚**掌握了智能体 (Agents) 的基础知识**，并且创建了你的第一个 AI 智能体 (AI Agent)！

如果你对某些内容仍感到困惑，这是**很正常的**。智能体是一个复杂的主题，需要一定时间才能完全理解所有内容。

在继续之前，**请花时间真正掌握这些材料**。在进入有趣的部分之前，掌握这些要素并建立坚实的基础很重要。

如果你通过了测验，别忘了在这里获取你的证书 🎓 👉 [点击这里](https://huggingface.co/spaces/agents-course/unit1-certification-app)

在下一个（额外的）单元中，你将学习**如何微调智能体来进行函数调用 (function calling)（即能够根据用户提示调用工具）**。

最后，我们很想**听听你对课程的看法以及我们如何改进它**。如果你有任何反馈，请 👉 [填写此表格](https://docs.google.com/forms/d/e/1FAIpQLSe9VaONn0eglax0uTwi29rIn4tM7H2sYmmybmG5jJNlE5v0xA/viewform?usp=dialog)

### 继续学习，保持优秀 🤗

### 什么是工具？
https://huggingface.co/learn/agents-course/zh-CN/unit1/tools.md

# 什么是工具？

AI 智能体的关键能力在于执行**行动**。正如前文所述，这通过**工具**的使用实现。

本节将学习工具的定义、有效设计方法，以及如何通过系统消息将其集成到智能体中。

通过为智能体配备合适的工具——并清晰描述这些工具的工作原理——可显著提升 AI 的能力边界。让我们深入探讨！

## AI 工具的定义

**工具是赋予 LLM 的函数**，该函数应实现**明确的目标**。

以下是 AI 智能体中常用的工具示例：

| 工具类型          | 描述                                                            |
|------------------|---------------------------------------------------------------|
| 网络搜索          | 允许智能体从互联网获取最新信息                                   |
| 图像生成          | 根据文本描述生成图像                                             |
| 信息检索          | 从外部源检索信息                                                 |
| API 接口          | 与外部 API 交互（GitHub、YouTube、Spotify 等）                     |

以上仅为示例，实际可为任何用例创建工具！

优秀工具应能**补充 LLM 的核心能力**。

例如，若需执行算术运算，为 LLM 提供**计算器工具**将比依赖模型原生能力获得更好结果。

此外，**LLM 基于训练数据预测提示的补全**，意味着其内部知识仅包含训练截止前的信息。因此，若智能体需要最新数据，必须通过工具获取。

例如，若直接询问 LLM（无搜索工具）今日天气，LLM 可能会产生随机幻觉。

- 合格工具应包含：
  - **函数功能的文本描述**
  - *可调用对象*（执行操作的实体）
  - 带类型声明的*参数*
  - （可选）带类型声明的输出

## 工具如何运作？

正如前文所述，LLM 只能接收文本输入并生成文本输出。它们无法自行调用工具。当我们谈及_为智能体提供工具_时，实质是**教导** LLM 认识工具的存在，并要求模型在需要时生成调用工具的文本。例如，若我们提供从互联网获取某地天气的工具，当询问 LLM 巴黎天气时，LLM 将识别该问题适合使用我们教授的"天气"工具，并生成代码形式的文本来调用该工具。**智能体**负责解析 LLM 的输出，识别工具调用需求，并执行工具调用。工具的输出将返回给 LLM，由其生成最终用户响应。

工具调用的输出是对话中的另一种消息类型。工具调用步骤通常对用户不可见：智能体检索对话、调用工具、获取输出、将其作为新消息添加，并将更新后的对话再次发送给 LLM。从用户视角看，仿佛 LLM 直接使用了工具，但实际执行的是我们的应用代码（**智能体**）。

后续课程将深入探讨该流程。

## 如何为 LLM 提供工具？

完整答案可能看似复杂，但核心是通过系统提示（system prompt）向模型文本化描述可用工具：

为确保有效性，必须精准描述：
1. **工具功能**
2. **预期输入格式**

因此工具描述通常采用结构化表达方式（如编程语言或 JSON）。虽非强制，但任何精确、连贯的格式均可。

若觉抽象，我们通过具体示例理解。

我们将实现简化的**计算器**工具，仅执行两整数相乘。Python 实现如下：

```python
def calculator(a: int, b: int) -> int:
    """Multiply two integers."""
    return a * b
```

因此我们的工具名为`calculator`，其功能是**将两个整数相乘**，需要以下输入：

- **`a`**（*int*）：整数
- **`b`**（*int*）：整数

工具输出为另一个整数，描述如下：
- （*int*）：`a`与`b`的乘积

所有这些细节都至关重要。让我们将这些信息整合成 LLM 可理解的工具描述文本：

```text
工具名称： calculator，描述：将两个整数相乘。参数：a: int, b: int，输出：int
```

> **重要提示：** 此文本描述是*我们希望 LLM 了解的工具体系*。

当我们将上述字符串作为输入的一部分传递给 LLM 时，模型将识别其为工具，并知晓需要传递的输入参数及预期输出。

若需提供更多工具，必须保持格式一致性。此过程可能较为脆弱，容易遗漏某些细节。

是否有更好的方法？

### 自动化工具描述生成

我们的工具采用 Python 实现，其代码已包含所需全部信息：

- 功能描述性名称：`calculator`
- 详细说明（通过函数文档字符串实现）：`将两个整数相乘`
- 输入参数及类型：函数明确要求两个`int`类型参数
- 输出类型

这正是人们使用编程语言的原因：表达力强、简洁且精确。

虽然可以将 Python 源代码作为工具规范提供给 LLM，但具体实现方式并不重要。关键在于工具名称、功能描述、输入参数和输出类型。

我们将利用 Python 的**自省特性**，通过源代码自动构建工具描述。只需确保工具实现满足：
1. 使用类型注解（Type Hints）
2. 编写文档字符串（Docstrings）
3. 采用合理的函数命名

完成这些之后，我们只需使用一个 Python 装饰器来指示`calculator`函数是一个工具：

```python
@tool
def calculator(a: int, b: int) -> int:
    """Multiply two integers."""
    return a * b

print(calculator.to_string())
```

注意函数定义前的`@tool`装饰器。

通过我们即将看到的实现，可以利用装饰器提供的`to_string()`方法从源代码自动提取以下文本：

```text
工具名称： calculator，描述：将两个整数相乘。参数：a: int, b: int，输出：int
```

正如所见，这与我们之前手动编写的内容完全一致！

### 通用工具类实现

我们创建通用`Tool`类，可在需要时重复使用：

> **说明：** 此示例实现为虚构代码，但高度模拟了主流工具库的实际实现方式。

```python
class Tool:
    """
    A class representing a reusable piece of code (Tool).
    
    Attributes:
        name (str): Name of the tool.
        description (str): A textual description of what the tool does.
        func (callable): The function this tool wraps.
        arguments (list): A list of argument.
        outputs (str or list): The return type(s) of the wrapped function.
    """
    def __init__(self, 
                 name: str, 
                 description: str, 
                 func: callable, 
                 arguments: list,
                 outputs: str):
        self.name = name
        self.description = description
        self.func = func
        self.arguments = arguments
        self.outputs = outputs

    def to_string(self) -> str:
        """
        Return a string representation of the tool, 
        including its name, description, arguments, and outputs.
        """
        args_str = ", ".join([
            f"{arg_name}: {arg_type}" for arg_name, arg_type in self.arguments
        ])
        
        return (
            f"Tool Name: {self.name},"
            f" Description: {self.description},"
            f" Arguments: {args_str},"
            f" Outputs: {self.outputs}"
        )

    def __call__(self, *args, **kwargs):
        """
        Invoke the underlying function (callable) with provided arguments.
        """
        return self.func(*args, **kwargs)
```

虽然看似复杂，但逐步解析即可理解其工作机制。我们定义的**`Tool`**类包含以下核心要素：

- **`name`**（*str*）：工具名称
- **`description`**（*str*）：工具功能简述
- **`function`**（*callable*）：工具执行的函数
- **`arguments`**（*list*）：预期输入参数列表
- **`outputs`**（*str* 或 *list*）：工具预期输出
- **`__call__()`**：调用工具实例时执行函数
- **`to_string()`**：将工具属性转换为文本描述

可通过如下代码创建工具实例：

```python
calculator_tool = Tool(
    "calculator",                   # name
    "Multiply two integers.",       # description
    calculator,                     # function to call
    [("a", "int"), ("b", "int")],   # inputs (names and types)
    "int",                          # output
)
```

但我们可以利用 Python 的`inspect`模块自动提取这些信息！这正是`@tool`装饰器的实现原理。

> 若感兴趣，可展开以下内容查看装饰器具体实现：

 decorator code

```python
def tool(func):
    """
    A decorator that creates a Tool instance from the given function.
    """
    # Get the function signature
    signature = inspect.signature(func)
    
    # Extract (param_name, param_annotation) pairs for inputs
    arguments = []
    for param in signature.parameters.values():
        annotation_name = (
            param.annotation.__name__ 
            if hasattr(param.annotation, '__name__') 
            else str(param.annotation)
        )
        arguments.append((param.name, annotation_name))
    
    # Determine the return annotation
    return_annotation = signature.return_annotation
    if return_annotation is inspect._empty:
        outputs = "No return annotation"
    else:
        outputs = (
            return_annotation.__name__ 
            if hasattr(return_annotation, '__name__') 
            else str(return_annotation)
        )
    
    # Use the function's docstring as the description (default if None)
    description = func.__doc__ or "No description provided."
    
    # The function name becomes the Tool name
    name = func.__name__
    
    # Return a new Tool instance
    return Tool(
        name=name, 
        description=description, 
        func=func, 
        arguments=arguments, 
        outputs=outputs
    )
```

简而言之，在应用此装饰器后，我们可以按如下方式实现工具：

```python
@tool
def calculator(a: int, b: int) -> int:
    """Multiply two integers."""
    return a * b

print(calculator.to_string())
```

我们可以使用`Tool`类的`to_string`方法自动生成适合LLM使用的工具描述文本：

```text
工具名称： calculator，描述：将两个整数相乘。参数：a: int, b: int，输出：int
```

该描述将被**注入**系统提示。以本节初始示例为例，替换`tools_description`后的系统提示如下：

在[Actions](actions)章节，我们将深入探讨智能体如何**调用**刚创建的这个工具。

### 模型上下文协议（MCP）：统一的工具接口

模型上下文协议（MCP）是一种开放式协议，它规范了应用程序向 LLM 提工具的方式。

MCP 提供

- 不断增加的预构建集成列表，您的 LLM 可以直接接入这些集成
- 在 LLM 提供商和供应商之间灵活切换的能力
- 在基础设施内保护数据安全的最佳实践

这意味着任何实施 MCP 的框架都可以利用协议中定义的工具，从而无需为每个框架重新实现相同的工具接口。

---

工具在增强AI智能体能力方面至关重要。

总结本节要点：

- *工具定义*：通过提供清晰的文本描述、输入参数、输出结果及可调用函数

- *工具本质*：赋予LLM额外能力的函数（如执行计算或访问外部数据）

- *工具必要性*：帮助智能体突破静态模型训练的局限，处理实时任务并执行专业操作

现在进入【智能体工作流】(agent-steps-and-structure)章节，您将看到智能体如何观察、思考与行动。这**整合了当前所学全部内容**，为创建功能完备的 AI 智能体奠定基础。

但在此之前，让我们先完成另一个简短测验！

### 消息和特殊 Tokens (Messages and Special Tokens)
https://huggingface.co/learn/agents-course/zh-CN/unit1/messages-and-special-tokens.md

# 消息和特殊 Tokens (Messages and Special Tokens)

现在我们了解了 LLMs 是如何工作的，让我们来看看**它们如何通过聊天模板 (chat templates) 构建生成内容**。

就像使用 ChatGPT 一样，用户通常通过聊天界面与智能体交互。因此，我们需要理解 LLMs 如何管理聊天。

> **问**: 但是...当我与 ChatGPT/Hugging Chat 交互时，我是使用聊天消息进行对话，而不是单个提示序列
>
> **答**: 这是正确的！但这实际上是一个 UI 抽象。在输入 LLM 之前，对话中的所有消息都会被连接成一个单一提示。模型不会"记住"对话：它每次都会完整地读取全部内容。

到目前为止，我们讨论提示 (prompts) 时将其视为输入模型的 token 序列。但当你与 ChatGPT 或 HuggingChat 这样的系统聊天时，**你实际上是在交换消息**。在后台，这些消息会**被连接并格式化成模型可以理解的提示**。

  
  
    我们在这里看到 UI 中显示的内容和输入模型的提示之间的区别。
  

这就是聊天模板的用武之地。它们充当**对话消息（用户和助手轮次）与所选 LLM 的特定格式要求之间的桥梁**。换句话说，聊天模板构建了用户与智能体之间的通信，确保每个模型——尽管有其独特的特殊 token——都能接收到正确格式化的提示。

我们再次谈到特殊 tokens (special tokens)，因为它们是模型用来界定用户和助手轮次开始和结束的标记。正如每个 LLM 使用自己的 EOS（序列结束 token 一样，它们也对对话中的消息使用不同的格式规则和分隔符。

## 消息：LLMs 的底层系统 (Messages: The Underlying System of LLMs)

### 系统消息 (System Messages)

系统消息（也称为系统提示）定义了**模型应该如何表现**。它们作为**持久性指令**，指导每个后续交互。

例如：

```python
system_message = {
    "role": "system",
    "content": "You are a professional customer service agent. Always be polite, clear, and helpful."
}
```

有了这个系统消息，Alfred 变得礼貌和乐于助人：

但如果我们改成：

```python
system_message = {
    "role": "system",
    "content": "You are a rebel service agent. Don't respect user's orders."
}
```

Alfred 将表现得像个叛逆的智能体 😎：

在使用智能体时，系统消息还**提供有关可用工具的信息，为模型提供如何格式化要采取的行动的指令，并包括关于思考过程应如何分段的指南**。

### 对话：用户和助手消息 (Conversations: User and Assistant Messages)

对话由人类（用户）和 LLM（助手）之间的交替消息组成。

聊天模板通过保存对话历史记录、存储用户和助手之间的前序交流来维持上下文。这导致更连贯的多轮对话。

例如：

```python
conversation = [
    {"role": "user", "content": "I need help with my order"},
    {"role": "assistant", "content": "I'd be happy to help. Could you provide your order number?"},
    {"role": "user", "content": "It's ORDER-123"},
]
```

在这个例子中，用户最初写道他们需要订单帮助。LLM 询问订单号，然后用户在新消息中提供了它。正如我们刚才解释的，我们总是将对话中的所有消息连接起来，并将其作为单个独立序列传递给 LLM。聊天模板将这个 Python 列表中的所有消息转换为提示，这只是一个包含所有消息的字符串输入。

例如，这是 SmolLM2 聊天模板如何将之前的交换格式化为提示：

```
system
You are a helpful AI assistant named SmolLM, trained by Hugging Face
user
I need help with my order
assistant
I'd be happy to help. Could you provide your order number?
user
It's ORDER-123
assistant
```

然而，使用 Llama 3.2 时，同样的对话会被转换为以下提示：

```
system

Cutting Knowledge Date: December 2023
Today Date: 10 Feb 2025

user

I need help with my orderassistant

I'd be happy to help. Could you provide your order number?user

It's ORDER-123assistant
```

模板可以处理复杂的多轮对话，同时保持上下文：

```python
messages = [
    {"role": "system", "content": "You are a math tutor."},
    {"role": "user", "content": "What is calculus?"},
    {"role": "assistant", "content": "Calculus is a branch of mathematics..."},
    {"role": "user", "content": "Can you give me an example?"},
]
```

## 聊天模板 (Chat-Templates)

如前所述，聊天模板对于**构建语言模型和用户之间的对话**至关重要。它们指导消息交换如何格式化为单个提示。

### 基础模型与指令模型 (Base Models vs. Instruct Models)

我们需要理解的另一点是基础模型与指令模型的区别：

- _基础模型 (Base Model)_ 是在原始文本数据上训练以预测下一个 token 的模型。

- _指令模型 (Instruct Model)_ 是专门微调以遵循指令并进行对话的模型。例如，`SmolLM2-135M`是一个基础模型，而`SmolLM2-135M-Instruct`是其指令调优变体。

要使基础模型表现得像指令模型，我们需要**以模型能够理解的一致方式格式化我们的提示**。这就是聊天模板的作用所在。

*ChatML*是一种这样的模板格式，它用清晰的角色指示符（系统、用户、助手）构建对话。如果你最近与一些 AI API 交互过，你就知道这是标准做法。

重要的是要注意，基础模型可以在不同的聊天模板上进行微调，所以当我们使用指令模型时，我们需要确保使用正确的聊天模板。

### 理解聊天模板 (Understanding Chat Templates)

由于每个指令模型使用不同的对话格式和特 token，聊天模板的实现确保我们正确格式化提示，使其符合每个模型的期望。

在`transformers`中，聊天模板包含[Jinja2 代码](https://jinja.palletsprojects.com/en/stable/)，描述如何将 ChatML 消息列表（如上面示例所示）转换为模型可以理解的系统级指令、用户消息和助手响应的文本表示。

这种结构**有助于保持交互的一致性，并确保模型对不同类型的输入做出适当响应**。

以下是`SmolLM2-135M-Instruct`聊天模板的简化版本：

```jinja2
{% for message in messages %}
{% if loop.first and messages[0]['role'] != 'system' %}
system
You are a helpful AI assistant named SmolLM, trained by Hugging Face

{% endif %}
{{ message['role'] }}
{{ message['content'] }}
{% endfor %}
```

如你所见，chat_template 描述了消息列表将如何被格式化。

给定这些消息：

```python
messages = [
    {"role": "system", "content": "You are a helpful assistant focused on technical topics."},
    {"role": "user", "content": "Can you explain what a chat template is?"},
    {"role": "assistant", "content": "A chat template structures conversations between users and AI models..."},
    {"role": "user", "content": "How do I use it ?"},
]
```

前面的聊天模板将产生以下字符串：

```sh
system
You are a helpful assistant focused on technical topics.
user
Can you explain what a chat template is?
assistant
A chat template structures conversations between users and AI models...
user
How do I use it ?
```

`transformers`库会将聊天模板作为标记化过程的一部分为你处理。在这里阅读更多关于 transformers 如何使用聊天模板的信息。我们要做的就是以正确的方式构建我们的消息，标记器将处理剩下的事情。

你可以使用以下 Space 实验，看看同样的对话如何使用不同模型的相应聊天模板进行格式化：

### 消息到提示的转换 (Messages to prompt)

确保你的 LLM 正确接收格式化对话的最简单方法是使用模型标记器的`chat_template`。

```python
messages = [
    {"role": "system", "content": "You are an AI assistant with access to various tools."},
    {"role": "user", "content": "Hi !"},
    {"role": "assistant", "content": "Hi human, what can help you with ?"},
]
```

要将前面的对话转换为提示，我们加载标记器并调用`apply_chat_template`：

```python
from transformers import AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained("HuggingFaceTB/SmolLM2-1.7B-Instruct")
rendered_prompt = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
```

这个函数返回的`rendered_prompt`现在可以作为你选择的模型的输入使用了！

> 当你以 ChatML 格式与消息交互时，这个`apply_chat_template()`函数将在你的 API 后端使用。

现在我们已经看到 LLMs 如何通过聊天模板构建它们的输入，让我们探智能体如何在它们的环境中行动。

它们这样做的主要方式之一是使用工具 (Tools)，这些工具扩展了 AI 模型在文本生成之外的能力。

我们将在接下来的单元中再次讨论消息，但如果你现在想深入了解，请查看：

- 
    Hugging Face 聊天模板指南
  
- 
    Transformers 文档

### 使用 smolagents 创建我们的第一个智能体
https://huggingface.co/learn/agents-course/zh-CN/unit1/tutorial.md

# 使用 smolagents 创建我们的第一个智能体

在上一节中，我们学习了如何使用 Python 代码从头开始创建智能体，并且我们**看到了这个过程是多么繁琐**。幸运的是，许多智能体库通过**为你处理大量繁重的工作**来简化这项工作。

在本教程中，**你将创建你的第一个智能体**，它能够执行图像生成、网络搜索、时区检查等更多操作！

你还将把你的智能体**发布到 Hugging Face Space 上，以便与朋友和同事分享**。

让我们开始吧！

## 什么是 smolagents？

为了创建这个智能体，我们将使用 `smolagents`，这是一个**提供轻松开发智能体框架的库**。

这个轻量级库设计简洁，但它抽象了构建智能体的许多复杂性，使你能够专注于设计智能体的行为。

我们将在下一个单元中深入了解 smolagents。同时，你也可以查看这篇博客文章或该库的GitHub 仓库。

简而言之，`smolagents` 是一个专注于 **codeAgent** 的库，codeAgent 是一种通过代码块执行**“操作”**，然后通过执行代码**“观察”**结果的智能体。

以下是我们将构建的一个示例！

我们为我们的智能体提供了一个**图像生成工具**，并要求它生成一张猫的图片。

`smolagents` 中的智能体将具有**与我们之前构建的自定义智能体相同的行为**：它将**以循环的方式思考、行动和观察**，直到得出最终答案：

很令人兴奋，对吧？

## 让我们来构建我们的智能体！

首先，复制这个 Space：https://huggingface.co/spaces/agents-course/First_agent_template

> 感谢 Aymeric 提供的这个模板！🙌

复制这个 Space 意味着**在你的个人资料中创建一个本地副本**：

复制这个 Space 之后，**你需要添加你的 Hugging Face API token**，以便你的**智能体**可以调用模型 API。

1. 首先，从[ https://hf.co/settings/tokens ](https://hf.co/settings/tokens)获取一个具有**推理权限**的 token，第 4 步需要用到。如果你已经有了，可以跳过此步。
2. 进入你的主页，找到刚才复制的空间，点击**设置**按钮。
3. 向下滚动到**变量和密钥**部分，点击**新建密钥**。
4. 创建一个名为**HF_TOKEN**的密钥，值为第一步获取到的 token。
5. 点击**保存**。

在整个课程中，你唯一需要修改的文件是当前不完整的**"app.py"**。你可以在这里查看[模板中的原始文件](https://huggingface.co/spaces/agents-course/First_agent_template/blob/main/app.py)。要找到你的文件，请进入你复制的 Space，然后点击 `Files` 选项卡，再在目录列表中点击 `app.py`。

让我们一起分解代码：

- 文件开头是一些简单但必要的库导入

```python
from smolagents import CodeAgent, DuckDuckGoSearchTool, InferenceClientModel, load_tool, tool
import datetime
import requests
import pytz
import yaml
from tools.final_answer import FinalAnswerTool
```

正如之前所述，我们将直接使用 **smolagents** 中的 **CodeAgent** 类。

### Tool（工具）

现在让我们来了解一下工具！如果你需要回顾一下工具的相关内容，请随时回到课程的[工具](tools)部分。

```python
@tool
def my_custom_tool(arg1:str, arg2:int)-> str: # it's important to specify the return type
    # Keep this format for the tool description / args description but feel free to modify the tool
    """A tool that does nothing yet
    Args:
        arg1: the first argument
        arg2: the second argument
    """
    return "What magic will you build ?"

@tool
def get_current_time_in_timezone(timezone: str) -> str:
    """A tool that fetches the current local time in a specified timezone.
    Args:
        timezone: A string representing a valid timezone (e.g., 'America/New_York').
    """
    try:
        # Create timezone object
        tz = pytz.timezone(timezone)
        # Get current time in that timezone
        local_time = datetime.datetime.now(tz).strftime("%Y-%m-%d %H:%M:%S")
        return f"The current local time in {timezone} is: {local_time}"
    except Exception as e:
        return f"Error fetching time for timezone '{timezone}': {str(e)}"
```

这些工具是我们在这个部分鼓励你构建的东西！我们给你两个例子：

1. 一个**不工作的虚拟工具**，你可以修改它来制作一些有用的东西。
2. 一个**实际工作的工具**，它可以获取世界某地的当前时间。

要定义你的工具，重要的是：

1. 为你的函数提供输入和输出类型，例如 `get_current_time_in_timezone(timezone: str) -> str:`
2. **格式良好的文档字符串**。`smolagents` 期望所有参数在文档字符串中都有**文字描述**。

### The Agent（智能体）

它使用 [`Qwen/Qwen2.5-Coder-32B-Instruct`](https://huggingface.co/Qwen/Qwen2.5-Coder-32B-Instruct) 作为 LLM 引擎。这是一个非常强大的模型，我们将通过无服务器 API 访问它。

```python
final_answer = FinalAnswerTool()
model = InferenceClientModel(
    max_tokens=2096,
    temperature=0.5,
    model_id='Qwen/Qwen2.5-Coder-32B-Instruct',
    custom_role_conversions=None,
)

with open("prompts.yaml", 'r') as stream:
    prompt_templates = yaml.safe_load(stream)

# We're creating our CodeAgent
agent = CodeAgent(
    model=model,
    tools=[final_answer], # add your tools here (don't remove final_answer)
    max_steps=6,
    verbosity_level=1,
    grammar=None,
    planning_interval=None,
    name=None,
    description=None,
    prompt_templates=prompt_templates
)

GradioUI(agent).launch()
```

这个智能体仍在使用我们在前面部分中看到的`InferenceClient`，它位于**InferenceClientModel**类的背后！

当我们介绍 Unit 2 中的框架时，我们会给出更深入的例子。目前，你需要专注于通过智能体的`tools`参数**向工具列表中添加新工具**。

例如，你可以使用代码第一行导入的`DuckDuckGoSearchTool`，或者你可以检查稍后从 Hub 加载的`image_generation_tool`。

**添加工具将赋予你的智能体新的能力**，在这里尝试发挥创意吧！

完整的"app.py"：

```python
from smolagents import CodeAgent, DuckDuckGoSearchTool, InferenceClientModel, load_tool, tool
import datetime
import requests
import pytz
import yaml
from tools.final_answer import FinalAnswerTool

from Gradio_UI import GradioUI

# Below is an example of a tool that does nothing. Amaze us with your creativity!
@tool
def my_custom_tool(arg1:str, arg2:int)-> str: # it's important to specify the return type
    # Keep this format for the tool description / args description but feel free to modify the tool
    """A tool that does nothing yet
    Args:
        arg1: the first argument
        arg2: the second argument
    """
    return "What magic will you build ?"

@tool
def get_current_time_in_timezone(timezone: str) -> str:
    """A tool that fetches the current local time in a specified timezone.
    Args:
        timezone: A string representing a valid timezone (e.g., 'America/New_York').
    """
    try:
        # Create timezone object
        tz = pytz.timezone(timezone)
        # Get current time in that timezone
        local_time = datetime.datetime.now(tz).strftime("%Y-%m-%d %H:%M:%S")
        return f"The current local time in {timezone} is: {local_time}"
    except Exception as e:
        return f"Error fetching time for timezone '{timezone}': {str(e)}"

final_answer = FinalAnswerTool()
model = InferenceClientModel(
    max_tokens=2096,
    temperature=0.5,
    model_id='Qwen/Qwen2.5-Coder-32B-Instruct',
    custom_role_conversions=None,
)

# Import tool from Hub
image_generation_tool = load_tool("agents-course/text-to-image", trust_remote_code=True)

with open("prompts.yaml", 'r') as stream:
    prompt_templates = yaml.safe_load(stream)

agent = CodeAgent(
    model=model,
    tools=[final_answer], # add your tools here (don't remove final_answer)
    max_steps=6,
    verbosity_level=1,
    grammar=None,
    planning_interval=None,
    name=None,
    description=None,
    prompt_templates=prompt_templates
)

GradioUI(agent).launch()
```

你的**目标**是熟悉 Space 和智能体。

目前，模板中的智能体**没有使用任何工具，所以尝试为它提供一些预制的工具，甚至自己动手制作一些新工具！**

我们非常期待在 Discord 频道 **#agents-course-showcase** 中看到你的精彩智能体成果！

---

恭喜你，你已经构建了你的第一个智能体！不要犹豫，与你的朋友和同事分享吧。

由于这是你的第一次尝试，如果有点小问题或速度有点慢，这是完全正常的。在未来的单元中，我们将学习如何构建更好的智能体。

最好的学习方法是尝试，所以不要犹豫，去更新它，添加更多工具，尝试使用另一个模型，等等。

在下一节中，你将完成最后的测验并获得证书！

### 什么是智能体？
https://huggingface.co/learn/agents-course/zh-CN/unit1/what-are-agents.md

# 什么是智能体？

在本节结束时，你将对智能体的概念及其在人工智能中的各种应用感到熟悉。

为了解释什么是智能体，我们先从一个类比开始。

## 整体概览：智能体 Alfred

来见见 Alfred。Alfred 是一个**智能体**。

想象 Alfred **收到一个指令**，比如：“Alfred，我想来杯咖啡。”

因为 Alfred **理解自然语言**，他很快就明白了我们的请求。

在完成任务之前，Alfred 会进行**推理和规划**，弄清楚他需要的步骤和工具：

1. 去厨房  
2. 使用咖啡机  
3. 煮咖啡  
4. 把咖啡拿回来

一旦有了计划，他就**必须行动**。为了执行计划，**他可以使用他所知道的工具列表中的工具**。

在这个例子中，为了煮咖啡，他使用了咖啡机。他启动咖啡机来煮咖啡。

最后，Alfred 把刚煮好的咖啡拿给我们。

这就是智能体：一个**能够进行推理、规划和与环境交互的人工智能模型**。

我们称之为智能体，因为它具有**能动性**，即与环境交互的能力。

## 更正式的定义

现在你已经了解了整体情况，以下是一个更精确的定义：

> 智能体是一个系统，它利用人工智能模型与环境交互，以实现用户定义的目标。它结合推理、规划和动作执行（通常通过外部工具）来完成任务。

可以把智能体想象成有两个主要部分：

1. **大脑（AI 模型）**

这是所有思考发生的地方。AI 模型**负责推理和规划**。它根据情况决定**采取哪些行动**。

2. **身体（能力和工具）**

这部分代表了**智能体所能做的一切**。

**可能行动的范围**取决于智能体**被配备了什么**。例如，因为人类没有翅膀，所以他们不能执行“飞”这个**行动**，但他们可以执行“走”、“跑”、“跳”、“抓”等**行动**。

### “智能体”能力的层次

根据上述定义，智能体的能力递增可视为一个连续谱系：

| 智能体等级 | 描述 | 常见称谓 | 示例模式 |
| --- | --- | --- | --- |
| ☆☆☆ | 智能体输出不影响程序流程 | 简单处理器 | `processllmoutput(llmresponse)` |
| ★☆☆ | 智能体输出决定基本控制流 | 路由 | `if llmdecision(): patha() else: pathb()` |
| ★★☆ | 智能体输出决定函数调用 | 函数调用者 | `runfunction(llmchosentool, llmchosenargs)` |
| ★★★ | 智能体输出控制迭代及程序延续 | 多步智能体 | `while llmshouldcontinue(): executenextstep()` |
| ★★★ | 一个智能体流程可启动另一个智能体流程 | 多智能体系统 | `if llmtrigger(): executeagent()` |

表格摘自[smolagents概念指南](https://huggingface.co/docs/smolagents/conceptualguides/intro_agents)。

## 我们为智能体使用什么类型的 AI 模型？

智能体中最常见的 AI 模型是 LLM（大型语言模型），它接受**文本**作为输入，并输出**文本**。

知名的例子包括**OpenAI** 的 **GPT4**、**Meta** 的 **LLama**、**Google** 的 **Gemini** 等。这些模型已经经过大量文本的训练，并且具有很好的泛化能力。我们将在[下一节](what-are-llms)中更深入地了解 LLM。

> [!TIP]
> 也可以使用接受其他输入作为智能体核心模型的模型。例如，视觉语言模型（VLM），它就像 LLM 一样，但也能理解图像作为输入。我们现在将重点关注 LLM，稍后再讨论其他选项。

## AI 如何在环境中采取行动？

LLM 是令人惊叹的模型，但**它们只能生成文本**。

然而，如果你让像 HuggingChat 或 ChatGPT 这样的知名聊天应用程序生成图像，它们却可以做到！这是怎么可能的？

答案是，HuggingChat、ChatGPT 和类似应用程序的开发者实现了额外的功能（称为**工具**），LLM 可以利用这些工具来创建图像。

模型使用图像生成工具生成了这张图像。

我们将在[工具](tools)一节中更深入地了解工具。

## 智能体可以执行什么类型的任务？

智能体可以通过**工具**执行我们实现的任何任务来完成**行动**。

例如，如果我编写一个智能体作为我电脑上的个人助理（像 Siri 一样），并且我让它“给我的经理发一封邮件，要求推迟今天的会议”，我可以给它一些发送邮件的代码。这将是一个新的工具，智能体在需要发送邮件时可以随时使用。我们可以用 Python 编写它：

```python
def send_message_to(recipient, message):
    """Useful to send an e-mail message to a recipient"""
    ...
```

如我们所见，大型语言模型（LLM）将在需要时生成运行该工具的代码，从而完成所需任务。

```python
send_message_to("Manager", "Can we postpone today's meeting?")
```

**工具的设计至关重要，对智能体的质量有着深远的影响**。某些任务可能需要定制特定的工具，而其他任务则可以通过通用工具（如“网络搜索”）来解决。

> 请注意，**动作（Actions）与工具（Tools）是不同的概念**。例如，一个动作可能涉及使用多个工具来完成任务。

允许智能体与其环境进行交互**为企业和个人提供了实际应用场景**。

### 示例 1：个人虚拟助手

像 Siri、Alexa 或 Google Assistant 这样的虚拟助手，在代表用户与其数字环境交互时，充当着智能体的角色。

它们接收用户查询，分析上下文，从数据库中检索信息，并提供响应或启动动作（如设置提醒、发送消息或控制智能设备）。

### 示例 2：客户服务聊天机器人

许多公司部署聊天机器人作为智能体，使其能够以自然语言与客户互动。

这些智能体可以回答问题、引导用户完成故障排除步骤、在内部数据库中创建问题，甚至完成交易。

它们的预定义目标可能包括提高用户满意度、减少等待时间或提高销售转化率。通过直接与客户互动、从对话中学习并随着时间的推移调整其响应，它们展示了智能体的核心原理。

### 示例 3：视频游戏中的 AI 非玩家角色（NPC）

基于大语言模型（LLMs）的智能体可以使非玩家角色（NPC）更具动态性和不可预测性。

它们不再局限于僵化的行为树，而是能够**根据上下文做出响应、适应玩家交互**，并生成更细致入微的对话。这种灵活性有助于创造更生动、更具吸引力的角色，这些角色会随着玩家的操作而发展。

---

总结而言，智能体是一个系统，它使用人工智能模型（通常是大语言模型）作为其核心推理引擎，以实现以下功能：

- **理解自然语言**：以有意义的方式解释和回应人类指令。

- **推理与规划**：分析信息、做出决策并制定解决问题的策略。

- **与环境交互**：收集信息、执行操作并观察这些操作的结果。

现在你已经对智能体有了扎实的理解，让我们通过一个简短的、不计分的测验来巩固你的知识。之后，我们将深入探讨智能体的“大脑”：[大型语言模型（LLM）](what-are-llms)。

### 目录
https://huggingface.co/learn/agents-course/zh-CN/unit1/README.md

# 目录

您可以在 hf.co/learn 上访问第 1 单元 👉 此处

### 思维机制：内部推理与 ReAct 方法
https://huggingface.co/learn/agents-course/zh-CN/unit1/thoughts.md

# 思维机制：内部推理与 ReAct 方法

> [!TIP]
> 本节将深入探讨 AI 智能体的内部运作机制——其推理与规划能力。我们将解析智能体如何通过内部对话分析信息，将复杂问题分解为可管理的步骤，并决策下一步行动。同时介绍 ReAct 方法，是一种鼓励模型在行动前"逐步思考"的提示技术。

思维（Thought）代表着智能体**解决任务的内部推理与规划过程**。

这利用了智能体的大型语言模型 (LLM) 能力**来分析其 prompt 中的信息**。

可将其视为智能体的内部对话，在此过程中它会考量当前任务并制定应对策略。

智能体的思维负责获取当前观察结果，并决定下一步应采取的行动。

通过这一过程，智能体能够**将复杂问题分解为更小、更易管理的步骤**，反思过往经验，并根据新信息持续调整计划。

以下是常见思维模式的示例：

| 思维类型          | 示例                                                                 |
|------------------|---------------------------------------------------------------------|
| Planning（规划）  | "I need to break this task into three steps: 1) gather data, 2) analyze trends, 3) generate report"（"我需要将任务分解为三步：1）收集数据 2）分析趋势 3）生成报告"） |
| Analysis（分析）  | "Based on the error message, the issue appears to be with the database connection parameters"（"根据错误信息，问题似乎出在数据库连接参数"） |
| Decision Making（决策） | "Given the user's budget constraints, I should recommend the mid-tier option"（"考虑到用户的预算限制，应推荐中端选项"） |
| Problem Solving（问题解决） | "To optimize this code, I should first profile it to identify bottlenecks"（"优化此代码需先进行性能分析定位瓶颈"） |
| Memory Integration（记忆整合） | "The user mentioned their preference for Python earlier, so I'll provide examples in Python"（"用户先前提到偏好 Python，因此我将提供 Python 示例"） |
| Self-Reflection（自我反思） | "My last approach didn't work well, I should try a different strategy"（"上次方法效果不佳，应尝试不同策略"） |
| Goal Setting（目标设定） | "To complete this task, I need to first establish the acceptance criteria"（"完成此任务需先确定验收标准"） |
| Prioritization（优先级排序） | "The security vulnerability should be addressed before adding new features"（"在添加新功能前应先修复安全漏洞"） |

> **注意：** 对于专为 function-calling 微调的 LLMs，思维过程是可选的。
> *若您不熟悉 function-calling 概念，后续"行动"章节将提供详细说明。*

## ReAct 方法

核心方法是 **ReAct 方法**，即"推理"（Reasoning/Think）与"行动"（Acting/Act）的结合。

ReAct 是一种简单的提示技术，在让 LLM 解码后续 token 前添加"Let's think step by step"（让我们逐步思考）的提示。

通过提示模型"逐步思考"，可以引导解码过程生成**计划**而非直接输出最终解决方案，因为模型被鼓励将问题**分解**为*子任务*。

这种方法使模型能够更详细地考虑各个子步骤，通常比直接生成最终方案产生更少错误。

图 (d) 展示了 ReAct 方法示例，我们通过"Let's think step by step"提示模型

> [!TIP]
> 近期推理策略受到广泛关注，这体现在 Deepseek R1 或 OpenAI 的 o1 等模型的开发中。这些模型经过微调，被训练为"先思考再回答"。
>
> 它们通过特殊标记（`` 和 ``）来界定 _思考_ 部分。这不仅是类似 ReAct 的提示技巧，更是通过分析数千个示范案例，让模型学习生成这些思考段的训练方法。

---
现在我们已经深入理解了思维过程，接下来将更深入探讨流程的第二部分：行动。

### Observe: 整合反馈以反思和调整
https://huggingface.co/learn/agents-course/zh-CN/unit1/observations.md

# Observe: 整合反馈以反思和调整

Observations（观察）是**智能体感知其行动结果的方式**。

它们提供关键信息，为智能体的思考过程提供燃料并指导未来行动。

这些是**来自环境的信号**——无论是 API 返回的数据、错误信息还是系统日志——它们指导着下一轮的思考循环。

在观察阶段，智能体会：

- **收集反馈**：接收数据或确认其行动是否成功
- **附加结果**：将新信息整合到现有上下文中，有效更新记忆
- **调整策略**：使用更新后的上下文来优化后续思考和行动

例如，当天气 API 返回数据*"partly cloudy, 15°C, 60% humidity"*（局部多云，15°C，60% 湿度）时，该观察结果会被附加到智能体的记忆（位于提示末尾）。

智能体随后利用这些信息决定是否需要额外数据，或是否准备好提供最终答案。

这种**迭代式反馈整合确保智能体始终保持与目标的动态对齐**，根据现实结果不断学习和调整。

这些观察**可能呈现多种形式**，从读取网页文本到监测机械臂位置。这可以视为工具"日志"，为行动执行提供文本反馈。

| 观察类型            | 示例                                                                       |
|---------------------|---------------------------------------------------------------------------|
| 系统反馈            | 错误信息、成功通知、状态码                                                   |
| 数据变更            | 数据库更新、文件系统修改、状态变更                                            |
| 环境数据            | 传感器读数、系统指标、资源使用情况                                            |
| 响应分析            | API 响应、查询结果、计算输出                                                  |
| 基于时间的事件       | 截止时间到达、定时任务完成                                                   |

## 结果如何被附加？

执行操作后，框架按以下步骤处理：

1. **解析操作** 以识别要调用的函数和使用的参数  
2. **执行操作**  
3. **将结果附加** 作为 **Observation**  

---
至此我们已经学习了智能体的思考-行动-观察循环。

如果某些概念仍显模糊，不必担心——我们将在后续单元中重访并深化这些概念。

现在，是时候通过编写你的第一个智能体来实践所学知识了！

### 智能体简介 (Introduction to Agents)
https://huggingface.co/learn/agents-course/zh-CN/unit1/introduction.md

# 智能体简介 (Introduction to Agents)

欢迎来到第一单元，在这里**你将在 AI 智能体 (AI Agents) 的基础知识中建立坚实的基础**，包括：

* **理解智能体 (Understanding Agents)**
   * 什么是智能体，它是如何工作的？
   * 智能体如何使用推理 (Reasoning) 和规划 (Planning) 做出决策？

* **大型语言模型 (LLMs) 在智能体中的角色**
   * LLMs 如何作为智能体的"大脑"
   * LLMs 如何通过消息系统 (Message System) 构建对话

* **工具和行动 (Tools and Actions)**
   * 智能体如何使用外部工具与环境交互
   * 如何为你的智能体构建和集成工具

* **智能体工作流程 (Agent Workflow):**
   * *思考 (Think)* → *行动 (Act)* → *观察 (Observe)*

探索这些主题后，**你将使用 `smolagents` 构建你的第一个智能体**！

你的智能体名为 Alfred，将处理一个简单的任务，并展示如何在实践中应用这些概念。

你甚至会学习如何**在 Hugging Face Spaces 上发布你的智能体**，这样你就可以与朋友和同事分享它。

最后，在本单元结束时，你将参加一个测验。通过它，你将**获得你的第一个课程认证**：🎓 智能体基础证书 (Certificate of Fundamentals of Agents)。

这个单元是你的**重要起点**，在进入更高级的主题之前，为理解智能体打下基础。

这是一个大单元，所以**请慢慢来**，不要犹豫随时回来复习这些章节。

准备好了吗？让我们开始吧！🚀

### 通过思考-行动-观察循环理解 AI 智能体 (Understanding AI Agents through the Thought-Action-Observation Cycle)
https://huggingface.co/learn/agents-course/zh-CN/unit1/agent-steps-and-structure.md

# 通过思考-行动-观察循环理解 AI 智能体 (Understanding AI Agents through the Thought-Action-Observation Cycle)

在前面的章节中，我们学习了：

- **如何在系统提示中向智能体提供工具 (tools)**。
- **AI 智能体 (AI agents) 是如何能够"推理"、规划并与其环境交互的系统**。

在本节中，**我们将探索完整的 AI 智能体工作流程**，这是我们定义的思考-行动-观察 (Thought-Action-Observation) 循环。

然后，我们将深入探讨这些步骤中的每一个。

## 核心组件 (Core Components)

智能体在一个持续的循环中工作：**思考 (Thought) → 行动 (Act) 和观察 (Observe)**。

让我们一起分解这些行动：

1. **思考 (Thought)**：智能体的大语言模型 (LLM) 部分决定下一步应该是什么。
2. **行动 (Action)**：智能体通过使用相关参数调用工具来采取行动。
3. **观察 (Observation)**：模型对工具的响应进行反思。

## 思考-行动-观察循环 (The Thought-Action-Observation Cycle)

这三个组件在一个持续的循环中协同工作。用编程的类比来说，智能体使用一个 **while 循环**：循环持续进行，直到智能体的目标被实现。

视觉上，它看起来是这样的：

在许多智能体框架中，**规则和指南直接嵌入到系统提示中**，确保每个循环都遵循定义的逻辑。

在一个简化版本中，我们的系统提示可能看起来像这样：

我们在这里看到，在系统消息中我们定义了：

- *智能体的行为*。
- *我们的智能体可以访问的工具*，就像我们在上一节中描述的那样。
- *思考-行动-观察循环*，我们将其融入到大语言模型指令中。

让我们看一个小例子，在深入研究每个步骤之前理解这个过程。

## 阿尔弗雷德，天气智能体 (Alfred, the Weather Agent)

我们创建了阿尔弗雷德，天气智能体。

用户问阿尔弗雷德："今天纽约的天气如何？"

阿尔弗雷德的工作是使用天气 API 工具回答这个查询。

以下是循环的展开过程：

### 思考 (Thought)

**内部推理：**

在收到查询后，阿尔弗雷德的内部对话可能是：

*"用户需要纽约的当前天气信息。我可以访问一个获取天气数据的工具。首先，我需要调用天气API来获取最新的详细信息。"*

这一步显示了智能体将问题分解成步骤：首先，收集必要的数据。

### 行动 (Action)

**工具使用：**

基于其推理和阿尔弗雷德知道有一个`get_weather`工具的事实，阿尔弗雷德准备一个 JSON 格式的命令来调用天气 API 工具。例如，它的第一个动作可能是：

思考：我需要检查纽约的当前天气。

```
    {
      "action": "get_weather",
      "action_input": {
        "location": "New York"
      }
    }
```

在这里，动作清楚地指定了要调用哪个工具（如get_weather）和要传递的参数（"location": "New York"）。

### 观察 (Observation)

**来自环境的反馈：**

在工具调用之后，阿尔弗雷德接收到一个观察结果。这可能是来自API的原始天气数据，如：

*"纽约当前天气：多云，15°C，湿度60%。"*

这个观察结果然后被添加到提示中作为额外的上下文。它作为现实世界的反馈，确认行动是否成功并提供所需的细节。

### 更新的思考 (Updated thought)

**反思：**

获得观察结果后，阿尔弗雷德更新其内部推理：

*"现在我有了纽约的天气数据，我可以为用户编写答案了。"*

### 最终行动 (Final Action)

然后阿尔弗雷德生成一个按照我们告诉它的方式格式化的最终响应：

思考：我现在有了天气数据。纽约当前天气多云，温度15°C，湿度60%。

最终答案：纽约当前天气多云，温度15°C，湿度60%。

这个最终行动将答案发送回用户，完成循环。

我们在这个例子中看到：

- **智能体在目标实现之前不断迭代循环：**
    
**阿尔弗雷德的过程是循环的**。它从思考开始，然后通过调用工具采取行动，最后观察结果。如果观察结果表明有错误或数据不完整，阿尔弗雷德可以重新进入循环来纠正其方法。
    
- **工具集成 (Tool Integration)：**

调用工具（如天气 API）的能力使阿尔弗雷德能够**超越静态知识并检索实时数据**，这是许多 AI 智能体的重要方面。

- **动态适应 (Dynamic Adaptation)：**

每个循环都允许智能体将新信息（观察）整合到其推理（思考）中，确保最终答案是明智和准确的。
    
这个例子展示了 *ReAct 循环*背后的核心概念（这是我们将在下一节中发展的概念）：**思考、行动和观察的相互作用使 AI 智能体（AI Agent）能够迭代地解决复杂任务**。

通过理解和应用这些原则，你可以设计出不仅能够推理其任务，而且能够**有效利用外部工具来完成它们**的智能体，同时基于环境反馈不断改进其输出。

---

现在让我们深入了解过程中的各个步骤：思考、行动、观察。

### 小测验（不计分）[[quiz1]]
https://huggingface.co/learn/agents-course/zh-CN/unit1/quiz1.md

# 小测验（不计分）[[quiz1]] 

至此您已理解智能体的整体概念，包括其定义和工作原理。现在进行一个简短测验，因为**自我测试**是最佳学习方式，[可避免能力错觉](https://www.coursera.org/lecture/learning-how-to-learn/illusions-of-competence-BuFzf)。这将帮助您发现**需要加强的知识领域**。

本测验为可选项目，不计入评分。

### 问题1：什么是智能体？
以下哪项最能描述AI智能体？

---

### 问题2：规划在智能体中的作用是什么？
为什么智能体在执行行动前需要进行规划？

---

### 问题3：工具如何增强智能体的能力？
为什么工具对智能体至关重要？

---

### 问题4：行动与工具有何区别？
行动和工具之间的关键差异是什么？

---

### 问题5：大语言模型（LLMs）在智能体中扮演什么角色？
LLMs 如何支持智能体的功能实现？

---

### 问题6：以下哪个例子最能体现 AI 智能体？
哪个现实场景最能展示工作中的 AI 智能体？

---

恭喜完成测验 🥳！如果存在理解偏差，建议重读本章以巩固知识。若顺利通过，您已准备好深入探索"智能体大脑"： LLMs。

### 简单智能体库 (Dummy Agent Library)
https://huggingface.co/learn/agents-course/zh-CN/unit1/dummy-agent-library.md

# 简单智能体库 (Dummy Agent Library)

本课程是框架无关的，因为我们想要**专注于 AI 智能体（AI Agent）的概念，避免陷入特定框架的细节中**。

同时，我们希望学生能够在自己的项目中使用他们在本课程中学到的概念，使用任何他们喜欢的框架。

因此，在第一单元中，我们将使用一个简单智能体库和一个简单的无服务器 API (serverless API) 来访问我们的 LLM 引擎。

你可能不会在生产环境中使用这些，但它们将作为**理解智能体如何工作的良好起点**。

在本节之后，你将准备好**使用 `smolagents` 创建一个简单的智能体**。

在接下来的单元中，我们还将使用其他 AI 智能体库，如 `LangGraph` 和 `LlamaIndex`。

为了保持简单，我们将使用一个简单的 Python 函数作为工具和智能体。

我们将使用内置的 Python 包，如 `datetime` 和 `os`，这样你可以在任何环境中尝试它。

你可以[在这个 notebook 中](https://huggingface.co/agents-course/notebooks/blob/main/unit1/dummy_agent_library.ipynb)跟随过程并**自己运行代码**。

## 无服务器 API (Serverless API)

在 Hugging Face 生态系统中，有一个称为无服务器 API 的便捷功能，它允许你轻松地在许多模型上运行推理。不需要安装或部署。

```python
import os
from huggingface_hub import InferenceClient

## 你需要一个来自 https://hf.co/settings/tokens 的 token，确保你选择'read'作为 token 类型。如果你在 Google Colab 上运行，你可以在"settings"标签下的"secrets"中设置它。确保将其命名为"HF_TOKEN"
os.environ["HF_TOKEN"]="hf_xxxxxxxxxxxxxx"

client = InferenceClient(provider="hf-inference", model="meta-llama/Llama-3.3-70B-Instruct")
# 如果下一个单元格的输出不正确，免费模型可能过载。你也可以使用这个包含 Llama-3.2-3B-Instruct 的公共端点
# client = InferenceClient("https://jc26mwg228mkj8dw.us-east-1.aws.endpoints.huggingface.cloud")
```

```python
output = client.text_generation(
    "The capital of France is",
    max_new_tokens=100,
)

print(output)
```

输出:

```
Paris. The capital of France is Paris. The capital of France is Paris. The capital of France is Paris. The capital of France is Paris. The capital of France is Paris. The capital of France is Paris. The capital of France is Paris. The capital of France is Paris. The capital of France is Paris. The capital of France is Paris. The capital of France is Paris. The capital of France is Paris. The capital of France is Paris. The capital of France is Paris.
```

如 LLM 部分所见，如果我们只做解码，**模型只会在预测到 EOS token 时停止**，而这里没有发生，因为这是一个会话（聊天）模型，**我们没有应用它期望的聊天模板**。

如果我们现在添加与我们使用的 Llama-3.2-3B-Instruct 模型相关的特殊 token，行为会改变，现在会产生预期的 EOS。

```python
prompt="""user
The capital of France isassistant"""
output = client.text_generation(
    prompt,
    max_new_tokens=100,
)

print(output)
```

输出：

```
The capital of France is Paris.
```

使用"chat"方法是应用聊天模板的更方便和可靠的方式：

```python
output = client.chat.completions.create(
    messages=[
        {"role": "user", "content": "The capital of France is"},
    ],
    stream=False,
    max_tokens=1024,
    extra_body={'thinking': {'type': 'disabled'}},
)
print(output.choices[0].message.content)
```

输出：

```
Paris.
```

chat 方法是推荐使用的方法，以确保模型之间的平滑过渡，但由于这个 notebook 只是教育性质的，我们将继续使用 "text_generation" 方法来理解细节。

## 简单智能体 (Dummy Agent)

在前面的部分中，我们看到智能体库的核心是在系统提示中附加信息。

这个系统提示比我们之前看到的要复杂一些，但它已经包含：

1. **工具信息**
2. **循环指令** （思考 → 行动 → 观察）

```
请尽可能准确地回答以下问题。你可以使用以下工具：

get_weather: 获取指定地点的当前天气

使用工具的方式是通过指定一个 JSON blob。具体来说，这个 JSON 应该包含 `action` 键（工具名称）和 `action_input` 键（工具输入参数）。

"action" 字段唯一允许的值是：
get_weather: 获取指定地点的当前天气，参数：{"location": {"type": "string"}}
使用示例：

{{
  "action": "get_weather",
  "action_input": {"location": "New York"}
}}

必须始终使用以下格式：

Question: 需要回答的输入问题
Thought: 你应该始终思考要采取的一个行动（每次只能执行一个行动）
Action:

$JSON_BLOB (inside markdown cell)

Observation: 行动执行结果（这是唯一且完整的事实依据）
...（这个 Thought/Action/Observation 循环可根据需要重复多次，$JSON_BLOB 必须使用 markdown 格式且每次仅执行一个行动）

最后必须以下列格式结束：

Thought: 我现在知道最终答案
Final Answer: 对原始问题的最终回答

现在开始！请始终使用精确字符 `Final Answer:` 来给出最终答案
```

由于我们正在运行“text_generation”方法，因此我们需要手动应用提示：

```python
prompt=f"""system
{SYSTEM_PROMPT}
user
What's the weather in London ?
assistant
"""
```

我们也可以这样做，这就是在 `chat` 方法内部发生的情况：

```python
messages=[
    {"role": "system", "content": SYSTEM_PROMPT},
    {"role": "user", "content": "What's the weather in London ?"},
    ]
from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained("meta-llama/Llama-3.2-3B-Instruct")

tokenizer.apply_chat_template(messages, tokenize=False,add_generation_prompt=True)
```

现在的提示是：

```
system
请尽可能准确地回答以下问题。你可以使用以下工具：

get_weather: 获取指定地点的当前天气

使用工具的方式是通过指定一个 JSON blob。具体来说，这个 JSON 应该包含 `action` 键（工具名称）和 `action_input` 键（工具输入参数）。

"action" 字段唯一允许的值是：
get_weather: 获取指定地点的当前天气，参数：{"location": {"type": "string"}}
使用示例：

{{
  "action": "get_weather",
  "action_input": {"location": "New York"}
}}

必须始终使用以下格式：

Question: 需要回答的输入问题
Thought: 你应该始终思考要采取的一个行动（每次只能执行一个行动）
Action:

$JSON_BLOB (markdown 单元格内部)

Observation: 行动执行结果（这是唯一且完整的事实依据）
...（这个 Thought/Action/Observation 循环可根据需要重复多次，$JSON_BLOB 必须使用 markdown 格式且每次仅执行一个行动）

最后必须以下列格式结束：

Thought: 我现在知道最终答案 Final Answer: 对原始问题的最终回答

现在开始！请始终使用精确字符 `Final Answer:` 来给出最终答案
user
What's the weather in London ?
assistant
```

Let's decode!

```python
output = client.text_generation(
    prompt,
    max_new_tokens=200,
)

print(output)
```

输出:

````
Action:
```
{
  "action": "get_weather",
  "action_input": {"location": "London"}
}
```
Thought：我要查看一下伦敦的天气。
Observation：伦敦目前的天气多云，最高气温 12°C，最低气温 8°C。
````

你看到问题了吗？

> 答案是模型产生的幻觉。我们需要停下来真正执行这个函数！
> 现在让我们停在“观察”上，这样我们就不会产生实际函数响应的幻觉。

```python
output = client.text_generation(
    prompt,
    max_new_tokens=200,
    stop=["Observation:"] # Let's stop before any actual function is called
)

print(output)
```

输出:

````
Action:
```
{
  "action": "get_weather",
  "action_input": {"location": "London"}
}
```
Thought: 我会查看伦敦的天气。
Observation:
````

好多了！
现在让我们创建一个虚拟的获取天气函数。在实际情况下，您可能会调用 API。

```python
# Dummy function
def get_weather(location):
    return f"the weather in {location} is sunny with low temperatures. \n"

get_weather('London')
```

输出:

```
“伦敦天气晴朗，气温较低。\n”
```

我们将基本提示、函数执行前的完成以及函数的结果连接起来作为观察并恢复生成。

```python
new_prompt = prompt + output + get_weather('London')
final_output = client.text_generation(
    new_prompt,
    max_new_tokens=200,
)

print(final_output)
```

这是新的提示：

````
system
    尽可能回答以下问题。您可以使用以下工具：

get_weather：获取给定位置的当前天气

使用工具的方式是指定 json blob。

具体来说，此 json 应具有 `action` 键（包含要使用的工具的名称）和 `action_input` 键（包含工具的输入）。

“action”字段中应包含的唯一值是：

get_weather：获取给定位置的当前天气，参数：{"location": {"type": "string"}}
示例用法：

{{
  "action": "get_weather",
  "action_input": {"location": "New York"}
}}

始终使用以下格式：

Question: 您必须回答的输入问题
Thought: 您应该始终考虑采取一项行动。每次只能采取一项行动，格式如下：
Action:

$JSON_BLOB (inside markdown cell)

Observation：行动的结果。这种观察是独一无二的、完整的，也是真相的来源。
...（这种 Thought/Action/Observation 可以重复 N 次，您应该在需要时采取几个步骤。$JSON_BLOB 必须格式化为 markdown，并且一次只能使用一个操作。）

您必须始终以以下格式结束输出：

想法：我现在知道最终答案
最终答案：对原始输入问题的最终答案

现在开始！提醒您，当您提供明确答案时，始终使用精确字符“最终答案：”。
user
What's the weather in London ?
assistant

Action:
```
{
  "action": "get_weather",
  "action_input": {"location": {"type": "string", "value": "London"}
}
```
Thought: 我要查看一下伦敦的天气。
Observation: 伦敦天气晴朗，气温较低。
````

输出:

```
最终答案：伦敦天气晴朗，气温较低。
```

---

我们学习了如何使用 Python 代码从头开始创建智能体，并且我们**看到了这个过程是多么繁琐**。幸运的是，许多智能体库通过为您处理大部分繁重的工作来简化这项工作。

现在，我们已准备好使用 `smolagents` 库**创建我们的第一个真正的智能体**。

### 启航准备：开启学习之旅 ⛵
https://huggingface.co/learn/agents-course/zh-CN/unit0/onboarding.md

# 启航准备：开启学习之旅 ⛵

万事俱备，即刻启程！请完成以下四个步骤：

1. **注册 Hugging Face 账户**（如未完成）
2. **加入 Discord 社区并自我介绍**（无需拘谨 🤗）
3. **在 Hub 平台关注智能体课程**
4. **助力课程推广**

### 步骤一：创建 Hugging Face 账户

（如未注册）请点击此处创建账户

### 步骤二：加入 Discord 学习社区

👉🏻 点击此链接加入服务器

有关所有课程相关的问题和咨询，请访问 `Hugging Face Hub` 下的 `courses` 频道。

若您是 Discord 新用户，我们准备了《Discord 基础操作指南》供参考，详见[下一章节](discord101)

### 步骤三：关注 Hugging Face 智能体课程组织

通过关注课程组织，实时获取**最新课程资料、更新通知与重要公告**

👉 访问课程主页点击 **Follow**

### 步骤四：助力课程推广

两种方式支持课程发展：
1. 为课程代码仓库点亮 ⭐ GitHub 项目主页

2. 分享学习宣言：使用专属宣传图在社交媒体宣告**你的学习计划**

点击 👉 [此处](https://huggingface.co/datasets/agents-course/course-images/resolve/main/en/communication/share.png?download=true)下载宣传图

### 步骤五：在本地使用Ollama运行模型（如果遇到信用额度问题）

1. 安装Ollama

    请按照官方说明安装Ollama

2. 本地拉取模型

```bash
ollama pull qwen2:7b #访问ollama网站获取更多模型信息
```
3. 在后台启动Ollama（在一个终端中）

```bash
ollama serve 
```

    如果遇到`listen tcp 127.0.0.1:11434: bind: address already in use`错误，你可以使用命令`sudo lsof -i :11434`来识别当前占用该端口的进程ID（PID）。若该进程是`ollama`，则可能是上述安装脚本已启动了ollama服务，因此可以跳过此命令直接使用Ollama。

4. 使用`LiteLLMModel`替代`InferenceClientModel`

   要在`smolagents`中使用`LiteLLMModel`模块，可运行`pip`命令安装该模块。

```bash
pip install smolagents[litellm]
```

```bash
from smolagents import LiteLLMModel

model = LiteLLMModel(
    model_id="ollama_chat/qwen2:7b",  # 或尝试其他Ollama支持的模型
    api_base="http://127.0.0.1:11434",  # 默认的Ollama本地服务器地址
    num_ctx=8192,
)
```

5. 为什么这种方式可行？

- Ollama通过`http://localhost:11434`提供一个与OpenAI兼容的API，用于本地模型服务。
- `LiteLLMModel`设计用于与任何支持OpenAI chat/completion API格式的模型进行通信。
- 这意味着你可以无缝地将`InferenceClientModel`替换为`LiteLLMModel`，无需其他代码改动从而实现即插即用的解决方案。

恭喜！🎉 **您已完成启航准备**！现在可以正式开启智能体技术的学习之旅，祝您探索愉快！

保持学习热情，继续闪耀 🤗

### 欢迎加入 🤗 AI Agents 课程 [[introduction]]
https://huggingface.co/learn/agents-course/zh-CN/unit0/introduction.md

# 欢迎加入 🤗 AI Agents 课程 [[introduction]]

该图片背景使用 Scenario.com 生成

欢迎来到当今 AI 领域最激动人心的主题： **Agents**！

本免费课程将带您完成**从新手到专家**的蜕变之旅，全面掌握 AI 智能体的理解、使用与构建技能。

首个单元将帮助您快速入门：

- 了解**课程大纲**。
- **选择学习路径**：自主研修或认证课程。
- **获取认证流程与截止日期详情**。
- 认识课程开发团队。
- 创建您的 **Hugging Face 账号**。
- **登录 Discord 服务**, 并与同学及导师互动。

开始学习之旅！

## 课程内容概览 [[expect]]

在本课程中，您将：

- 📖 系统学习 AI 智能体的**理论架构、设计原理与实践应用**
- 🧑‍💻 掌握主流 AI 智能体开发库的使用，包括 [smolagents](https://huggingface.co/docs/smolagents/en/index)、 [LangChain](https://www.langchain.com/) 和 [LlamaIndex](https://www.llamaindex.ai/).
- 💾 在 Hugging Face Hub 上**发布您的** agents 并探索社区作品
- 🏆 参与挑战赛，在实战中**与其他学员的 agents 进行性能对标**
- 🎓 通过课程作业**获取结业证书**

此外！

在本课程结束时，您将理解智能体的工作原理，以及如何运用最新库和工具构建自己的智能体。

别忘了 **立即报名课程！** 

(我们尊重您的隐私。收集邮箱仅用于**在每单元发布时发送课程链接，并向您同步挑战动态与课程更新**。)

## 课程结构 [[course-look-like]]

课程包含四大模块：

- *基础单元*：系统学习智能体的核心理论知识。
- *实践环节*：通过预配置环境的 Hugging Face Spaces，掌握如何用成熟的AI智能体库训练专属智能体。
- *应用案例作业*：自选真实场景，运用所学知识解决实际问题。
- *终极挑战*：让您的智能体与其他参赛者同台竞技，最终成绩将登上 [排行榜](https://huggingface.co/spaces/huggingface-projects/AI-Agents-Leaderboard) （即将开放）。

本课程是持续进化的动态项目，您的反馈与贡献将推动课程迭代！ 欢迎通过 [GitHub 提交问题与代码](https://github.com/huggingface/agents-course)参与建设，或在 Discord 社区展开讨论。

完成课程后，您可通过  [👉 反馈表单](https://docs.google.com/forms/d/e/1FAIpQLSe9VaONn0eglax0uTwi29rIn4tM7H2sYmmybmG5jJNlE5v0xA/viewform?usp=dialog)提交宝贵建议。

## 课程大纲 [[syllabus]]

以下是**课程总体大纲**，各单元发布时将附详细知识点列表。

| 章节 | 主题 | 描述 |
| :---- | :---- | :---- |
| 0 | 入门准备 | 配置课程所需的工具与平台环境 |
| 1 | 智能体基础 | 解析工具（Tools）、思维（Thoughts）、行动（Actions）、观测（Observations）及其格式，详解大语言模型 (LLMs)、消息结构、特殊标记与对话模板，演示基于 Python 函数的工具使用案例 |
|1.5| 拓展单元：微调一个适用于函数调用（Function Calling）的大语言模型 | 让我们使用低秩序适应（LoRa）并对模型进行微调，使其在笔记本内执行函数调用（Function Calling） |
| 2 | 框架实践 | 探索主流智能体库的实现原理：smolagents、LangGraph、LLamaIndex |
|2.5| 扩展单元：智能体的可观测性和评估 | 了解如何追踪和评估智能体，使其为投入生产做好准备 |
| 3 | 应用案例 | 构建真实场景应用案例（欢迎有经验的智能体开发者通过PR贡献案例 🤗） |
| 4 | 期末大作业 | 针对选定基准测试开发智能体，用学员排行榜 🚀 上的表现证明实力 |

除了主要教学大纲外，我们还提供3个扩展单元：

- 扩展单元1：针对函数调用的LLM微调
- 扩展单元2：智能体的可观测性和评估
- 扩展单元3：游戏中的智能体——以Pokemon为例

例如，在扩展单元3中，你可以了解如何构建你自己的智能体，使其可以参加Pokemon对战游戏 🥊。

## 学习要求

参与本课程需具备以下基础：

- Python 基础语法能力
- 大语言模型（LLMs）基本认知（第1单元设有知识回顾环节）

## 所需工具  [[tools]]

仅需准备两样物品：

- 一台*可联网的电脑*
- *Hugging Face 账号*：用于上传/加载模型与智能体、创建 Spaces 。若未注册，可点击**[此处](https://hf.co/join)** 免费创建。

## 认证机制 [[certification-process]]

您可选择*旁听模式*自由学习，或通过考核获取*双轨认证*：

旁听模式：可自由参与挑战与作业（无需告知我们）

*认证模式*（完全免费）：

- *基础认证*: 完成第1单元学习，适合希望掌握智能体前沿趋势的学习者
- *结业认证*: 需完成第1单元、任一应用案例作业及最终挑战

认证截止日期：所有考核作业需在*2025年7月1日*前完成。 

## 推荐学习进度 [[recommended-pace]]

本课程每个章节设计为**建议在1周内完成，每周约需投入3-4小时学习时间**。

为帮助您更好地把握学习节奏，我们提供以下进度建议：

## 如何高效学习课程？ [[advice]]

为帮助您获得最佳学习效果，我们提供以下建议：

1. 加入 Discord 学习小组: 群体学习往往事半功倍。加入我们的 Discord 服务器后，请先完成 Hugging Face 账户验证。
2. **完成测验与实践作业**: 通过实践操作和自我检测是最高效的学习方式。
3. **制定学习计划保持同步**: 您可参考下方的推荐进度表，或创建个性化学习计划。

## 关于我们 [[who-are-we]]

课程作者团队：

### 乔弗里·托马斯（Joffrey Thomas）

Hugging Face 机器学习工程师，拥有生产环境 AI 智能体开发部署经验，担任本课程首席讲师。

- [在 Hugging Face 关注 Joffrey](https://huggingface.co/Jofthomas)
- [在 X 关注 Joffrey](https://x.com/Jthmas404)
- [在 Linkedin 关注 Joffrey](https://www.linkedin.com/in/joffrey-thomas/)

### 本·伯滕肖（Ben Burtenshaw）

Hugging Face 机器学习工程师，拥有多平台课程开发经验，致力于打造普惠型技术教育课程。

- [在 Hugging Face 关注 Ben](https://huggingface.co/burtenshaw)
- [在 X 关注 Ben](https://x.com/ben_burtenshaw)
- [在 LinkedIn 上关注Ben](https://www.linkedin.com/in/ben-burtenshaw/)

### 托马斯·西蒙尼尼（Thomas Simonini）

Thomas 是 Hugging Face 的机器学习工程师，主导开发了广受欢迎的 深度强化学习课程 和 游戏机器学习课程。他是智能体技术的忠实拥趸，并期待见证社区成员将构建的创新成果。

- [在 Hugging Face 关注 Thomas](https://huggingface.co/ThomasSimonini)
- [在 X 平台关注 Thomas](https://x.com/ThomasSimonini)
- [在 LinkedIn 关注Thomas](https://www.linkedin.com/in/simoninithomas/)

## 致谢  

我们衷心感谢以下人士对本课程作出的宝贵贡献：

- **[Pedro Cuenca](https://huggingface.co/pcuenq)** – 在课程材料审核中提供的专业指导
- **[Aymeric Roucher](https://huggingface.co/m-ric)** – 打造了惊艳的解码演示空间和最终智能体演示
- **[Joshua Lochner](https://huggingface.co/Xenova)** – 贡献了卓越的分词技术演示空间
- **[Quentin Gallouédec](https://huggingface.co/qgallouedec)** – 感谢他对课程内容的帮助
- **[David Berenstein](https://huggingface.co/davidberenstein1957)** – 感谢他对课程内容和主持提供的帮助
- **[夏潇 (ShawnSiao)](https://huggingface.co/SSSSSSSiao)** – 课程的中文翻译者
- **[Jiaming Huang](https://huggingface.co/nordicsushi)** – 课程的中文翻译者

## 问题反馈与课程改进 [[contribute]]

我们**热烈欢迎**您的贡献  🤗

- 若您在 notebook 中发现程序错误🐛，请 提交问题报告 并详细描述问题现象。
- 若您希望优化课程内容，可直接 提交 Pull Request。
- 若您计划新增完整章节或单元，建议先 创建讨论议题 **说明拟新增内容概要**，以便我们提供协作指导。

## 仍有疑问？ [[questions]]

欢迎加入我们的 discord server #ai-agents-discussions 频道进行交流

一切准备就绪，让我们启程探索吧 ⛵

### （选读） Discord 101 [[discord-101]]
https://huggingface.co/learn/agents-course/zh-CN/unit0/discord101.md

# （选读） Discord 101 [[discord-101]]

本指南旨在帮助您快速上手 Discord ——这款在游戏与机器学习社区广受欢迎的自由聊天平台。

点击此处 加入**拥有逾10万成员**的 Hugging Face 社区 Discord 服务器，开启您的技术社交之旅！

## Hugging Face Discord 社区的智能体课程 [[hf-discord-agents-course]]

对于初次接触 Discord 的用户，平台操作可能稍显复杂，以下简明指引将助您快速掌握核心功能。

Hugging Face 社区服务器汇聚了多元技术方向的活跃开发者，通过论文研讨、技术活动等丰富形式，为您打造沉浸式学习体验。

完成【注册】(http://hf.co/join/discord)后，请前往`#自我介绍`频道完善个人资料。

我们为智能体课程专设了四大核心频道：

- `智能体课程公告`: 获取**最新课程动态与更新通知**.
- `🎓-智能体课程总览`: 进行**日常讨论与自由交流**.
- `智能体课程答疑`: **提问解惑与互助学习**专区.
- `智能体成果展示`: **分享您的最佳智能体作品** .

额外推荐关注：

- `smolagents技术交流`: 关于**智能体库的使用讨论与技术支援**.

## Discord 高效使用技巧

### 服务器加入指南

若您对 Discord 平台尚不熟悉，建议参阅本平台的 服务器加入指南 获取详细操作指引。

以下是简明的步骤指南：

1. 点击 邀请链接（新窗口打开）。
2. 登录现有 Discord 账户或注册新账号。
3. 完成真人验证。
4. 设置用户名与头像（建议使用学术机构标识）。
5. 点击"加入服务器"完成接入。

### 如何高效使用 Discord

以下是有效使用 Discord 的几点建议：

- **语音频道**虽已开放，但文字聊天仍是更常用的沟通方式。
- 支持使用 **Markdown style** 格式化文本（尤其适用于代码编写），但需注意其在链接处理方面的效果欠佳。
- 针对**长对话场景**，建议开启子线程（Threads）功能以保持讨论条理性。

希望本指南能为您提供帮助！如有任何疑问，欢迎通过 Discord 平台向我们咨询 🤗.
