客户端开发者学 Eino(01):从一次模型调用理解 Agent 架构

客户端开发者学 Eino(01):从一次模型调用理解 Agent 架构

1 从一个冷启动问题开始

做客户端开发时,我习惯把一个功能拆成页面状态、业务流程和数据接口。学习 Eino 时,我也先从这些熟悉的职责入手:模型、工具、Agent 和 Runner 分别负责什么,一次请求怎样在它们之间流动?

这是"客户端开发者学 Eino"系列的第 01 篇,面向熟悉应用开发、刚开始接触 Agent 的读者。我们用一个假设的冷启动分析助手,把架构图和真实调用代码对应起来。

用户向助手提出的问题是:

为什么 B 版本的冷启动比 A 版本慢?帮我找出主要变化。

如果步骤已经确定,可以直接写一个业务用例(UseCase):查询版本指标、计算差异、读取阶段耗时,再生成结果。这和客户端组织一段业务流程很接近。

但调查过程还可能继续变化:发现初始化阶段变慢后,要不要继续查询某个 SDK?资料里出现了新线索,要不要再检索文档?

Agent 在这里承担的工作,是让模型结合目标和已有结果,参与下一步行动的选择。 Eino 提供连接模型、组织工具和运行任务的框架能力,业务系统再把实际的数据与操作接进去。

2 先把 Eino 放进熟悉的架构

Eino 是 CloudWeGo 开源的 Go 语言框架,用于构建大模型应用与 Agent。本文的例子把它放在 Go 服务端,客户端负责发送问题、接收事件和展示结果。

text 复制代码
客户端:页面 → ViewModel → HTTP / SSE
                             ↓
Go 服务:请求入口 → Runner → Agent
                              ├─ ChatModel → 模型服务
                              └─ Tool → 指标、日志、文档服务

先认识四个核心角色:

角色 在这个助手中负责什么
ChatModel 调用模型,取得文本或工具调用请求
Tool 查询指标、读取日志、检索文档等可执行能力
Agent 根据目标与执行结果,组织后续行动
Runner 启动 Agent 执行,把运行事件交给应用

可以借助客户端概念理解:ChatModel 像对外部服务的接口抽象,Tool 像封装后的 Repository 方法,Agent 像由模型参与决策的 UseCase。Runner 则位于应用调用 Agent 的入口。Eino 官方的 ADK(Agent Development Kit,智能体开发套件)围绕这些对象组织执行。Eino ADK 概述

这些类比对应的是职责。框架没有替应用实现指标系统,也没有自动决定页面怎样管理任务;业务能力和产品行为仍由应用提供。

3 从一段真实代码看模型接口

接口后面到底是什么

在客户端,上层代码经常依赖接口,实际请求交给具体实现。用 Kotlin 表达一段类似的依赖关系,可以写成:

kotlin 复制代码
interface ModelService {
    suspend fun generate(question: String): String
}

class StartupExplainer(private val model: ModelService) {
    suspend fun explain(question: String): String =
        model.generate(question)
}

这是客户端职责示意,不是 Eino 的 Kotlin SDK。重点在于 StartupExplainer 通过接口使用模型能力,具体怎么访问服务,由注入的实现承担。

Eino 也有模型接口。本例使用 model.BaseChatModel 表示基础调用能力,它提供 Generate 和 Stream:前者等待完整结果,后者逐段读取生成内容。模型接口源码

创建实现 持有接口 发起请求

下面是一段完整的 Go 参考代码。先看标记 ①、②、③ 的三处,其余语法可以结合后面的解释阅读。

示例依赖 Eino v0.9.14、OpenAI 模型适配器 v0.1.13,已通过编译检查;本篇没有发起在线模型调用。环境变量用于传入模型服务的 Key 和模型标识,理解流程无需实际配置它们。

go 复制代码
package main

import (
	"context"
	"fmt"
	"log"
	"os"
	"time"

	"github.com/cloudwego/eino-ext/components/model/openai"
	"github.com/cloudwego/eino/components/model"
	"github.com/cloudwego/eino/schema"
)

func main() {
	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
	defer cancel()

	// ① 创建具体的模型适配器。
	provider, err := openai.NewChatModel(ctx, &openai.ChatModelConfig{
		APIKey: os.Getenv("OPENAI_API_KEY"),
		Model:  os.Getenv("OPENAI_MODEL"),
	})
	if err != nil {
		log.Fatal(err)
	}

	// ② 用接口类型持有这个具体对象。
	var llm model.BaseChatModel = provider

	systemPrompt := "你是客户端性能分析助手。" +
		"只解释给定数据,不推测未提供的原因。"
	question := "示例数据:A 版本冷启动 800ms,B 版本 1000ms。" +
		"请计算增加的耗时和比例。"

	// ③ 通过接口发起一次请求。
	reply, err := llm.Generate(ctx, []*schema.Message{
		schema.SystemMessage(systemPrompt),
		schema.UserMessage(question),
	})
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(reply.Content)
}

这里的三个动作分别是:

  1. openai.NewChatModel(...) 创建具体模型适配器,保存配置并组织客户端对象。
  2. var llm model.BaseChatModel = provider 让接口类型的变量持有具体实现,类似 Kotlin 的 val service: ModelService = ...。
  3. llm.Generate(...) 经接口进入适配器实现,在这一步发起模型请求。成功后,reply.Content 就是返回文本。

因此,创建 ChatModel 对象和调用模型,是两个不同步骤。 这里创建的是应用中的适配器对象,模型本身运行在被调用的服务中。OpenAI 适配器说明

还有一个容易疑惑的地方:Go 为什么没有写 implements?因为 Go 按方法集合判断接口实现。具体类型提供了接口要求的方法,就能赋给接口变量;上面那行赋值会由编译器检查。

消息与返回值怎样对应

SystemMessage 承载通用行为要求,本例要求模型只解释给定数据;UserMessage 承载本次问题,提供两个版本的耗时,并要求计算差值和比例。

给定 800ms 和 1000ms,期望解释是增加 200ms、相对 A 版本增加 25%。这是依据示例数据得到的预期结果,并非在线调用的实测输出。实际系统可以由程序完成确定的计算,再由模型组织说明;这里用简单数字,是为了看清输入与返回值。

读到这里,一个问题很自然:PE 是否就是写 system message?PE 是 Prompt Engineering,即提示词工程。system message 是其中一个落点,具体任务、示例和输出约定也会影响结果,工程过程还需要通过案例验证效果。提示词设计将在后续篇目展开。提示词工程概览

代码中的 ctx 则是 Go 的请求上下文,用于传递取消和截止时间,可以借助协程任务的超时机制理解。它与模型本轮看到的消息、资料分属不同概念,也与 Android 的 Context 职责不同。

到这里,我们只完成了一次文本模型调用。要回答"为什么变慢",助手还需要取得真实阶段数据,这就轮到 Tool 出场。

4 Tool 怎样连接已有业务能力

假设指标平台已经提供查询服务,可以把下面这些能力封装成工具:

工具 业务能力
query_startup_metrics 查询某版本的启动指标
query_stage_duration 查询各启动阶段的耗时
search_technical_docs 检索与当前问题有关的资料

模型需要看到工具名称、用途和参数约定,才能提出结构化请求。例如,它可能要求调用 query_stage_duration,参数为 {"version":"B"}。这是本例的调用示意,具体协议字段取决于模型和适配方式。

程序负责校验参数、检查访问权限,再执行实际的业务方法。 模型提出的调用请求经过这一步,才会变成指标服务上的查询;查询结果随后被组织成工具结果,交回模型。

从客户端角度看,可以把 Tool 理解为 Repository 方法外面的一层适配。除了能执行方法,还要给模型提供可理解的能力说明。

工具也要区分"没有匹配数据"和"请求超时"。前者可能需要改变查询条件,后者可能需要重试;如果都返回空字符串,后续决策就缺少了必要信息。

5 Agent 怎样根据结果继续行动

单次模型调用取得一个结果,Agent 则围绕任务反复组织模型与工具执行。Eino 的 ChatModelAgent 将这类过程组织成 ReAct 循环,可以理解为模型判断与程序行动交替推进。ChatModelAgent 的职责

text 复制代码
用户目标
    ↓
Agent 调用模型
    ├─ 最终答案 → 结束
    └─ 工具调用请求
           ↓
       程序执行 Tool
           ↓
       工具结果加入消息
           └─ 回到模型,决定下一步

把这个循环放回冷启动案例,可以推演一次调查过程:

  1. 先查 A、B 版本总耗时,确认差异。
  2. 再查阶段数据,发现增量主要集中在初始化阶段。
  3. 根据任务需要继续读取相关日志或技术资料。
  4. 证据足够后,给出结论、依据以及后续动作。

这是任务流程推演,阶段数据由工具提供。前面的文本调用代码只输入了总耗时,所以它展示的是整个助手中的模型调用环节。

与普通 UseCase 相比,这里的部分分支由模型结合新结果提出。应用仍需控制循环次数、总耗时、可用工具和结束条件。例如,达到查询预算后就结束执行,并在当前证据范围内返回结果。

这也是 Agent 工程需要同时关注模型与程序的原因:模型提供下一步建议,程序负责把建议变成受约束的执行过程。

6 Runner 怎样把执行过程交给界面

如果 Agent 在后台连续查询多个工具,客户端需要展示进度,也需要知道什么时候完成、失败或等待用户输入。

Runner 是运行 Agent 的入口,应用通过它取得执行事件,再将事件转换成产品协议和页面状态。下面是一种可采用的组织方式:

text 复制代码
服务端 Runner → AgentEvent → HTTP / SSE 事件
                                  ↓
客户端接收事件 → 更新 UiState → 页面渲染

SSE(Server-Sent Events,服务端发送事件)是一种服务端持续向客户端推送内容的方式。客户端可以将收到的文本片段、工具进度、错误与结束信息映射为状态,再由 ViewModel 驱动页面更新。具体事件类型和展示文案由应用定义。

例如,界面展示"正在读取版本指标""正在分析阶段差异",并不意味着需要公开模型内部思维。工具执行记录、证据和明确的任务状态,已经能帮助用户理解进度。

页面退出时是否取消任务、任务能否后台继续、断线后如何查询结果,仍由产品与应用层设计。Runner 提供运行能力,生命周期策略需要与客户端交互约定一致。

7 固定步骤怎样与动态判断配合

有些步骤始终固定,比如并行查询两个版本、计算差值、按固定字段生成对比表。这类流程可以直接由程序编排。

Eino 的 Compose 用于组织步骤、分支和数据传递。在这个例子里,可以把"生成版本对比表"封装成稳定流程,再由 Agent 决定何时调用它。这样,确定的步骤交给程序执行,需要结合新线索选择方向的部分交给模型参与判断。Eino 编排说明

名称上要区分:Jetpack Compose 组织 UI,Eino Compose 组织模型、工具与数据处理步骤。二者处在不同的软件职责层。

我理解这套架构时,会先沿着一次请求看职责:模型接口负责调用,工具负责连接业务能力,Agent 组织任务推进,Runner 把执行过程交给应用。对应到代码,就能继续追问对象在哪里创建、请求何时发出、结果由谁消费。

下一篇会补齐阅读这些代码所需的 Go 基础,重点看结构体、接口、错误返回和请求上下文,再逐步进入工具调用。

相关推荐
武汉星际互动1 小时前
政务大厅软硬件升级改造怎么做?
人工智能·政务
IT_陈寒1 小时前
Vite开发实战:从入门到精通
前端·人工智能·后端
LaughingZhu1 小时前
Product Hunt 热榜 2026-11-02:Screen Studio 4.0、ClawsGO Science、Riverside Agents
人工智能·深度学习·神经网络·数码相机·搜索引擎·百度
mumu1571 小时前
快速上手大模型推理vllm
人工智能·python·大语言模型·agent·vllm
明天会有多晴朗1 小时前
从零手写 Transformer!一个 Python 文件跑通英译中(PyTorch 附完整源码)
人工智能·pytorch·python·深度学习·transformer
Hello.Reader1 小时前
100 人同时操作一座三维城市,为什么不需要服务器渲染 6000 帧?Rust + wgpu 多端并发架构实战
服务器·架构·rust
田里的水稻1 小时前
FA_CNN卷积神经网络二十一---丢弃法PyTorch
人工智能·pytorch·cnn
学IT的周星星1 小时前
本地部署与开源模型:不想花钱怎么办
人工智能·开源·大模型·llm·agent
AI人工智能+1 小时前
炫彩活体检测利用手机屏幕投射随机彩色光,通过分析真实皮肤对光的次表面散射、血红蛋白吸收及纹理细节等光学特性,实现1-2秒内无感核验
人工智能·深度学习·人脸识别·炫彩活体检测