From cdedfd0425a92e27ad70f35c0bbea3a7b6cf74c1 Mon Sep 17 00:00:00 2001 From: "shentong.martin" Date: Wed, 11 Mar 2026 18:31:29 +0800 Subject: [PATCH] docs: improve ch01 and ch02 quickstart documentation - Add clear warnings for code snippets that cannot run directly - Replace absolute paths with GitHub URLs - Add natural transitions between Component and Agent concepts - Add ChatModel vs ChatModelAgent comparison table - Clarify the relationship between Component and Agent - Add analogies for better understanding - Improve language clarity and specificity - Add ch02 example code for ChatModelAgent and Runner Change-Id: I08e8944a7a09b65023b691e3c158a3b7f3fe5649 --- quickstart/chatwithdoc/cmd/ch01/main.go | 61 ++-- quickstart/chatwithdoc/cmd/ch02/main.go | 118 ++++++++ .../docs/ch01_chatmodel_agent_console.md | 130 +++++--- .../ch02_chatmodel_agent_runner_console.md | 283 ++++++++++++++++++ 4 files changed, 527 insertions(+), 65 deletions(-) create mode 100644 quickstart/chatwithdoc/cmd/ch02/main.go create mode 100644 quickstart/chatwithdoc/docs/ch02_chatmodel_agent_runner_console.md diff --git a/quickstart/chatwithdoc/cmd/ch01/main.go b/quickstart/chatwithdoc/cmd/ch01/main.go index b868b48..91457ad 100644 --- a/quickstart/chatwithdoc/cmd/ch01/main.go +++ b/quickstart/chatwithdoc/cmd/ch01/main.go @@ -25,9 +25,10 @@ import ( "os" "strings" + "github.com/cloudwego/eino-ext/components/model/ark" + "github.com/cloudwego/eino-ext/components/model/openai" + "github.com/cloudwego/eino/components/model" "github.com/cloudwego/eino/schema" - - examplemodel "github.com/cloudwego/eino-examples/adk/common/model" ) func main() { @@ -42,49 +43,53 @@ func main() { } ctx := context.Background() - cm := examplemodel.NewChatModel() - - msgs := make([]*schema.Message, 0, 2) - if strings.TrimSpace(instruction) != "" { - msgs = append(msgs, schema.SystemMessage(instruction)) + cm, err := newChatModel(ctx) + if err != nil { + _, _ = fmt.Fprintln(os.Stderr, err) + os.Exit(1) } - msgs = append(msgs, schema.UserMessage(query)) - _, _ = fmt.Fprint(os.Stdout, "[assistant] ") - stream, err := cm.Stream(ctx, msgs) - if err == nil { - stream.SetAutomaticClose() - if err := printChatModelStream(stream); err != nil { - _, _ = fmt.Fprintln(os.Stderr, err) - os.Exit(1) - } - _, _ = fmt.Fprintln(os.Stdout) - return + messages := []*schema.Message{ + schema.SystemMessage(instruction), + schema.UserMessage(query), } - out, err := cm.Generate(ctx, msgs) + _, _ = fmt.Fprint(os.Stdout, "[assistant] ") + stream, err := cm.Stream(ctx, messages) if err != nil { _, _ = fmt.Fprintln(os.Stderr, err) os.Exit(1) } - if out != nil { - _, _ = fmt.Fprintln(os.Stdout, out.Content) - } else { - _, _ = fmt.Fprintln(os.Stdout) - } -} + defer stream.Close() -func printChatModelStream(stream *schema.StreamReader[*schema.Message]) error { for { frame, err := stream.Recv() if errors.Is(err, io.EOF) { - return nil + break } if err != nil { - return err + _, _ = fmt.Fprintln(os.Stderr, err) + os.Exit(1) } if frame != nil { _, _ = fmt.Fprint(os.Stdout, frame.Content) } } + _, _ = fmt.Fprintln(os.Stdout) +} + +func newChatModel(ctx context.Context) (model.ToolCallingChatModel, error) { + if os.Getenv("MODEL_TYPE") == "ark" { + return ark.NewChatModel(ctx, &ark.ChatModelConfig{ + APIKey: os.Getenv("ARK_API_KEY"), + Model: os.Getenv("ARK_MODEL"), + BaseURL: os.Getenv("ARK_BASE_URL"), + }) + } + return openai.NewChatModel(ctx, &openai.ChatModelConfig{ + APIKey: os.Getenv("OPENAI_API_KEY"), + Model: os.Getenv("OPENAI_MODEL"), + BaseURL: os.Getenv("OPENAI_BASE_URL"), + ByAzure: os.Getenv("OPENAI_BY_AZURE") == "true", + }) } diff --git a/quickstart/chatwithdoc/cmd/ch02/main.go b/quickstart/chatwithdoc/cmd/ch02/main.go new file mode 100644 index 0000000..f01e0fd --- /dev/null +++ b/quickstart/chatwithdoc/cmd/ch02/main.go @@ -0,0 +1,118 @@ +package main + +import ( + "bufio" + "context" + "errors" + "flag" + "fmt" + "io" + "os" + "strings" + + "github.com/cloudwego/eino/adk" + "github.com/cloudwego/eino/schema" + + examplemodel "github.com/cloudwego/eino-examples/adk/common/model" +) + +func main() { + var instruction string + flag.StringVar(&instruction, "instruction", "You are a helpful assistant.", "") + flag.Parse() + + ctx := context.Background() + cm := examplemodel.NewChatModel() + + agent, err := adk.NewChatModelAgent(ctx, &adk.ChatModelAgentConfig{ + Name: "Ch02ChatModelAgent", + Description: "A minimal ChatModelAgent with in-memory multi-turn history.", + Instruction: instruction, + Model: cm, + }) + if err != nil { + _, _ = fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + + runner := adk.NewRunner(ctx, adk.RunnerConfig{ + Agent: agent, + EnableStreaming: true, + }) + + history := make([]*schema.Message, 0, 16) + scanner := bufio.NewScanner(os.Stdin) + for { + _, _ = fmt.Fprint(os.Stdout, "you> ") + if !scanner.Scan() { + break + } + line := strings.TrimSpace(scanner.Text()) + if line == "" { + break + } + history = append(history, schema.UserMessage(line)) + + events := runner.Run(ctx, history) + content, err := printAndCollectAssistantFromEvents(events) + if err != nil { + _, _ = fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + history = append(history, schema.AssistantMessage(content, nil)) + } + if err := scanner.Err(); err != nil { + _, _ = fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } +} + +func printAndCollectAssistantFromEvents(events *adk.AsyncIterator[*adk.AgentEvent]) (string, error) { + var sb strings.Builder + + for { + event, ok := events.Next() + if !ok { + break + } + if event.Err != nil { + return "", event.Err + } + if event.Output == nil || event.Output.MessageOutput == nil { + continue + } + + mv := event.Output.MessageOutput + if mv.Role != schema.Assistant { + continue + } + + if mv.IsStreaming { + mv.MessageStream.SetAutomaticClose() + for { + frame, err := mv.MessageStream.Recv() + if errors.Is(err, io.EOF) { + break + } + if err != nil { + return "", err + } + if frame != nil && frame.Content != "" { + sb.WriteString(frame.Content) + _, _ = fmt.Fprint(os.Stdout, frame.Content) + } + } + _, _ = fmt.Fprintln(os.Stdout) + continue + } + + if mv.Message != nil { + sb.WriteString(mv.Message.Content) + _, _ = fmt.Fprintln(os.Stdout, mv.Message.Content) + } else { + _, _ = fmt.Fprintln(os.Stdout) + } + } + + return sb.String(), nil +} diff --git a/quickstart/chatwithdoc/docs/ch01_chatmodel_agent_console.md b/quickstart/chatwithdoc/docs/ch01_chatmodel_agent_console.md index 31e7f81..511d129 100644 --- a/quickstart/chatwithdoc/docs/ch01_chatmodel_agent_console.md +++ b/quickstart/chatwithdoc/docs/ch01_chatmodel_agent_console.md @@ -2,82 +2,138 @@ title: "第一章:ChatModel 与 Message(Console)" --- -本章目标:用最小代码调用一次 ChatModel(支持流式输出),并掌握 `schema.Message` 的基本用法。 +本章目标:理解 Eino 的 Component 抽象,用最小代码调用一次 ChatModel(支持流式输出),并掌握 `schema.Message` 的基本用法。 ## 代码位置 -- 入口代码:[cmd/ch01/main.go](file:///Users/bytedance/github/eino/examples/quickstart/chatwithdoc/cmd/ch01/main.go) +- 入口代码:[cmd/ch01/main.go](https://github.com/cloudwego/eino/blob/main/examples/quickstart/chatwithdoc/cmd/ch01/main.go) -## 前置条件 +## 为什么需要 Component 接口 -- Go 版本:与本目录 `go.mod` 一致 -- 一个可调用的 ChatModel(默认使用 OpenAI;也支持 Ark) +Eino 定义了一组 Component 接口(`ChatModel`、`Tool`、`Retriever`、`Loader` 等),每个接口描述一类可替换的能力: -本 quickstart 复用 `github.com/cloudwego/eino-examples/adk/common/model.NewChatModel()` 来创建模型,它会读取环境变量选择不同 provider: +```go +type BaseChatModel interface { + Generate(ctx context.Context, input []*schema.Message, opts ...Option) (*schema.Message, error) + Stream(ctx context.Context, input []*schema.Message, opts ...Option) ( + *schema.StreamReader[*schema.Message], error) +} +``` -### 方式 A:OpenAI(默认) +**接口带来的好处:** -需要配置(最小集合): +1. **实现可替换**:`eino-ext` 提供了 OpenAI、Ark、Claude、Ollama 等多种实现,业务代码只依赖接口,切换模型只需改构造逻辑。 +2. **编排可组合**:Agent、Graph、Chain 等编排层只依赖 Component 接口,不关心具体实现。你可以把 OpenAI 换成 Ark,编排代码无需改动。 +3. **测试可 Mock**:接口天然支持 mock,单元测试不需要真实调用模型。 -- `OPENAI_API_KEY` -- `OPENAI_MODEL` +本章只涉及 `ChatModel`,后续章节会逐步引入 `Tool`、`Retriever` 等 Component。 -可选: +## schema.Message:对话的基本单位 -- `OPENAI_BASE_URL`(例如走代理或兼容 OpenAI 协议的服务) -- `OPENAI_BY_AZURE=true`(如使用 Azure OpenAI) +`Message` 是 Eino 里对话数据的基本结构: -示例: +```go +type Message struct { + Role RoleType // system / user / assistant / tool + Content string // 文本内容 + ToolCalls []ToolCall // 仅 assistant 消息可能有 + // ... +} +``` -```bash -export OPENAI_API_KEY="..." -export OPENAI_MODEL="gpt-4.1-mini" +常用构造函数: + +```go +schema.SystemMessage("You are a helpful assistant.") +schema.UserMessage("What is the weather today?") +schema.AssistantMessage("I don't know.", nil) // 第二个参数是 ToolCalls +schema.ToolMessage("tool result", "call_id") ``` -### 方式 B:Ark +**角色语义:** +- `system`:系统指令,通常放在 messages 最前面 +- `user`:用户输入 +- `assistant`:模型回复 +- `tool`:工具调用结果(后续章节涉及) -需要配置: +## 前置条件 -- `MODEL_TYPE=ark` -- `ARK_API_KEY` -- `ARK_MODEL` +- Go 版本:与本目录 `go.mod` 一致 +- 一个可调用的 ChatModel(默认使用 OpenAI;也支持 Ark) -可选: +### 方式 A:OpenAI(默认) -- `ARK_BASE_URL` +```bash +export OPENAI_API_KEY="..." +export OPENAI_MODEL="gpt-4.1-mini" +# 可选: +# OPENAI_BASE_URL(代理或兼容服务) +# OPENAI_BY_AZURE=true(使用 Azure OpenAI) +``` -示例: +### 方式 B:Ark ```bash export MODEL_TYPE="ark" export ARK_API_KEY="..." export ARK_MODEL="..." +# 可选:ARK_BASE_URL ``` -## 关键概念(只讲本章用到的) - -- `schema.Message`:对话消息。常见角色为 `system/user/assistant/tool`。本章使用 `system + user` 作为输入。 -- `ChatModel`:模型组件,负责基于一组 messages 生成回复。示例中用 `model.NewChatModel()` 创建。 - ## 运行 -在本章里我们用 Console 直接运行,不启动 Web 服务。 +在 `examples/quickstart/chatwithdoc` 目录下执行: ```bash -cd /Users/bytedance/github/eino/examples/quickstart/chatwithdoc go run ./cmd/ch01 -- "用一句话解释 Eino 的 Component 设计解决了什么问题?" ``` -你会看到类似输出(流式逐步打印): +输出示例(流式逐步打印): ```text -[assistant] ... +[assistant] Eino 的 Component 设计通过定义统一接口... ``` ## 入口代码做了什么 按执行顺序: -1. `NewChatModel()` 创建 ChatModel(从环境变量选择 OpenAI/Ark) -2. 构造输入 messages:`SystemMessage(instruction)` + `UserMessage(query)` -3. 优先尝试 `ChatModel.Stream(...)` 并打印流式输出;若不支持则回退到 `ChatModel.Generate(...)` +1. **创建 ChatModel**:根据 `MODEL_TYPE` 环境变量选择 OpenAI 或 Ark 实现 +2. **构造输入 messages**:`SystemMessage(instruction)` + `UserMessage(query)` +3. **调用 Stream**:所有 ChatModel 实现都必须支持 `Stream()`,返回 `StreamReader[*Message]` +4. **打印结果**:迭代 `StreamReader` 逐帧打印 assistant 回复 + +关键代码片段(**注意:这是简化后的代码片段,不能直接运行,完整代码请参考** [cmd/ch01/main.go](https://github.com/cloudwego/eino/blob/main/examples/quickstart/chatwithdoc/cmd/ch01/main.go)): + +```go +// 构造输入 +messages := []*schema.Message{ + schema.SystemMessage(instruction), + schema.UserMessage(query), +} + +// 调用 Stream(所有 ChatModel 都必须实现) +stream, err := cm.Stream(ctx, messages) +if err != nil { + log.Fatal(err) +} +defer stream.Close() + +for { + chunk, err := stream.Recv() + if errors.Is(err, io.EOF) { + break + } + if err != nil { + log.Fatal(err) + } + fmt.Print(chunk.Content) +} +``` + +## 本章小结 + +- **Component 接口**:定义可替换、可组合、可测试的能力边界 +- **Message**:对话数据的基本单位,通过角色区分语义 +- **ChatModel**:最基础的 Component,提供 `Generate` 和 `Stream` 两个核心方法 +- **实现选择**:通过环境变量或配置切换 OpenAI/Ark 等不同实现,业务代码无需改动 diff --git a/quickstart/chatwithdoc/docs/ch02_chatmodel_agent_runner_console.md b/quickstart/chatwithdoc/docs/ch02_chatmodel_agent_runner_console.md new file mode 100644 index 0000000..6275dba --- /dev/null +++ b/quickstart/chatwithdoc/docs/ch02_chatmodel_agent_runner_console.md @@ -0,0 +1,283 @@ +--- +title: "第二章:ChatModelAgent、Runner、AgentEvent(Console 多轮)" +--- + +本章目标:引入 ADK 的执行抽象(Agent + Runner),并用一个 Console 程序实现多轮对话。 + +## 代码位置 + +- 入口代码:[cmd/ch02/main.go](https://github.com/cloudwego/eino/blob/main/examples/quickstart/chatwithdoc/cmd/ch02/main.go) + +## 前置条件 + +与第一章一致:需要配置一个可用的 ChatModel(OpenAI 或 Ark)。 + +## 运行 + +在 `examples/quickstart/chatwithdoc` 目录下执行: + +```bash +go run ./cmd/ch02 +``` + +看到提示后输入问题(空行退出): + +```text +you> 你好,解释一下 Eino 里的 Agent 是什么? +... +you> 再用一句话总结一下 +... +``` + +## 关键概念 + +### 从 Component 到 Agent + +第一章我们学习了 **Component**(组件),它是 Eino 中可替换、可组合的能力单元: + +- `ChatModel`:调用大语言模型 +- `Tool`:执行特定任务 +- `Retriever`:检索信息 +- `Loader`:加载数据 + +**Component 和 Agent 的关系:** + +- **Component 不构成完整的 AI 应用**:它只是能力单元,需要被组织、编排、执行 +- **Agent 是完整的 AI 应用**:它封装了完整的业务逻辑,可以直接运行 +- **Agent 内部使用 Component**:最核心的是 `ChatModel`(对话能力)和 `Tool`(执行能力) + +**为什么需要 Agent?** + +如果只有 Component,你需要自己: +- 管理对话历史 +- 编排调用流程(何时调用模型、何时调用工具) +- 处理流式输出 +- 实现中断恢复 +- ... + +**Agent 提供了什么?** + +- **完整的运行时框架**:通过 `Runner` 统一管理执行过程 +- **标准的事件流输出**:`Run() -> AsyncIterator[*AgentEvent]`,支持流式、中断、恢复 +- **可扩展能力**:可以添加 tools、middleware、interrupt 等 +- **开箱即用**:创建 Agent 后直接运行,无需关心内部细节 + +**本章示例:** + +`ChatModelAgent` 是最简单的 Agent,它内部只使用了 `ChatModel`,但已经具备了 Agent 的完整能力框架。后续章节会展示如何添加 `Tool` 等更多能力。 + +### Agent 接口 + +`Agent` 是 ADK 中的核心接口,定义了智能体的基本行为: + +```go +type Agent interface { + Name(ctx context.Context) string + Description(ctx context.Context) string + + // Run 执行 Agent,返回事件流 + Run(ctx context.Context, input *AgentInput, options ...AgentRunOption) *AsyncIterator[*AgentEvent] +} +``` + +**接口职责:** +- `Name()` / `Description()`:标识 Agent 的名称和描述 +- `Run()`:执行 Agent 的核心方法,接收输入消息,返回事件流 + +**设计理念:** +- **统一抽象**:所有 Agent(ChatModelAgent、WorkflowAgent、SupervisorAgent 等)都实现这个接口 +- **事件驱动**:通过事件流(`AsyncIterator[*AgentEvent]`)输出执行过程,支持流式响应 +- **可扩展性**:后续加入 tools、middleware、interrupt 等能力时,接口保持不变 + +### ChatModelAgent + +`ChatModelAgent` 是 Agent 接口的一个实现,基于 ChatModel 构建: + +```go +agent, err := adk.NewChatModelAgent(ctx, &adk.ChatModelAgentConfig{ + Name: "Ch02ChatModelAgent", + Description: "A minimal ChatModelAgent with in-memory multi-turn history.", + Instruction: instruction, + Model: cm, +}) +``` + +**ChatModel vs ChatModelAgent:本质区别** + +| 维度 | ChatModel | ChatModelAgent | +|------|-----------|----------------| +| **定位** | Component(组件) | Agent(智能体) | +| **接口** | `Generate() / Stream()` | `Run() -> AsyncIterator[*AgentEvent]` | +| **输出** | 直接返回消息内容 | 返回事件流(包含消息、控制动作等) | +| **能力** | 单纯的模型调用 | 可扩展 tools、middleware、interrupt 等 | +| **适用场景** | 简单的对话场景 | 复杂的智能体应用 | + +**为什么需要 ChatModelAgent?** + +1. **统一抽象**:ChatModel 只是 Component 的一种,而 Agent 是更高层的抽象,可以组合多种 Component +2. **事件驱动**:Agent 输出事件流,支持流式响应、中断恢复、状态转移等复杂场景 +3. **可扩展性**:ChatModelAgent 可以添加 tools、middleware、interrupt 等能力,而 ChatModel 只能调用模型 +4. **编排友好**:Agent 可以被 Runner 统一管理,支持 checkpoint、恢复等运行时能力 + +**简单来说:** +- **ChatModel** = "负责与大语言模型通信的组件,屏蔽不同模型提供商的差异(OpenAI、Ark、Claude 等)" +- **ChatModelAgent** = "基于模型构建的智能体,可以调用模型,但还能做更多事" + +**类比理解:** +- **ChatModel** 就像"数据库驱动":负责与数据库通信,屏蔽 MySQL/PostgreSQL 的差异 +- **ChatModelAgent** 就像"业务逻辑层":基于数据库驱动构建,但还包含业务规则、事务管理等 + +**特点:** +- 封装了 ChatModel 的调用逻辑 +- 提供统一的 `Run() -> AgentEvent` 输出形态 +- 后续可以添加 tools、middleware 等能力 + +### Runner + +`Runner` 是执行 Agent 的入口点,负责管理 Agent 的生命周期: + +```go +type Runner struct { + a Agent // 要执行的 Agent + enableStreaming bool + store CheckPointStore // 用于中断恢复的状态存储 +} +``` + +**为什么需要 Runner?** + +虽然 Agent 提供了 `Run()` 方法,但直接调用会缺少很多运行时能力: + +1. **生命周期管理**:Runner 管理 Agent 的启动、恢复、中断等状态 +2. **Checkpoint 支持**:配合 `CheckPointStore` 实现中断恢复(后续章节涉及) +3. **统一入口**:提供 `Run()` 和 `Query()` 等便捷方法 +4. **事件流封装**:将 Agent 的事件流转换为可消费的 `AsyncIterator[*AgentEvent]` + +**使用方式:** + +```go +runner := adk.NewRunner(ctx, adk.RunnerConfig{ + Agent: agent, + EnableStreaming: true, +}) + +// 方式 1:传入消息列表 +events := runner.Run(ctx, history) + +// 方式 2:便捷方法,传入单个查询字符串 +events := runner.Query(ctx, "你好") +``` + +### AgentEvent + +`AgentEvent` 是 Runner 返回的事件单元: + +```go +type AgentEvent struct { + AgentName string + RunPath []RunStep + + Output *AgentOutput // 输出内容 + Action *AgentAction // 控制动作 + Err error // 执行错误 +} +``` + +**主要字段:** +- `event.Err`:执行错误 +- `event.Output.MessageOutput`:message 或 message stream(流式) +- `event.Action`:中断/转移/退出等控制动作(后续章节用到) + +**消费方式:** + +```go +for { + event, ok := events.Next() + if !ok { + break + } + if event.Err != nil { + // 处理错误 + } + if event.Output != nil && event.Output.MessageOutput != nil { + // 处理消息输出(可能是流式) + } +} +``` + +## 多轮对话的实现 + +本章实现的是简单的多轮对话:用户输入 → 模型回复 → 用户继续输入 → ... + +**实现方式:** + +没有 tools 时,`ChatModelAgent` 在一次 `Run()` 里只会完成一轮模型调用。多轮对话是通过调用侧维护 history 实现的: + +1. 用 `history []*schema.Message` 保存累计对话 +2. 每次用户输入:把 `UserMessage` 追加到 history +3. 调用 `runner.Run(ctx, history)` 得到事件流,消费得到 assistant 文本 +4. 把本轮 assistant 文本追加回 history,进入下一轮 + +**关键代码片段(**注意:这是简化后的代码片段,不能直接运行,完整代码请参考** [cmd/ch02/main.go](https://github.com/cloudwego/eino/blob/main/examples/quickstart/chatwithdoc/cmd/ch02/main.go)): + +```go +history := make([]*schema.Message, 0, 16) + +for { + // 1. 读取用户输入 + line := readUserInput() + if line == "" { + break + } + + // 2. 追加用户消息到 history + history = append(history, schema.UserMessage(line)) + + // 3. 调用 Runner 执行 Agent + events := runner.Run(ctx, history) + + // 4. 消费事件流,收集 assistant 回复 + content := collectAssistantFromEvents(events) + + // 5. 追加 assistant 消息到 history + history = append(history, schema.AssistantMessage(content, nil)) +} +``` + +**流程图:** + +``` +┌─────────────────────────────────────────┐ +│ 初始化 history = [] │ +└─────────────────────────────────────────┘ + ↓ + ┌──────────────────────┐ + │ 用户输入 UserMessage │ + └──────────────────────┘ + ↓ + ┌──────────────────────┐ + │ 追加到 history │ + └──────────────────────┘ + ↓ + ┌──────────────────────┐ + │ runner.Run(history) │ + └──────────────────────┘ + ↓ + ┌──────────────────────┐ + │ 消费事件流 │ + └──────────────────────┘ + ↓ + ┌──────────────────────┐ + │ 追加 AssistantMessage│ + └──────────────────────┘ + ↓ + (循环继续) +``` + +## 本章小结 + +- **Agent 接口**:定义智能体的基本行为,核心是 `Run() -> AsyncIterator[*AgentEvent]` +- **ChatModelAgent**:基于 ChatModel 实现的 Agent,提供统一的执行抽象 +- **Runner**:Agent 的执行入口,管理生命周期、checkpoint、事件流等运行时能力 +- **AgentEvent**:事件驱动的输出单元,支持流式响应和控制动作 +- **多轮对话**:通过调用侧维护 history 实现,每次 `Run()` 完成一轮对话