llm

package
v1.6.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 28, 2026 License: Apache-2.0 Imports: 31 Imported by: 0

README

llm — Eino LLM 高级封装库

基于 Eino 框架的生产级 LLM 工具调用与 Agent 封装库。本 README 旨在为 AI 编程助手提供完整的上下文索引。

🌟 核心特性

  • 多协议统一路由:一键切换 OpenAI / Claude / DeepSeek / Gemini / Ollama / Moonshot(Kimi) 等模型协议。
  • React Agent 增强:内置工具调用循环、自动重试、死循环检测。
  • StructTool:基于 Go 结构体标签(tag)自动生成 JSON Schema,支持嵌套结构、枚举值。
  • 可观测性:开箱即用的 Langfuse 和自定义 LogClient 集成。
  • 运行时动态控制:支持请求级别的模型替换、工具替换、参数调整。
  • 流式完整支持:从推理到工具调用再到最终回答的全链路流式。

🚀 快速开始

1. 初始化模型配置
cfg := llm.ModelConfig{
    Protocol: llm.OPENAI, // 或 llm.CLAUDE, llm.OLLAMA ...
    BaseURL:  "https://api.openai.com/v1", // 可选
    APIKey:   "sk-xxx",
    Model:    "gpt-4o",
}
2. 定义工具 (推荐 StructTool)

使用 struct 定义目标结构,自动生成 Schema:

type WeatherResult struct {
    City        string `json:"city" desc:"城市" required:"true"`
    Condition   string `json:"condition" desc:"天气情况" required:"true"`
    Temperature string `json:"temperature" desc:"温度" required:"true"`
}

weatherTool := llm.NewStructTool[WeatherResult]("extract_weather", "提取天气结果")
3. 创建 Agent 并运行
agent, _ := llm.NewAgent(ctx, llm.AgentConfig{
    Model: llm.AgentModelConfig{Config: cfg},
    Tools: llm.ToolsConfig{Invokable: []llm.InvokableTool{weatherTool}},
    Prompt: llm.PromptConfig{
        System: "从用户请求中提取天气结果,输出必须是合法 JSON。",
    },
    Execution: llm.ExecutionConfig{
        Mode:              llm.Extraction,
        DirectReturnTools: map[string]struct{}{"extract_weather": {}},
    },
})

msg, _ := agent.Generate(ctx, []*schema.Message{
    schema.UserMessage("北京天气晴,25摄氏度。请提取结构化结果。"),
})
fmt.Println(msg.Content)

🛠 高级功能

0. 执行模式与配置约束

推荐优先使用 Execution.Mode 描述 Agent 行为:

  • Conversation:纯对话,不启用工具
  • Assistant:工具可用,由模型自行决定是否调用
  • Extraction:先完成工具任务,再决定是否总结

配置约束:

  • ModeToolChoice 同时传入时,以 Mode 为准;ToolChoice 仅保留兼容路径
  • Conversation 不允许同时配置工具、MaxRetriesDirectReturnTools
  • Assistant 不允许配置 MaxRetries
  • DirectReturnTools 中的工具名必须真实存在,否则 NewAgent 会直接返回错误
1. 可观测性 (Observability)

llm 现在有两条互补的观测链路:

  • Callbacks / NewLogHandler:保留现有组件级日志语义,适合看底层 ChatModel / Tool 组件有没有被调用
  • StructuredLogs:新增的 Agent 语义日志,适合排查“为什么没调工具 / 为什么重试 / 为什么 direct return”

两者可以同时开启:

// 初始化 Langfuse
lfHandler, flush, _ := llm.NewLangfuseHandler(&llm.LangfuseConfig{...})
defer flush()

// 初始化日志客户端(示例:go-kit/kit)
logger := kit.New(kit.Options{Format: kit.FormatJSON})
logHandler := llm.NewLogHandler(logger)

// 注入 Agent
agent, _ := llm.NewAgent(ctx, llm.AgentConfig{
    // ...
    Observability: llm.ObservabilityConfig{
        Callbacks: []callbacks.Handler{lfHandler, logHandler},
        StructuredLogs: &llm.StructuredLogConfig{
            Client:           logger,
            LogToolArguments: true,
            LogToolResults:   true,
            MaxFieldLength:   256,
        },
    },
})

说明:

  • NewLogHandler 的输出语义没有改,仍然只输出 Component Start / Component End / Component Error
  • NewLogHandlerStructuredLogs 现在都依赖 LogClient 接口,而不是 *slog.Logger
  • LogClient 只要求实现 Info(ctx, msg, fields...)Error(ctx, msg, fields...)
  • StructuredLogs 会输出 agent.start / model.decision / tool.start / tool.success / tool.error / agent.end
  • 当前没有为 RuntimeSpec 做缓存;构造成本不在热路径,不值得为此引入额外状态
  • 当前不承诺公开 ParentMessageID 一类字段;上游 schema.Message 没有稳定的顶层 message id
  • 结构化日志和 callback 日志都会继承调用时的 ctx;如果你的 LogClient 会从 ctx 提取 trace_id/request_id,这些字段会自然出现在日志里
  • 每次 Generate / Stream 都会生成一个 invocation_id,用于区分并发链路

常用字段:

  • invocation_id:单次 Generate / Stream 的链路标识
  • agent.startexecution_modetool_countdirect_return_enabledmessage_count
  • model.decisionconfigured_tool_choicetool_call_counttool_namesfinish_reasonreasoning_tokens
  • tool.starttool_nametool_call_idattemptarguments
  • tool.successtool_nameattemptlatency_msresultdirect_return
  • tool.errortool_nameattemptlatency_msretryableterminalerror
  • agent.endstatuslatency_msdirect_return

Assistant 场景日志示例:

{"level":"INFO","msg":"agent.start","event":"agent.start","invocation_id":"inv-001","execution_mode":"assistant","tool_count":1,"direct_return_enabled":false,"message_count":1}
{"level":"INFO","msg":"model.decision","event":"model.decision","invocation_id":"inv-001","execution_mode":"assistant","configured_tool_choice":"allowed","tool_call_count":1,"tool_names":["lookup_user"],"finish_reason":"tool_calls"}
{"level":"INFO","msg":"tool.start","event":"tool.start","invocation_id":"inv-001","tool_name":"lookup_user","tool_call_id":"tc1","attempt":1,"arguments":"{\"name\":\"Alice\"}"}
{"level":"INFO","msg":"tool.success","event":"tool.success","invocation_id":"inv-001","tool_name":"lookup_user","tool_call_id":"tc1","attempt":1,"latency_ms":12,"result":"{\"name\":\"Alice\"}"}
{"level":"INFO","msg":"agent.end","event":"agent.end","invocation_id":"inv-001","execution_mode":"assistant","tool_count":1,"direct_return_enabled":false,"latency_ms":38,"status":"success"}

Extraction 重试 + direct return 日志示例:

{"level":"INFO","msg":"agent.start","event":"agent.start","invocation_id":"inv-002","execution_mode":"extraction","tool_count":1,"direct_return_enabled":true,"message_count":1}
{"level":"INFO","msg":"model.decision","event":"model.decision","invocation_id":"inv-002","execution_mode":"extraction","configured_tool_choice":"forced","tool_call_count":1,"tool_names":["extract_resume"]}
{"level":"INFO","msg":"tool.start","event":"tool.start","invocation_id":"inv-002","tool_name":"extract_resume","tool_call_id":"tc1","attempt":1,"arguments":"{\"query\":\"bad\"}"}
{"level":"ERROR","msg":"tool.error","event":"tool.error","invocation_id":"inv-002","tool_name":"extract_resume","tool_call_id":"tc1","attempt":1,"latency_ms":4,"retryable":true,"terminal":false,"error":"missing required field"}
{"level":"INFO","msg":"model.decision","event":"model.decision","invocation_id":"inv-002","execution_mode":"extraction","configured_tool_choice":"forced","tool_call_count":1,"tool_names":["extract_resume"]}
{"level":"INFO","msg":"tool.start","event":"tool.start","invocation_id":"inv-002","tool_name":"extract_resume","tool_call_id":"tc2","attempt":2,"arguments":"{\"query\":\"good\"}"}
{"level":"INFO","msg":"tool.success","event":"tool.success","invocation_id":"inv-002","tool_name":"extract_resume","tool_call_id":"tc2","attempt":2,"latency_ms":6,"result":"{\"name\":\"Alice\"}","direct_return":true}
{"level":"INFO","msg":"agent.end","event":"agent.end","invocation_id":"inv-002","execution_mode":"extraction","tool_count":1,"direct_return_enabled":true,"latency_ms":29,"status":"success","direct_return":true}

排障顺序建议:

  1. 先看 agent.start,确认 execution_modetool_countdirect_return_enabled 是否符合预期
  2. 再看 model.decision,判断模型是否真的产出了 tool_calls
  3. 如果有 tool.start 但没有 tool.success,继续看 tool.errorretryable / terminal
  4. 如果工具成功但结果不像最终回答,检查 direct_return 是否命中,或者是否仍回到了模型总结
2. Extraction 模式与失败修复
agent, _ := llm.NewAgent(ctx, llm.AgentConfig{
    // ...
    Execution: llm.ExecutionConfig{
        Mode:       llm.Extraction, // 强制模型先完成工具任务
        MaxRetries: 3,              // 工具报错后反馈给模型修正再试
    },
})

MaxRetries 只在 Extraction 模式下有效;如果放到 ConversationAssistantNewAgent 会直接报错。

3. 工具结果直接返回 (Direct Return)

某些场景下(如搜索),工具执行后不需要模型再通过 LLM 总结,直接返回工具结果可节省 Token:

agent, _ := llm.NewAgent(ctx, llm.AgentConfig{
    // ...
    Execution: llm.ExecutionConfig{
        Mode: llm.Assistant,
        DirectReturnTools: map[string]struct{}{
            "search_tool": {}, // 执行完 search_tool 后立即结束对话并返回结果
        },
    },
})

DirectReturnTools 只能填写已经注册到 Tools.StandardTools.Invokable 或 MCP 中的工具名。

4. 运行时动态控制 (Runtime Options)

GenerateStream 时动态修改行为,不影响 Agent 实例。

import "github.com/cloudwego/eino/flow/agent"

// 场景 A: 临时更换模型 (例如降级到 3.5)
agent.Generate(ctx, input, agent.WithChatModel(gpt35Model))

// 场景 B: 调整温度
agent.Generate(ctx, input, agent.WithChatModelOptions(model.WithTemperature(0.1)))

// 场景 C: 获取中间状态 (Result Event Stream)
logOpt, future := agent.WithMessageFuture()
go func() {
    defer future.Close()
    stream, _ := future.GetMessageStreams()
    for { msg, _ := stream.Recv(); fmt.Println("中间状态:", msg) }
}()
agent.Generate(ctx, input, logOpt)

📦 架构说明

llm 包是对 CloudWeGo Eino 框架的 Opinionated 封装:

  • Model: 实现了 eino/components/model 接口。
  • Agent: 封装了 eino/flow/agent/react
  • Tool: 提供了 StructTooleino/components/tool 的适配器。

如需更复杂的编排(如多 Agent 协作),可使用 agent.ExportGraph() 导出底层 Graph 节点,嵌入到 Eino 的 Graph 中。

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func CombineTools added in v1.1.0

func CombineTools(args ...interface{}) []tool.BaseTool

CombineTools 合并单个工具或工具列表。 支持 tool.BaseTool 和 []tool.BaseTool 类型。 对于不支持的类型,会打印警告并忽略。

func NewLangfuseHandler added in v1.1.0

func NewLangfuseHandler(cfg *LangfuseConfig) (callbacks.Handler, func(), error)

NewLangfuseHandler 创建一个 Langfuse 回调处理器。 返回的 flush 函数应该在程序退出前调用,确保所有 Trace 上报完成。

func NewLogHandler added in v1.1.0

func NewLogHandler(client LogClient) callbacks.Handler

NewLogHandler 创建一个基于 LogClient 的日志回调处理器。 它会记录组件的输入、输出和 Token 消耗(如果有),并沿用调用时的 ctx。

func NewMCPTools added in v1.1.0

func NewMCPTools(ctx context.Context, cfg MCPConfig) ([]tool.BaseTool, func() error, error)

NewMCPTools 创建 MCP Client 并加载工具。 返回工具列表和清理函数。清理函数用于关闭 Client。 注意:Client 的底层运行依赖于内部创建的 Context,该 Context 会在调用 cleanup 时取消。 传入的 ctx 仅用于初始化过程(握手超时控制)。

func NewModel added in v1.1.0

NewModel 根据 Protocol 创建对应的 eino-ext ToolCallingChatModel。

Types

type Agent added in v1.1.0

type Agent struct {
	// contains filtered or unexported fields
}

Agent 封装 Eino ReactAgent,提供简化的高层 API。

func NewAgent added in v1.1.0

func NewAgent(ctx context.Context, cfg AgentConfig) (*Agent, error)

NewAgent 创建一个 Agent。 Mode 是推荐配置入口;如果 Mode 和 ToolChoice 同时设置,以 Mode 为准。

配置约束:

  • Conversation 不允许同时配置工具、MaxRetries 或 DirectReturnTools
  • Assistant 不允许配置 MaxRetries
  • DirectReturnTools 只能引用已注册的工具名

使用示例:

// 场景 1: 纯对话
agent, _ := llm.NewAgent(ctx, llm.AgentConfig{Model: llm.AgentModelConfig{Config: cfg}})
msg, _ := agent.Generate(ctx, messages)

// 场景 2: 强制调工具 → 结果回模型 → 模型决策
agent, _ := llm.NewAgent(ctx, llm.AgentConfig{
    Model: llm.AgentModelConfig{Config: cfg},
    Tools: llm.ToolsConfig{Invokable: []llm.InvokableTool{myTool}},
    Execution: llm.ExecutionConfig{Mode: llm.Extraction},
})

// 场景 3: 强制调工具 → 直接拿结果
agent, _ := llm.NewAgent(ctx, llm.AgentConfig{
    Model: llm.AgentModelConfig{Config: cfg},
    Tools: llm.ToolsConfig{Invokable: []llm.InvokableTool{myTool}},
    Execution: llm.ExecutionConfig{
        Mode:              llm.Extraction,
        DirectReturnTools: map[string]struct{}{"my_tool": {}},
    },
})

func (*Agent) Close added in v1.4.1

func (a *Agent) Close() error

func (*Agent) ExportGraph added in v1.1.0

func (a *Agent) ExportGraph() (compose.AnyGraph, []compose.GraphAddNodeOpt)

ExportGraph 导出底层 Graph,用于嵌入更大的编排图。

func (*Agent) Generate added in v1.1.0

func (a *Agent) Generate(ctx context.Context, messages []*schema.Message, opts ...agent.AgentOption) (msg *schema.Message, err error)

Generate 非流式调用 Agent。 模型会自动处理工具调用循环,直到返回最终答案。

func (*Agent) Stream added in v1.1.0

func (a *Agent) Stream(ctx context.Context, messages []*schema.Message, opts ...agent.AgentOption) (*schema.StreamReader[*schema.Message], error)

Stream 流式调用 Agent。 完整支持流式 tool call:模型推理 → 工具调用 → 再推理,全程流式。

type AgentConfig added in v1.1.0

type AgentConfig struct {
	Model         AgentModelConfig
	Prompt        PromptConfig
	Tools         ToolsConfig
	Execution     ExecutionConfig
	Streaming     StreamingConfig
	Observability ObservabilityConfig
}

type AgentModelConfig added in v1.4.1

type AgentModelConfig struct {
	Config   ModelConfig
	Instance model.ToolCallingChatModel
}

type ExecutionConfig added in v1.4.1

type ExecutionConfig struct {
	// Mode 是推荐配置入口,用于声明 Agent 的高层执行模式。
	Mode ExecutionMode
	// Deprecated: ToolChoice 仅保留给 legacy-only 兼容路径;新代码应优先使用 Mode。
	ToolChoice *schema.ToolChoice
	// MaxRetries 仅用于 Extraction;Conversation 和 Assistant 会拒绝该配置。
	MaxRetries int
	MaxStep    int
	// DirectReturnTools 仅允许引用已注册的工具名;Conversation 会拒绝该配置。
	DirectReturnTools map[string]struct{}
}

type ExecutionMode added in v1.4.1

type ExecutionMode string
const (
	// Conversation 表示纯对话,不启用工具。
	Conversation ExecutionMode = "conversation"
	// Assistant 表示工具可用,由模型自行决定是否调用。
	Assistant ExecutionMode = "assistant"
	// Extraction 表示先完成工具任务,再决定是否继续总结。
	Extraction ExecutionMode = "extraction"
)

type InvokableTool added in v1.1.0

type InvokableTool interface {
	Info() *schema.ToolInfo
	Invoke(ctx context.Context, args string) (string, error)
}

InvokableTool 代表一个可执行工具(定义 + 执行能力)。 这是一个简化接口,可通过 ToolAdapter 适配到 Eino 标准 tool.InvokableTool。

type LangfuseConfig added in v1.1.0

type LangfuseConfig struct {
	Host      string
	PublicKey string
	SecretKey string
}

LangfuseConfig 是 Langfuse 的配置。

type LogClient added in v1.4.1

type LogClient interface {
	Info(ctx context.Context, msg string, fields ...any)
	Error(ctx context.Context, msg string, fields ...any)
}

type MCPConfig added in v1.1.0

type MCPConfig struct {
	Name    string // 此客户端的标识符
	Version string // 客户端版本,默认为 1.0.0

	Protocol MCPProtocol

	// Stdio 特定配置
	Command string
	Args    []string
	Env     []string

	// SSE 特定配置
	BaseURL string

	// 可选:过滤要包含的工具
	// 若为空,则加载所有工具
	ToolWhitelist []string
}

MCPConfig 定义单个 MCP 服务器连接的配置。

type MCPProtocol added in v1.1.0

type MCPProtocol string

MCPProtocol 定义 MCP 的传输协议。

const (
	MCPProtocolStdio MCPProtocol = "stdio"
	MCPProtocolSSE   MCPProtocol = "sse"
)

type MessageModifier added in v1.1.0

type MessageModifier = react.MessageModifier

MessageModifier 在每轮调用模型前修改消息列表。 常用于注入 system prompt 或上下文压缩。

type ModelConfig added in v1.1.0

type ModelConfig struct {
	Protocol ModelProtocol
	BaseURL  string
	APIKey   string
	Model    string
	Timeout  time.Duration

	MaxTokens   *int
	Temperature *float32
	TopP        *float32
	Stop        []string
}

ModelConfig 是创建模型的统一配置。

type ModelProtocol added in v1.1.0

type ModelProtocol string

ModelProtocol 定义模型厂商协议。

const (
	OPENAI        ModelProtocol = "OPENAI"
	OPENAI_COMPAT ModelProtocol = "OPENAI_COMPAT"
	CLAUDE        ModelProtocol = "CLAUDE"
	CLAUDE_COMPAT ModelProtocol = "CLAUDE_COMPAT"
	ARK           ModelProtocol = "ARK"
	ARKBOT        ModelProtocol = "ARKBOT"
	DEEPSEEK      ModelProtocol = "DEEPSEEK"
	GEMINI        ModelProtocol = "GEMINI"
	OLLAMA        ModelProtocol = "OLLAMA"
	QIANFAN       ModelProtocol = "QIANFAN"
	QWEN          ModelProtocol = "QWEN"
	KIMI          ModelProtocol = "KIMI"
)

type ObservabilityConfig added in v1.4.1

type ObservabilityConfig struct {
	Callbacks      []callbacks.Handler
	StructuredLogs *StructuredLogConfig
}

type PromptConfig added in v1.4.1

type PromptConfig struct {
	System          string
	PrepareMessages MessageModifier
	RewriteHistory  MessageModifier
}

type RuntimeExecutionSpec added in v1.4.1

type RuntimeExecutionSpec struct {
	Mode              ExecutionMode
	DisableTools      bool
	ToolChoice        schema.ToolChoice
	RepairMaxAttempts int
	MaxStep           int
	DirectReturnTools map[string]struct{}
}

type RuntimeSpec added in v1.4.1

type RuntimeSpec struct {
	Model         AgentModelConfig
	Prompt        PromptConfig
	Tools         ToolsConfig
	Execution     RuntimeExecutionSpec
	Streaming     StreamingConfig
	Observability ObservabilityConfig
}

type StreamingConfig added in v1.4.1

type StreamingConfig struct {
	ToolCallChecker func(ctx context.Context, sr *schema.StreamReader[*schema.Message]) (bool, error)
}

type StructTool added in v1.1.0

type StructTool[T any] struct {
	// contains filtered or unexported fields
}

StructTool 是一个泛型工具,用于让模型生成指定结构体。

它利用 Tool Call 的 JSON Schema 约束模型输出格式:

  • 模型按 Schema 生成 JSON 参数
  • Invoke 内部做 json.Unmarshal 校验
  • 成功 → 返回合法 JSON
  • 失败 → 返回 error,触发自动重试

配合 Extraction 模式和 DirectReturnTools 使用,实现「结构化输出提取」:

type JD struct {
    Title        string   `json:"title"`
    Requirements []string `json:"requirements"`
}

tool := llm.NewStructTool[JD]("generate_jd", "生成职位描述")

agent, _ := llm.NewAgent(ctx, llm.AgentConfig{
    Model: llm.AgentModelConfig{Config: cfg},
    Tools: llm.ToolsConfig{Invokable: []llm.InvokableTool{tool}},
    Execution: llm.ExecutionConfig{
        Mode:              llm.Extraction,
        DirectReturnTools: map[string]struct{}{"generate_jd": {}},
    },
    Prompt: llm.PromptConfig{
        System: "根据用户需求生成职位描述。",
    },
})

msg, _ := agent.Generate(ctx, messages)
var jd JD
json.Unmarshal([]byte(msg.Content), &jd)

func NewStructTool added in v1.1.0

func NewStructTool[T any](name, desc string) *StructTool[T]

NewStructTool 创建一个结构化输出提取工具。 自动从 T 的 json tag 生成 ToolInfo 的参数定义。

func (*StructTool[T]) Info added in v1.1.0

func (s *StructTool[T]) Info() *schema.ToolInfo

func (*StructTool[T]) Invoke added in v1.1.0

func (s *StructTool[T]) Invoke(_ context.Context, args string) (string, error)

type StructuredLogConfig added in v1.4.1

type StructuredLogConfig struct {
	// Client 负责输出 llm 的结构化日志;建议直接传入支持 ctx 的日志客户端。
	Client           LogClient
	LogToolArguments bool
	LogToolResults   bool
	MaxFieldLength   int
}

type ToolAdapter added in v1.1.0

type ToolAdapter struct {
	// contains filtered or unexported fields
}

ToolAdapter 将 llm.InvokableTool 适配为 Eino tool.InvokableTool。 用于桥接简化接口与 Eino 标准接口。

func NewToolAdapter added in v1.1.0

func NewToolAdapter(t InvokableTool) *ToolAdapter

NewToolAdapter 创建一个工具适配器。

func (*ToolAdapter) Info added in v1.1.0

func (*ToolAdapter) InvokableRun added in v1.1.0

func (a *ToolAdapter) InvokableRun(ctx context.Context, argumentsInJSON string, _ ...tool.Option) (string, error)

type ToolsConfig added in v1.4.1

type ToolsConfig struct {
	Standard   []tool.BaseTool
	Invokable  []InvokableTool
	MCPServers []MCPConfig
}

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL