DeepSeek-R1(以下简称 DeepSeek)以其优秀的复杂问题推理能力和规划能力脱颖而出,然而其原生函数调用(Function Call)功能的缺失,无法让大模型去选择不同的工具和程序,以获取对应的信息,使其难以完成以下关键动作:
-
实时数据获取(天气 / 票务 / 交通)
-
外部服务交互(地图 API / 支付接口)
-
复杂任务拆解执行(多步骤自动化)
这就导致它的应用场景受到限制,大多只能用于简单的对话式问答。有没有一个解决办法,能实现让 DeepSeek 做 Function Call?
答案是肯定的,我们提出 " 计划------执行" 多智能体的协同范式:
由 DeepSeek 负责 "指挥",由擅长 Function Call 的其他大模型去听指挥进行函数调用。这需要利用"计划------执行" 多智能体范式,由 "计划" 智能体负责推理和生成计划,由 "执行" 智能体负责执行计划:
"计划------执行" 多智能体范式的三大优势:
-
专业的 "智能体" 干专业的事情:比如 DeepSeek 负责推理和计划,豆包大模型负责 Function Call。
-
"智能体" 层面的单一职责原则:每个智能体的职责是明确的,解耦的,方便 Prompt 调优和评测。
-
在提供解决问题整体方案的同时,保持灵活性:符合人类解决问题的通用模式。
要实现 "计划 ------ 执行" 多智能体,我们必须要解决几个问题:多模型、多工具集成,复杂流程编排,上下文管理以及中间步骤追踪。Eino(文档 cloudwego.io/zh/docs/ein... 项目页 github.com/cloudwego/e... 框架通过提供开箱即用的模型组件实现和工具执行器、面向抽象接口的灵活流程编排能力、完备的全局状态管理以及回调机制,确保了上述问题的有效解决。
接下来,文章将直观的解释 "计划 ------ 执行" 多智能体范式,介绍如何借助 Eino 框架来实现基于 DeepSeek 的'计划 ------ 执行'多智能体,最后通过一个有趣且靠谱的主题乐园行程规划助手的实战案例,带大家从 0 到 1 搭建一个完整的应用。
"计划------执行" 多智能体
基本的 ReAct 单智能体,是由一个 Agent 既负责计划拆解,也负责 Function Call:
可能存在的问题有三个:
-
对 LLM 的要求高:既要擅长推理规划,也要擅长做 Function Call。
-
LLM 的 prompt 复杂:既要能正确规划,又要正确的做 Function Call,还要能输出正确的结果。
-
没有计划:每次 Function Call 之后,LLM 需要重新推理,没有整体的可靠计划。
解决的思路,首先是把单个的 LLM 节点拆分成两个,一个负责 "计划",一个负责 "执行":
这样就解决了上面的问题 3,Planner 会给出完整计划,Executor 依据这个完整计划来依次执行。部分解决了问题 1、2,Planner 只需要擅长推理规划,Executor 则需要擅长做 Function Call 和总结,各自的 prompt 都是原先的一个子集。但同时带来一个新的问题:
- 缺少纠错能力:最开始的计划,在执行后,是否真的符合预期、能够解决问题?
继续优化多智能体结构,在 Executor 后面增加一个 LLM 节点,负责 "反思和调整计划":
这样就彻底解决了上面列出的问题,Executor 只需要按计划执行 Function Call,Reviser 负责反思和总结。
这就是 "计划------执行" 多智能体:通过将任务解决过程拆解为负责计划的 Planner 和 Reviser,以及负责执行的 Executor,实现了智能体的单一职责以及任务的有效计划与反思,同时也能够充分发挥 DeepSeek 这种推理模型的长项、规避其短板(Function Call)。
基于 Eino 框架实现 "计划------
执****行" 多智能体
实现一个 "计划------执行" 多智能体,需要:
-
能够快速简单的集成 DeepSeek、豆包等各种大模型。
-
能够快速简单的集成和执行各种 Tool。
-
能够快速实现流程编排,把多个智能体以及工具按设计的流程串联起来,并能随时快速调整。
-
能够及时的输出各智能体的执行过程,包括 DeepSeek 的推理过程。
-
能够有效的管理和传递上下文。
Eino 是字节跳动开源的基于 Golang 的大模型应用开发框架,已在豆包、抖音、扣子等多个业务线广泛使用。我们选择 Eino 作为框架来进行全码开发,因为:
-
Eino 可以用几行代码完成对各种大模型的调用,包括 DeepSeek。
-
Eino 可以用几行代码快速把一个本地 Function 封装成 Tool,且有开箱即用的 Tool 执行器。
-
Eino 的流程编排能力可靠且灵活:分支判断,循环,运行时参数配置等。
-
Eino 的数据流处理能力为大模型应用场景而设计,可配合完整的回调机制实时输出中间结果。
-
Eino 可以通过在图编排时配置和读写全局状态来实现有效的上下文管理和传递。
Eino 的详细信息参见:文档 cloudwego.io/zh/docs/ein...
GitHub 项目页 github.com/cloudwego/e...
实战:主题乐园行程规划助手
我们通过实现一个主题乐园行程规划助手,来探索如何用 Eino 实现基于 DeepSeek 的 "计划------执行" 多智能体。这个多智能体的功能是根据用户的游园需求,规划出具体、符合要求、可操作的行程安排。完整代码仓库地址:github.com/cloudwego/e...
定义多智能体
首先定义多智能体以及需要的配置:
go
package plan_execute
import (
"github.com/cloudwego/eino/components/model"
"github.com/cloudwego/eino/compose"
"github.com/cloudwego/eino/schema"
)
// Config "计划------执行"多智能体的配置.
type Config struct {
PlannerModel model.ChatModel // planner 智能体使用的大模型
PlannerSystemPrompt string // planner 智能体的 system prompt
ExecutorModel model.ChatModel // executor 智能体使用的大模型
ToolsConfig compose.ToolsNodeConfig // executor 智能体使用的工具执行器配置
ExecutorSystemPrompt string // executor 智能体的 system prompt
ReviserModel model.ChatModel // reviser 智能体使用的大模型
ReviserSystemPrompt string // reviser 智能体的 system prompt
MaxStep int // 多智能体的最大执行步骤数,避免无限循环
}
// PlanExecuteMultiAgent "计划------执行"多智能体.
type PlanExecuteMultiAgent struct {
// 图编排后的可执行体,输入是 Message 数组,输出是单条 Message
runnable compose.Runnable[[]*schema.Message, *schema.Message]
}
多智能体编排逻辑
Eino 的流程编排有 "节点(Node)"、"边(Edge)" 和"分支 (Branch)" 组成,数据流转时要求严格的类型对齐。完整的数据流转图如下:
上图中,Planner,Executor,Reviser 都是输入为 []*Message,输出为 * Message 的 ChatModel 节点,Branch1 判断 Executor 是否完成了本轮次所有的 Function Call,Branch2 判断 Reviser 是否输出了最终答案,各个 ToList 节点负责连接两个 ChatModel,将输出的 *Message 转化为 []*Message,从而满足类型校验要求。
我们实现一个 NewMultiAgent 方法来实现上述编排逻辑:
go
// NewMultiAgent 根据配置编排一个"计划------执行"多智能体.
func NewMultiAgent(ctx context.Context, config *Config) (*PlanExecuteMultiAgent, error) {
var (
toolInfos []*schema.ToolInfo
toolsNode *compose.ToolsNode
err error
plannerPrompt = config.PlannerSystemPrompt
executorPrompt = config.ExecutorSystemPrompt
reviserPrompt = config.ReviserSystemPrompt
maxStep = config.MaxStep
)
if len(plannerPrompt) == 0 {
plannerPrompt = defaultPlannerPrompt
}
if len(executorPrompt) == 0 {
executorPrompt = defaultExecutorPrompt
}
if len(reviserPrompt) == 0 {
reviserPrompt = defaultReviserPrompt
}
if maxStep == 0 {
maxStep = defaultMaxStep
}
if toolInfos, err = genToolInfos(ctx, config.ToolsConfig); err != nil {
return nil, err
}
// 为 Executor 配置工具
if err = config.ExecutorModel.BindTools(toolInfos); err != nil {
return nil, err
}
// 初始化 Tool 执行器节点,传入可执行的工具
if toolsNode, err = compose.NewToolNode(ctx, &config.ToolsConfig); err != nil {
return nil, err
}
// 创建一个待编排的 graph,规定整体的输入输出类型
graph := compose.NewGraph[[]*schema.Message, *schema.Message]()
// 定义 Executor 后的分支判断用的条件函数。该函数的输出是运行时选中的 NodeKey
executorPostBranchCondition := func(_ context.Context, msg *schema.Message) (endNode string, err error) {
if len(msg.ToolCalls) == 0 {
return nodeKeyExecutorToList, nil
}
return nodeKeyTools, nil
}
// 定义 Reviser 后的分支判断用的条件函数。
reviserPostBranchCondition := func(_ context.Context, sr *schema.StreamReader[*schema.Message]) (endNode string, err error) {
defer sr.Close()
var content string
for {
msg, err := sr.Recv()
if err != nil {
if err == io.EOF {
return nodeKeyReviserToList, nil
}
return "", err
}
content += msg.Content
if strings.Contains(content, "最终答案") {
return compose.END, nil
}
if len(content) > 20 {
return nodeKeyReviserToList, nil
}
}
}
// 添加 Planner 节点
_ = graph.AddChatModelNode(nodeKeyPlanner, config.PlannerModel, compose.WithNodeName(nodeKeyPlanner))
// 添加 Executor 节点
_ = graph.AddChatModelNode(nodeKeyExecutor, config.ExecutorModel, compose.WithNodeName(nodeKeyExecutor))
// 添加 Reviser 节点
_ = graph.AddChatModelNode(nodeKeyReviser, config.ReviserModel, compose.WithNodeName(nodeKeyReviser))
// 添加 Tool 执行器节点
_ = graph.AddToolsNode(nodeKeyTools, toolsNode)
// 添加三个 ToList 转换节点
_ = graph.AddLambdaNode(nodeKeyPlannerToList, compose.ToList[*schema.Message]())
_ = graph.AddLambdaNode(nodeKeyExecutorToList, compose.ToList[*schema.Message]())
_ = graph.AddLambdaNode(nodeKeyReviserToList, compose.ToList[*schema.Message]())
// 添加节点之间的边和分支
_ = graph.AddEdge(compose.START, nodeKeyPlanner)
_ = graph.AddEdge(nodeKeyPlanner, nodeKeyPlannerToList)
_ = graph.AddEdge(nodeKeyPlannerToList, nodeKeyExecutor)
_ = graph.AddBranch(nodeKeyExecutor, compose.NewStreamGraphBranch(executorPostBranchCondition, map[string]bool{
nodeKeyTools: true,
nodeKeyExecutorToList: true,
}))
_ = graph.AddEdge(nodeKeyTools, nodeKeyExecutor)
_ = graph.AddEdge(nodeKeyExecutorToList, nodeKeyReviser)
_ = graph.AddBranch(nodeKeyReviser, compose.NewStreamGraphBranch(reviserPostBranchCondition, map[string]bool{
nodeKeyReviserToList: true,
compose.END: true,
}))
_ = graph.AddEdge(nodeKeyReviserToList, nodeKeyExecutor)
// 编译 graph,将节点、边、分支转化为面向运行时的结构。由于 graph 中存在环,使用 AnyPredecessor 模式,同时设置运行时最大步数。
runnable, err := graph.Compile(ctx, compose.WithNodeTriggerMode(compose.AnyPredecessor), compose.WithMaxRunSteps(maxStep))
if err != nil {
return nil, err
}
return &PlanExecuteMultiAgent{
runnable: runnable,
}, nil
}
Tool 实现
我们的主题乐园行程规划助手,需要用到下列工具:
-
query_theme_park_opening_hour: 查询乐园 A 的整体营业时间
-
query_park_ticket_price: 查询乐园 A 的门票价格
-
list_locations: 列出乐园 A 中的所有区域,每个游乐设施都归属于一个区域
-
query_location_adjacency_info: 查询乐园 A 中的一个区域到其他相邻区域的步行时间,以分钟为单位
-
query_attraction_queue_time: 查询游乐设施的排队时间,以分钟为单位
-
query_attraction_info: 查询游乐设施的具体信息
-
query_performance_info: 查询演出的具体信息
-
query_restaurant_info: 查询餐厅的具体信息
-
validate_performance_time_table: 校验安排的表演场次是否符合事实
-
arrange_performances: 根据选中的表演名称,自动根据表演的时间表排程
-
validate_plan_items: 根据一个一日日程安排提案,校验各个计划项内部及之间是否自洽
首先定义核心的领域模型:
c
type ActivityType string
const (
ActivityTypeAttraction ActivityType = "attraction"
ActivityTypePerformance ActivityType = "performance"
ActivityTypeRestaurant ActivityType = "restaurant"
ActivityTypeOther ActivityType = "other"
)
// Activity 主题乐园中的一个项目,可以是游乐设施、表演或餐厅.
type Activity struct {
Name string `json:"name"`
Desc string `json:"desc"`
Type ActivityType `json:"type"`
Location string `json:"location" jsonschema:"description:项目所属的区域"`
MinHeight int `json:"min_height,omitempty" jsonschema:"description:参加游乐设施需要的最小身高,单位是厘米。如果为空,则没有身高要求"`
Duration int `json:"duration,omitempty" jsonschema:"description:一个项目参加一次需要的时间,注意不包括排队的时间。如果为空,则缺少具体的时间信息"`
TimeTable []string `json:"time_table,omitempty" jsonschema:"description:一个演出的时间表。如果为空,则使用 OpenTime 和 CloseTime 来表示这个项目的运营时间范围"`
OpenTime string `json:"open_time,omitempty" jsonschema:"description:一个项目开始运营的时间"`
CloseTime string `json:"close_time,omitempty" jsonschema:"description:一个项目结束运营的时间"`
RequireBooking bool `json:"require_booking,omitempty" jsonschema:"description:一个餐厅是否需要提前预约"`
HasPriorityAccess bool `json:"has_priority_access,omitempty" jsonschema:"description:一个项目是否有高速票服务"`
PriorityAccessCost int `json:"priority_access_cost,omitempty" jsonschema:"description:一个项目如果有高速票服务,则一个人的高速票需要花多少钱"`
QueueTime int `json:"queue_time,omitempty" jsonschema:"description:一个项目常规需要的排队时间,单位是分钟。如果为空,则这个项目一般不需要排队"`
}
go
注意大多数字段中都有 jsonschema:"description:xxx" 的 go struct tag。Eino 框架可抽取这个信息以及其他的 tag 给到大模型。
实现工具列表中需要的本地 function,如:
go
// GetAttractionInfo 获取游乐设施信息.
func GetAttractionInfo(_ context.Context, in *ListAttractionRequest) (out *ListAttractionResponse, err error) {
if len(in.Name) > 0 && in.Name != "all" {
for _, a := range attractions {
if a.Name == in.Name {
return &ListAttractionResponse{
Attractions: []Activity{
a,
},
}, nil
}
}
}
if len(in.Location) > 0 {
locationAttractions := make([]Activity, 0)
for _, a := range attractions {
if a.Location == in.Location {
locationAttractions = append(locationAttractions, a)
return &ListAttractionResponse{
Attractions: locationAttractions,
}, nil
}
}
}
return &ListAttractionResponse{
Attractions: attractions,
}, nil
}
完整的领域模型及服务定义参见代码链接 (github.com/cloudwego/e...%25E3%2580%2582 "https://github.com/cloudwego/eino-examples/blob/main/flow/agent/multiagent/plan_execute/tools/theme_park.go)%E3%80%82")
数据来源:可以是主题乐园提供的 API,也可以是外置的数据库,在我们的场景中,直接在项目中维护结构化的信息(完整代码链接 github.com/cloudwego/e...
将本地 function 封装成 Tool:
go
func GetTools(ctx context.Context) (tools []tool.BaseTool, err error) { queryTimeTool, err := utils.InferTool("query_theme_park_opening_hour", "查询乐园 A 的整体营业时间", GetParkHour) if err != nil { return nil, err } tools = append(tools, queryTimeTool) // 以下省略多个 Tool return}
完整的 Tool 封装代码参见代码链接 (github.com/cloudwego/e...%25E3%2580%2582 "https://github.com/cloudwego/eino-examples/blob/main/flow/agent/multiagent/plan_execute/tools/tools.go)%E3%80%82")
上下文管理
针对每个智能体的一次执行,它的上下文应当包括:
-
用户输入的任务。
-
之前执行的智能体(包括自身)的输出。
-
之前执行的智能体的 Function Call,以及对应的结果。
-
自身的 System Prompt。
为了保存多智能体的上下文,我们为 graph 增加全局状态,并在各智能体执行前以及 Tool 执行前,向这个全局状态中读写上下文:
go
// state 以多智能体一次运行为 scope 的全局状态,用于记录上下文
type state struct {
messages []*schema.Message
}
func NewMultiAgent(ctx context.Context, config *Config) (*PlanExecuteMultiAgent, error) {
// ... 省略 N 行 ...
// 创建一个待编排的 graph,规定整体的输入输出类型,配置全局状态的初始化方法
graph := compose.NewGraph[[]*schema.Message, *schema.Message](compose.WithGenLocalState(func(ctx context.Context) *state {
return &state{}
}))
// 在大模型执行之前,向全局状态中保存上下文,并组装本次的上下文
modelPreHandle := func(systemPrompt string, isDeepSeek bool) compose.StatePreHandler[[]*schema.Message, *state] {
return func(ctx context.Context, input []*schema.Message, state *state) ([]*schema.Message, error) {
for _, msg := range input {
state.messages = append(state.messages, msg)
}
if isDeepSeek {
return append([]*schema.Message{schema.SystemMessage(systemPrompt)}, convertMessagesForDeepSeek(state.messages)...), nil
}
return append([]*schema.Message{schema.SystemMessage(systemPrompt)}, state.messages...), nil
}
}
// ... 省略 N 行 ...
// 添加 Planner 节点,同时添加 StatePreHandler 读写上下文
_ = graph.AddChatModelNode(nodeKeyPlanner, config.PlannerModel, compose.WithStatePreHandler(modelPreHandle(plannerPrompt, true)), compose.WithNodeName(nodeKeyPlanner))
// 添加 Executor 节点,同时添加 StatePreHandler 读写上下文
_ = graph.AddChatModelNode(nodeKeyExecutor, config.ExecutorModel, compose.WithStatePreHandler(modelPreHandle(executorPrompt, false)), compose.WithNodeName(nodeKeyExecutor))
// 添加 Reviser 节点,同时添加 StatePreHandler 读写上下文
_ = graph.AddChatModelNode(nodeKeyReviser, config.ReviserModel, compose.WithStatePreHandler(modelPreHandle(reviserPrompt, true)), compose.WithNodeName(nodeKeyReviser))
// 添加 Tool 执行器节点,同时添加 StatePreHandler 读写上下文
_ = graph.AddToolsNode(nodeKeyTools, toolsNode, compose.WithStatePreHandler(func(ctx context.Context, in *schema.Message, state *state) (*schema.Message, error) {
state.messages = append(state.messages, in)
return in, nil
}))
// ... 省略 N 行 ...
}
完整编排代码见链接 github.com/cloudwego/e...
main 函数:多智能体执行
多智能体执行逻辑需要实现下列功能:
-
实例化 DeepSeek 和豆包的模型,并放到多智能体的配置中。
-
获取 Tool 列表。
-
依据配置编排和初始化多智能体。
-
将多智能体的各中间步骤及时输出。
在 main 函数中:利用 Eino 框架提供的组件实现,实例化需要的大模型,获取 Tool,初始化多智能体:
go
func main() {
ctx := context.Background()
deepSeekModel, err := deepseek.NewChatModel(ctx, &deepseek.ChatModelConfig{
Model: os.Getenv("DEEPSEEK_MODEL_ID"),
APIKey: os.Getenv("DEEPSEEK_API_KEY"),
BaseURL: os.Getenv("DEEPSEEK_BASE_URL"),
})
if err != nil {
log.Fatalf("new DeepSeek model failed: %v", err)
}
arkModel, err := ark.NewChatModel(ctx, &ark.ChatModelConfig{
APIKey: os.Getenv("ARK_API_KEY"),
Model: os.Getenv("ARK_MODEL_ID"),
})
if err != nil {
log.Fatalf("new Ark model failed: %v", err)
}
toolsConfig, err := tools.GetTools(ctx)
if err != nil {
log.Fatalf("get tools config failed: %v", err)
}
// 创建多智能体的配置,system prompt 都用默认值
config := &Config{
// planner 在调试时大部分场景不需要真的去生成,可以用 mock 输出替代
PlannerModel: &debug.ChatModelDebugDecorator{
Model: deepSeekModel,
},
ExecutorModel: arkModel,
ToolsConfig: compose.ToolsNodeConfig{Tools: toolsConfig},
ReviserModel: &debug.ChatModelDebugDecorator{
Model: deepSeekModel,
},
}
planExecuteAgent, err := NewMultiAgent(ctx, config)
if err != nil {
log.Fatalf("new plan execute multi agent failed: %v", err)
}
printer := newIntermediateOutputPrinter() // 创建一个中间结果打印器
printer.printStream() // 开始异步输出到 console
handler := printer.toCallbackHandler() // 转化为 Eino 框架的 callback handler
// 以流式方式调用多智能体,实际的 OutputStream 不再需要关注,因为所有输出都由 intermediateOutputPrinter 处理了
_, err = planExecuteAgent.Stream(ctx, []*schema.Message{schema.UserMessage("我们一家三口去乐园玩,孩子身高 120 cm,园内预算 2000 元,最爱的是各种表演,游乐设施比较偏爱刺激项目,希望能在一天内尽可能多体验不同的活动,帮忙规划一个行程。我们会在园区开门之后立刻入园,在园区关闭之后再离开。")},
agent.WithComposeOptions(compose.WithCallbacks(handler)), // 将中间结果打印的 callback handler 注入进来
// 给 planner 指定 mock 输出
//agent.WithComposeOptions(compose.WithChatModelOption(debug.WithDebugOutput(schema.AssistantMessage(debug.PlannerOutput, nil))).DesignateNode(nodeKeyPlanner)),
// 给 reviser 指定 mock 输出
//agent.WithComposeOptions(compose.WithChatModelOption(debug.WithDebugOutput(schema.AssistantMessage("最终答案", nil))).DesignateNode(nodeKeyReviser)),
)
if err != nil {
log.Fatalf("stream error: %v", err)
}
printer.wait() // 等待所有输出都处理完再结束
}
完整 main 函数代码实现链接 github.com/cloudwego/e...
流式输出中间过程
在上面的 main 函数中可以看到,我们通过 printer 这个 "中间结果打印器",把各智能体的流式输出异步打印了出来。这利用了 Eino 框架的 callback 机制,在 ChatModel 输出时和 Tool 起止时触发执行切面逻辑。
调试与优化
在上面的 main 函数中,通过 ChatModelDebugDecorator 把 Planner 智能体封装起来,这是因为调试过程中,我们经常需要固定 Planner 的输出,单独调试后续流程。在 Eino 框架中,能够很方便的实现类似的装饰者,因为所有的组件如 ChatModel 等都是 interface,从编排角度看,原始的 ChatModel 实现和对应的装饰者可以无缝替换。完整调试代码实现见链接 github.com/cloudwego/e...
如果你更习惯可视化调试,可以安装 Eino Dev IDE 插件 www.cloudwego.io/zh/docs/ein... Golang 和 VSCode),把多智能体的编排拓扑在 IDE 中直观的展示出来,还可以指定中间的某个智能体开始调试。
在实战过程中,对 system prompt 的优化占据了相当多的时间,有关优化的最佳实践还在总结之中,后续会在 Eino 项目中发布,可以关注我们的 Github 项目 github.com/cloudwego/e... 获取最新的进展。
实际效果
以下为 Reviser 智能体的最终输出:
最终答案:
总费用:门票 1100 元 + 高速票 540 元 + 餐饮 420 元 = **2060 元 **(超支 60 元,可通过午餐降级为蓝莓熊餐盒节省 60 元)
关键优化点:
-
高速票精准投放:仅在 "抱抱熊飞天赛车" 午间时段使用(性价比最高),其他项目利用排队低峰期
-
表演全覆盖:新增 "复仇者小分队培训行动",保留所有核心表演场次
-
动线优化:区域集中化(未来世界→玩具的故事→幻想世界→奇幻园林→入口大街)
-
预算控制:通过调整午餐为快餐(蓝莓熊餐盒人均 50 元)可使总费用降至 2000 元以内
-
刺激项目最大化:包含 3 个过山车类项目(抱抱熊 / 矿山车 / 飞吧地平线)+ 2 个黑暗骑乘(家勒比海贼 / 派斯音速)
执行效果:在保证所有热门表演的前提下,实现 5 个高刺激项目体验,时间利用率达 92%,区域移动时间占比仅 8%
完整执行过程见链接 github.com/cloudwego/e...
相关链接
Eino 框架 Github 仓库地址:
-
扩展仓库,含本文用到的 DeepSeek 和豆包模型实现:github.com/cloudwego/e...
-
示例仓库,含本文内的主题乐园行程规划助手:github.com/cloudwego/e...
项目官网:www.cloudwego.io