Local branch had diverged from origin/master (sibling commits on the same base). Rewrote local history linearly on top of origin/master, folding in all local content: exa/hot-platform discovery hunters, leaks ledger, nightly sweep orchestrator, updated .gitignore and skill docs, plus the latest hunt output and vault state. Remote-only files (vault keys, channel scripts) were restored rather than dropped, so the resulting tree is a full union of both sides.
1 line
135 KiB
JSON
1 line
135 KiB
JSON
{"repo": "celanwang/einotelos", "files": {"lab01/chat_quickstart.go": {"ec86072535f9835cf7765273291366d07bf9a0bc": {"t": 1785745618.9314787, "n": 1488, "h": "a1b6c9ff8cf544d0", "c": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\n\t\"github.com/cloudwego/eino-ext/components/model/deepseek\"\n\t\"github.com/cloudwego/eino/schema\"\n)\n\nfunc main() {\n\t// This is a placeholder for the chat quickstart application.\n\n\t// 1. 创建上下文\n\tctx := context.Background()\n\n\t// 2. 创建 ChatModel 实例\n\tchatModel, err := deepseek.NewChatModel(ctx, &deepseek.ChatModelConfig{\n\t\t// 提供火山 ARK 的 APIKey 模型名称的可选项\n\t\t// APIKey: os.Getenv(\"ARK_API_KEY\"),\n\t\t// Model: os.Getenv(\"ARK_MODEL_NAME\"),\n\t\t// 注:对于获取环境变量和配置 .env 文件都可以\n\t\tAPIKey: os.Getenv(\"DEEPSEEK_API_KEY\"), // 获取环境变量中的 APIKey 配置\n\t\tModel: \"deepseek-chat\", // 指定使用的模型名称\n\t\tBaseURL: \"https://api.deepseek.com\", // 自选的 API 服务器地址\n\t})\n\tif err != nil {\n\t\tlog.Fatalf(\"创建 ChatModel 实例失败: %v\", err)\n\t}\n\n\t// 3. 准备发送聊天请求\n\tmessages := []*schema.Message{\n\t\tschema.SystemMessage(\"你是一个知识渊博的篮球解说员\"), // 系统消息,设定对话背景\n\t\tschema.UserMessage(\"你好,请介绍一下 Kobe Bryant 的职业生涯。\"), // 用户消息,提出问题\n\t}\n\n\t// 4. 调用模型生成响应\n\tresponse, err := chatModel.Generate(ctx, messages)\n\tif err != nil {\n\t\tlog.Fatalf(\"生成响应失败: %v\", err)\n\t}\n\n\t// 5. 输出结果\n\tfmt.Printf(\"AI 响应: %s\\n\", response.Content)\n\n\t// 6. 输出 token 使用情况(可选项)\n\tif response.ResponseMeta != nil && response.ResponseMeta.Usage != nil {\n\t\tfmt.Printf(\"\\nToken 使用统计:\\n\")\n\t\tfmt.Printf(\" 输入 Token: %d\\n\", response.ResponseMeta.Usage.PromptTokens)\n\t\tfmt.Printf(\" 输出 Token: %d\\n\", response.ResponseMeta.Usage.CompletionTokens)\n\t\tfmt.Printf(\" 总计 Token: %d\\n\", response.ResponseMeta.Usage.TotalTokens)\n\t}\n}\n"}, "c2568ad0faf623a556b7756e72e37ad5c5092a8e": {"t": 1785754918.4101708, "n": 1488, "h": "a1b6c9ff8cf544d0", "c": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\n\t\"github.com/cloudwego/eino-ext/components/model/deepseek\"\n\t\"github.com/cloudwego/eino/schema\"\n)\n\nfunc main() {\n\t// This is a placeholder for the chat quickstart application.\n\n\t// 1. 创建上下文\n\tctx := context.Background()\n\n\t// 2. 创建 ChatModel 实例\n\tchatModel, err := deepseek.NewChatModel(ctx, &deepseek.ChatModelConfig{\n\t\t// 提供火山 ARK 的 APIKey 模型名称的可选项\n\t\t// APIKey: os.Getenv(\"ARK_API_KEY\"),\n\t\t// Model: os.Getenv(\"ARK_MODEL_NAME\"),\n\t\t// 注:对于获取环境变量和配置 .env 文件都可以\n\t\tAPIKey: os.Getenv(\"DEEPSEEK_API_KEY\"), // 获取环境变量中的 APIKey 配置\n\t\tModel: \"deepseek-chat\", // 指定使用的模型名称\n\t\tBaseURL: \"https://api.deepseek.com\", // 自选的 API 服务器地址\n\t})\n\tif err != nil {\n\t\tlog.Fatalf(\"创建 ChatModel 实例失败: %v\", err)\n\t}\n\n\t// 3. 准备发送聊天请求\n\tmessages := []*schema.Message{\n\t\tschema.SystemMessage(\"你是一个知识渊博的篮球解说员\"), // 系统消息,设定对话背景\n\t\tschema.UserMessage(\"你好,请介绍一下 Kobe Bryant 的职业生涯。\"), // 用户消息,提出问题\n\t}\n\n\t// 4. 调用模型生成响应\n\tresponse, err := chatModel.Generate(ctx, messages)\n\tif err != nil {\n\t\tlog.Fatalf(\"生成响应失败: %v\", err)\n\t}\n\n\t// 5. 输出结果\n\tfmt.Printf(\"AI 响应: %s\\n\", response.Content)\n\n\t// 6. 输出 token 使用情况(可选项)\n\tif response.ResponseMeta != nil && response.ResponseMeta.Usage != nil {\n\t\tfmt.Printf(\"\\nToken 使用统计:\\n\")\n\t\tfmt.Printf(\" 输入 Token: %d\\n\", response.ResponseMeta.Usage.PromptTokens)\n\t\tfmt.Printf(\" 输出 Token: %d\\n\", response.ResponseMeta.Usage.CompletionTokens)\n\t\tfmt.Printf(\" 总计 Token: %d\\n\", response.ResponseMeta.Usage.TotalTokens)\n\t}\n}\n"}, "594fb03f94ddf45f442946a44873d15ec32d528e": {"t": 1785754919.793102, "n": 1458, "h": "ae11a9b85f930003", "c": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\n\t\"github.com/cloudwego/eino-ext/components/model/deepseek\"\n\t\"github.com/cloudwego/eino/schema\"\n)\n\nfunc main() {\n\t// This is a placeholder for the chat quickstart application.\n\n\t// 1. 创建上下文\n\tctx := context.Background()\n\n\t// 2. 创建 ChatModel 实例\n\tchatModel, err := deepseek.NewChatModel(ctx, &deepseek.ChatModelConfig{\n\t\t// 提供火山 ARK 的 APIKey 模型名称的可选项\n\t\t// APIKey: os.Getenv(\"ARK_API_KEY\"),\n\t\t// Model: os.Getenv(\"ARK_MODEL_NAME\"),\n\t\tAPIKey: os.Getenv(\"DEEPSEEK_API_KEY\"), // 获取环境变量中的 APIKey 配置\n\t\tModel: \"deepseek-chat\", // 指定使用的模型名称\n\t\tBaseURL: \"https://api.deepseek.com\", // 自选的 API 服务器地址\n\t})\n\tif err != nil {\n\t\tlog.Fatalf(\"创建 ChatModel 实例失败: %v\", err)\n\t}\n\n\t// 3. 准备发送聊天请求\n\tmessages := []*schema.Message{\n\t\tschema.SystemMessage(\"你是一个知识渊博的篮球解说员\"), // 系统消息,设定对话背景\n\t\tschema.UserMessage(\"你好,请介绍一下 Kobe Bryant 的职业生涯。\"), // 用户消息,提出问题\n\t}\n\n\t// 4. 调用模型生成响应\n\tresponse, err := chatModel.Generate(ctx, messages)\n\tif err != nil {\n\t\tlog.Fatalf(\"生成响应失败: %v\", err)\n\t}\n\n\t// 5. 输出结果\n\tfmt.Printf(\"AI 响应: %s\\n\", response.Content)\n\n\t// 6. 输出 token 使用情况(可选项)\n\tif response.ResponseMeta != nil && response.ResponseMeta.Usage != nil {\n\t\tfmt.Printf(\"\\nToken 使用统计:\\n\")\n\t\tfmt.Printf(\" 输入 Token: %d\\n\", response.ResponseMeta.Usage.PromptTokens)\n\t\tfmt.Printf(\" 输出 Token: %d\\n\", response.ResponseMeta.Usage.CompletionTokens)\n\t\tfmt.Printf(\" 总计 Token: %d\\n\", response.ResponseMeta.Usage.TotalTokens)\n\t}\n}\n"}, "7acba5ff41f726cf5a20207300bea1a91d444e03": {"t": 1785754921.0494795, "n": 1345, "h": "0eb6a6c482764225", "c": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\n\t\"github.com/cloudwego/eino-ext/components/model/deepseek\"\n\t\"github.com/cloudwego/eino/schema\"\n)\n\nfunc main() {\n\t// This is a placeholder for the chat quickstart application.\n\n\t// 1. 创建上下文\n\tctx := context.Background()\n\n\t// 2. 创建 ChatModel 实例\n\tchatModel, err := deepseek.NewChatModel(ctx, &deepseek.ChatModelConfig{\n\t\tAPIKey: os.Getenv(\"DEEPSEEK_API_KEY\"), // 获取环境变量中的 APIKey 配置\n\t\tModel: \"deepseek-chat\", // 指定使用的模型名称\n\t\tBaseURL: \"https://api.deepseek.com\", // 自选的 API 服务器地址\n\t})\n\tif err != nil {\n\t\tlog.Fatalf(\"创建 ChatModel 实例失败: %v\", err)\n\t}\n\n\t// 3. 准备发送聊天请求\n\tmessages := []*schema.Message{\n\t\tschema.SystemMessage(\"你是一个知识渊博的篮球解说员\"), // 系统消息,设定对话背景\n\t\tschema.UserMessage(\"你好,请介绍一下 Kobe Bryant 的职业生涯。\"), // 用户消息,提出问题\n\t}\n\n\t// 4. 调用模型生成响应\n\tresponse, err := chatModel.Generate(ctx, messages)\n\tif err != nil {\n\t\tlog.Fatalf(\"生成响应失败: %v\", err)\n\t}\n\n\t// 5. 输出结果\n\tfmt.Printf(\"AI 响应: %s\\n\", response.Content)\n\n\t// 6. 输出 token 使用情况(可选项)\n\tif response.ResponseMeta != nil && response.ResponseMeta.Usage != nil {\n\t\tfmt.Printf(\"\\nToken 使用统计:\\n\")\n\t\tfmt.Printf(\" 输入 Token: %d\\n\", response.ResponseMeta.Usage.PromptTokens)\n\t\tfmt.Printf(\" 输出 Token: %d\\n\", response.ResponseMeta.Usage.CompletionTokens)\n\t\tfmt.Printf(\" 总计 Token: %d\\n\", response.ResponseMeta.Usage.TotalTokens)\n\t}\n}\n"}, "7ac1c2eb8abf3b7c52bd83aeeb77dae0e438dae6": {"t": 1785754922.2794313, "n": 1458, "h": "1012911eeb3e24ec", "c": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\n\t\"github.com/cloudwego/eino-ext/components/model/deepseek\"\n\t\"github.com/cloudwego/eino/schema\"\n)\n\nfunc main() {\n\t// This is a placeholder for the chat quickstart application.\n\n\t// 1. 创建上下文\n\tctx := context.Background()\n\n\t// 2. 创建 ChatModel 实例\n\tchatModel, err := deepseek.NewChatModel(ctx, &deepseek.ChatModelConfig{\n\t\t// 提供火山 ARK 的 APIKey 模型名称的可选项\n\t\t// APIKey: os.Getenv(\"ARK_API_KEY\"),\n\t\t// Model: os.Getenv(\"ARK_MODEL_NAME\"),\n\t\tAPIKey: os.Getenv(\"deepseek_api_key\"), // 获取环境变量中的 APIKey 配置\n\t\tModel: \"deepseek-chat\", // 指定使用的模型名称\n\t\tBaseURL: \"https://api.deepseek.com\", // 自选的 API 服务器地址\n\t})\n\tif err != nil {\n\t\tlog.Fatalf(\"创建 ChatModel 实例失败: %v\", err)\n\t}\n\n\t// 3. 准备发送聊天请求\n\tmessages := []*schema.Message{\n\t\tschema.SystemMessage(\"你是一个知识渊博的篮球解说员\"), // 系统消息,设定对话背景\n\t\tschema.UserMessage(\"你好,请介绍一下 Kobe Bryant 的职业生涯。\"), // 用户消息,提出问题\n\t}\n\n\t// 4. 调用模型生成响应\n\tresponse, err := chatModel.Generate(ctx, messages)\n\tif err != nil {\n\t\tlog.Fatalf(\"生成响应失败: %v\", err)\n\t}\n\n\t// 5. 输出结果\n\tfmt.Printf(\"AI 响应: %s\\n\", response.Content)\n\n\t// 6. 输出 token 使用情况(可选项)\n\tif response.ResponseMeta != nil && response.ResponseMeta.Usage != nil {\n\t\tfmt.Printf(\"\\nToken 使用统计:\\n\")\n\t\tfmt.Printf(\" 输入 Token: %d\\n\", response.ResponseMeta.Usage.PromptTokens)\n\t\tfmt.Printf(\" 输出 Token: %d\\n\", response.ResponseMeta.Usage.CompletionTokens)\n\t\tfmt.Printf(\" 总计 Token: %d\\n\", response.ResponseMeta.Usage.TotalTokens)\n\t}\n}\n"}, "723138e9cc733176016e3259f70db985cbeed5e0": {"t": 1785754924.8390448, "n": 1419, "h": "21c3260362e0aa07", "c": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\n\t\"github.com/cloudwego/eino-ext/components/model/deepseek\"\n\t\"github.com/cloudwego/eino/schema\"\n)\n\nfunc main() {\n\t// This is a placeholder for the chat quickstart application.\n\n\t// 1. 创建上下文\n\tctx := context.Background()\n\n\t// 2. 创建 ChatModel 实例\n\tchatModel, err := deepseek.NewChatModel(ctx, &deepseek.ChatModelConfig{\n\t\t// 提供火山 ARK 的 APIKey 模型名称的可选项\n\t\t// APIKey: os.Getenv(\"ARK_API_KEY\"),\n\t\t// Model: os.Getenv(\"ARK_MODEL_NAME\"),\n\t\tAPIKey: os.Getenv(\"deepseek_api_key\"), // 获取环境变量中的 APIKey 配置\n\t\tModel: \"deepseek-chat\", // 指定使用的模型名称\n\t\tBaseURL: \"https://api.deepseek.com\", // 自选的 API 服务器地址\n\t})\n\tif err != nil {\n\t\tlog.Fatalf(\"创建 ChatModel 实例失败: %v\", err)\n\t}\n\n\t// 3. 准备发送聊天请求\n\tmessages := []*schema.Message{\n\t\tschema.SystemMessage(\"你是一个知识渊博的篮球解说员\"),\n\t\tschema.UserMessage(\"你好,请介绍一下 Kobe Bryant 的职业生涯。\"),\n\t}\n\n\t// 4. 调用模型生成响应\n\tresponse, err := chatModel.Generate(ctx, messages)\n\tif err != nil {\n\t\tlog.Fatalf(\"生成响应失败: %v\", err)\n\t}\n\n\t// 5. 输出结果\n\tfmt.Printf(\"AI 响应: %s\\n\", response.Content)\n\n\t// 6. 输出 token 使用情况(可选项)\n\tif response.ResponseMeta != nil && response.ResponseMeta.Usage != nil {\n\t\tfmt.Printf(\"\\nToken 使用统计:\\n\")\n\t\tfmt.Printf(\" 输入 Token: %d\\n\", response.ResponseMeta.Usage.PromptTokens)\n\t\tfmt.Printf(\" 输出 Token: %d\\n\", response.ResponseMeta.Usage.CompletionTokens)\n\t\tfmt.Printf(\" 总计 Token: %d\\n\", response.ResponseMeta.Usage.TotalTokens)\n\t}\n}\n"}, "6390edb3c7d6ff896121d739f9d8f1e5993f5c97": {"t": 1785754927.213235, "n": 1345, "h": "daf19301db7c2b47", "c": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\n\t\"github.com/cloudwego/eino-ext/components/model/deepseek\"\n\t\"github.com/cloudwego/eino/schema\"\n)\n\nfunc main() {\n\t// This is a placeholder for the chat quickstart application.\n\n\t// 1. 创建上下文\n\tctx := context.Background()\n\n\t// 2. 创建 ChatModel 实例\n\tchatModel, err := deepseek.NewChatModel(ctx, &deepseek.ChatModelConfig{\n\t\tAPIKey: os.Getenv(\"deepseek_api_key\"), // 获取环境变量中的 APIKey 配置\n\t\tModel: \"deepseek-chat\", // 指定使用的模型名称\n\t\tBaseURL: \"https://api.deepseek.com\", // 自选的 API 服务器地址\n\t})\n\tif err != nil {\n\t\tlog.Fatalf(\"创建 ChatModel 实例失败: %v\", err)\n\t}\n\n\t// 3. 准备发送聊天请求\n\tmessages := []*schema.Message{\n\t\tschema.SystemMessage(\"你是一个知识渊博的篮球解说员\"), // 系统消息,设定对话背景\n\t\tschema.UserMessage(\"你好,请介绍一下 Kobe Bryant 的职业生涯。\"), // 用户消息,提出问题\n\t}\n\n\t// 4. 调用模型生成响应\n\tresponse, err := chatModel.Generate(ctx, messages)\n\tif err != nil {\n\t\tlog.Fatalf(\"生成响应失败: %v\", err)\n\t}\n\n\t// 5. 输出结果\n\tfmt.Printf(\"AI 响应: %s\\n\", response.Content)\n\n\t// 6. 输出 token 使用情况(可选项)\n\tif response.ResponseMeta != nil && response.ResponseMeta.Usage != nil {\n\t\tfmt.Printf(\"\\nToken 使用统计:\\n\")\n\t\tfmt.Printf(\" 输入 Token: %d\\n\", response.ResponseMeta.Usage.PromptTokens)\n\t\tfmt.Printf(\" 输出 Token: %d\\n\", response.ResponseMeta.Usage.CompletionTokens)\n\t\tfmt.Printf(\" 总计 Token: %d\\n\", response.ResponseMeta.Usage.TotalTokens)\n\t}\n}\n"}, "08caa1f13da029fa5c62315fdbd51d51a186e0b7": {"t": 1785754928.479975, "n": 1306, "h": "ea373e647db817d0", "c": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\n\t\"github.com/cloudwego/eino-ext/components/model/deepseek\"\n\t\"github.com/cloudwego/eino/schema\"\n)\n\nfunc main() {\n\t// This is a placeholder for the chat quickstart application.\n\n\t// 1. 创建上下文\n\tctx := context.Background()\n\n\t// 2. 创建 ChatModel 实例\n\tchatModel, err := deepseek.NewChatModel(ctx, &deepseek.ChatModelConfig{\n\t\tAPIKey: os.Getenv(\"deepseek_api_key\"), // 获取环境变量中的 APIKey 配置\n\t\tModel: \"deepseek-chat\", // 指定使用的模型名称\n\t\tBaseURL: \"https://api.deepseek.com\", // 自选的 API 服务器地址\n\t})\n\tif err != nil {\n\t\tlog.Fatalf(\"创建 ChatModel 实例失败: %v\", err)\n\t}\n\n\t// 3. 准备发送聊天请求\n\tmessages := []*schema.Message{\n\t\tschema.SystemMessage(\"你是一个知识渊博的篮球解说员\"),\n\t\tschema.UserMessage(\"你好,请介绍一下 Kobe Bryant 的职业生涯。\"),\n\t}\n\n\t// 4. 调用模型生成响应\n\tresponse, err := chatModel.Generate(ctx, messages)\n\tif err != nil {\n\t\tlog.Fatalf(\"生成响应失败: %v\", err)\n\t}\n\n\t// 5. 输出结果\n\tfmt.Printf(\"AI 响应: %s\\n\", response.Content)\n\n\t// 6. 输出 token 使用情况(可选项)\n\tif response.ResponseMeta != nil && response.ResponseMeta.Usage != nil {\n\t\tfmt.Printf(\"\\nToken 使用统计:\\n\")\n\t\tfmt.Printf(\" 输入 Token: %d\\n\", response.ResponseMeta.Usage.PromptTokens)\n\t\tfmt.Printf(\" 输出 Token: %d\\n\", response.ResponseMeta.Usage.CompletionTokens)\n\t\tfmt.Printf(\" 总计 Token: %d\\n\", response.ResponseMeta.Usage.TotalTokens)\n\t}\n}\n"}}, "lab04/replace/var_replace.go": {"ec86072535f9835cf7765273291366d07bf9a0bc": {"t": 1785745619.265725, "n": 1208, "h": "fa59de6b22727c68", "c": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\n\t\"github.com/cloudwego/eino-ext/components/model/deepseek\"\n\t\"github.com/cloudwego/eino/components/prompt\"\n\t\"github.com/cloudwego/eino/schema\"\n)\n\nfunc main() {\n\tctx := context.Background()\n\n\t// 1. 创建 ChatTemplate\n\ttemplate := prompt.FromMessages(\n\t\tschema.FString, // 使用 FString 格式化(类似 Python 的 f-string)\n\t\tschema.SystemMessage(\"你是一个{role}\"),\n\t\tschema.UserMessage(\"{question}\"),\n\t)\n\n\t// 2. 准备变量\n\tvariables := map[string]any{\n\t\t\"role\": \"你是一个热爱运动的程序员。\",\n\t\t\"question\": \"你认为运动和工作哪个更重要?\",\n\t}\n\n\t// 3. 格式化消息\n\tmessages, err := template.Format(ctx, variables)\n\tif err != nil {\n\t\tlog.Fatalf(\"格式化失败: %v\", err)\n\t}\n\n\t// 4. 查看生成的消息\n\tfmt.Println(\"生成的消息:\")\n\tfor i, msg := range messages {\n\t\tfmt.Printf(\"%d. [%s] %s\\\\n\", i+1, msg.Role, msg.Content)\n\t}\n\n\t// 5. 使用生成的消息调用模型\n\tchatModel, err := deepseek.NewChatModel(ctx, &deepseek.ChatModelConfig{\n\t\tAPIKey: os.Getenv(\"DEEPSEEK_API_KEY\"),\n\t\tModel: \"deepseek-chat\",\n\t\tBaseURL: \"https://api.deepseek.com\",\n\t})\n\tif err != nil {\n\t\tlog.Fatalf(\"创建模型失败: %v\", err)\n\t}\n\n\tresponse, err := chatModel.Generate(ctx, messages)\n\tif err != nil {\n\t\tlog.Fatalf(\"生成失败: %v\", err)\n\t}\n\n\tfmt.Printf(\"\\\\nAI 回答:\\\\n%s\\\\n\", response.Content)\n}\n"}, "f52b079c54b6e9b6ce33d8fb663ca8c397a6a297": {"t": 1785754913.916639, "n": 1208, "h": "fa59de6b22727c68", "c": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\n\t\"github.com/cloudwego/eino-ext/components/model/deepseek\"\n\t\"github.com/cloudwego/eino/components/prompt\"\n\t\"github.com/cloudwego/eino/schema\"\n)\n\nfunc main() {\n\tctx := context.Background()\n\n\t// 1. 创建 ChatTemplate\n\ttemplate := prompt.FromMessages(\n\t\tschema.FString, // 使用 FString 格式化(类似 Python 的 f-string)\n\t\tschema.SystemMessage(\"你是一个{role}\"),\n\t\tschema.UserMessage(\"{question}\"),\n\t)\n\n\t// 2. 准备变量\n\tvariables := map[string]any{\n\t\t\"role\": \"你是一个热爱运动的程序员。\",\n\t\t\"question\": \"你认为运动和工作哪个更重要?\",\n\t}\n\n\t// 3. 格式化消息\n\tmessages, err := template.Format(ctx, variables)\n\tif err != nil {\n\t\tlog.Fatalf(\"格式化失败: %v\", err)\n\t}\n\n\t// 4. 查看生成的消息\n\tfmt.Println(\"生成的消息:\")\n\tfor i, msg := range messages {\n\t\tfmt.Printf(\"%d. [%s] %s\\\\n\", i+1, msg.Role, msg.Content)\n\t}\n\n\t// 5. 使用生成的消息调用模型\n\tchatModel, err := deepseek.NewChatModel(ctx, &deepseek.ChatModelConfig{\n\t\tAPIKey: os.Getenv(\"DEEPSEEK_API_KEY\"),\n\t\tModel: \"deepseek-chat\",\n\t\tBaseURL: \"https://api.deepseek.com\",\n\t})\n\tif err != nil {\n\t\tlog.Fatalf(\"创建模型失败: %v\", err)\n\t}\n\n\tresponse, err := chatModel.Generate(ctx, messages)\n\tif err != nil {\n\t\tlog.Fatalf(\"生成失败: %v\", err)\n\t}\n\n\tfmt.Printf(\"\\\\nAI 回答:\\\\n%s\\\\n\", response.Content)\n}\n"}}, "lab03/generate/multi/multi_chat.go": {"ec86072535f9835cf7765273291366d07bf9a0bc": {"t": 1785745619.2910244, "n": 1168, "h": "7de03ea8bb52b831", "c": "package main\n\nimport (\n\t\"bufio\"\n\t\"context\"\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/cloudwego/eino-ext/components/model/deepseek\"\n\t\"github.com/cloudwego/eino/schema\"\n)\n\nfunc main() {\n\tctx := context.Background()\n\n\t// 创建 ChatModel\n\tchatModel, err := deepseek.NewChatModel(ctx, &deepseek.ChatModelConfig{\n\t\tAPIKey: os.Getenv(\"DEEPSEEK_API_KEY\"),\n\t\tModel: \"deepseek-chat\",\n\t\tBaseURL: \"https://api.deepseek.com\",\n\t})\n\tif err != nil {\n\t\tlog.Fatalf(\"创建失败: %v\", err)\n\t}\n\n\t// 对话历史\n\tmessages := []*schema.Message{\n\t\tschema.SystemMessage(\"你是一个懂得哲学的程序员。\"),\n\t}\n\n\tscanner := bufio.NewScanner(os.Stdin)\n\tfmt.Println(\"开始对话(输入 'exit' 退出):\")\n\n\tfor {\n\t\tfmt.Print(\"\\\\n你: \")\n\t\tif !scanner.Scan() {\n\t\t\tbreak\n\t\t}\n\n\t\tuserInput := strings.TrimSpace(scanner.Text())\n\t\tif userInput == \"exit\" {\n\t\t\tfmt.Println(\"再见!\")\n\t\t\tbreak\n\t\t}\n\n\t\tif userInput == \"\" {\n\t\t\tcontinue\n\t\t}\n\n\t\t// 添加用户消息\n\t\tmessages = append(messages, schema.UserMessage(userInput))\n\n\t\t// 生成 AI 响应\n\t\tresponse, err := chatModel.Generate(ctx, messages)\n\t\tif err != nil {\n\t\t\tlog.Printf(\"生成失败: %v\", err)\n\t\t\tcontinue\n\t\t}\n\n\t\t// 添加 AI 响应到历史\n\t\tmessages = append(messages, response)\n\n\t\tfmt.Printf(\"\\\\nAI: %s\\\\n\", response.Content)\n\t}\n}\n"}, "716e229e9a9261da93e044a1359aac8d5b960320": {"t": 1785754916.845922, "n": 1168, "h": "7de03ea8bb52b831", "c": "package main\n\nimport (\n\t\"bufio\"\n\t\"context\"\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/cloudwego/eino-ext/components/model/deepseek\"\n\t\"github.com/cloudwego/eino/schema\"\n)\n\nfunc main() {\n\tctx := context.Background()\n\n\t// 创建 ChatModel\n\tchatModel, err := deepseek.NewChatModel(ctx, &deepseek.ChatModelConfig{\n\t\tAPIKey: os.Getenv(\"DEEPSEEK_API_KEY\"),\n\t\tModel: \"deepseek-chat\",\n\t\tBaseURL: \"https://api.deepseek.com\",\n\t})\n\tif err != nil {\n\t\tlog.Fatalf(\"创建失败: %v\", err)\n\t}\n\n\t// 对话历史\n\tmessages := []*schema.Message{\n\t\tschema.SystemMessage(\"你是一个懂得哲学的程序员。\"),\n\t}\n\n\tscanner := bufio.NewScanner(os.Stdin)\n\tfmt.Println(\"开始对话(输入 'exit' 退出):\")\n\n\tfor {\n\t\tfmt.Print(\"\\\\n你: \")\n\t\tif !scanner.Scan() {\n\t\t\tbreak\n\t\t}\n\n\t\tuserInput := strings.TrimSpace(scanner.Text())\n\t\tif userInput == \"exit\" {\n\t\t\tfmt.Println(\"再见!\")\n\t\t\tbreak\n\t\t}\n\n\t\tif userInput == \"\" {\n\t\t\tcontinue\n\t\t}\n\n\t\t// 添加用户消息\n\t\tmessages = append(messages, schema.UserMessage(userInput))\n\n\t\t// 生成 AI 响应\n\t\tresponse, err := chatModel.Generate(ctx, messages)\n\t\tif err != nil {\n\t\t\tlog.Printf(\"生成失败: %v\", err)\n\t\t\tcontinue\n\t\t}\n\n\t\t// 添加 AI 响应到历史\n\t\tmessages = append(messages, response)\n\n\t\tfmt.Printf(\"\\\\nAI: %s\\\\n\", response.Content)\n\t}\n}\n"}}, "lab03/generate/single/single_chat.go": {"ec86072535f9835cf7765273291366d07bf9a0bc": {"t": 1785745619.3989744, "n": 730, "h": "fbedd39ab3ac00f3", "c": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\n\t\"github.com/cloudwego/eino-ext/components/model/deepseek\"\n\t\"github.com/cloudwego/eino/schema\"\n)\n\nfunc main() {\n\tctx := context.Background()\n\n\t// 创建 ChatModel\n\tchatModel, err := deepseek.NewChatModel(ctx, &deepseek.ChatModelConfig{\n\t\tAPIKey: os.Getenv(\"DEEPSEEK_API_KEY\"),\n\t\tModel: \"deepseek-chat\",\n\t\tBaseURL: \"https://api.deepseek.com\",\n\t})\n\tif err != nil {\n\t\tlog.Fatalf(\"创建失败: %v\", err)\n\t}\n\n\t// 构建消息\n\tmessages := []*schema.Message{\n\t\tschema.SystemMessage(\"你是一个懂得哲学的程序员。\"),\n\t\tschema.UserMessage(\"什么是存在主义?\"),\n\t}\n\n\t// 生成响应\n\tresponse, err := chatModel.Generate(ctx, messages)\n\tif err != nil {\n\t\tlog.Fatalf(\"生成失败: %v\", err)\n\t}\n\n\tfmt.Printf(\"回答:\\\\n%s\\\\n\", response.Content)\n}\n"}, "716e229e9a9261da93e044a1359aac8d5b960320": {"t": 1785754912.1833582, "n": 730, "h": "fbedd39ab3ac00f3", "c": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\n\t\"github.com/cloudwego/eino-ext/components/model/deepseek\"\n\t\"github.com/cloudwego/eino/schema\"\n)\n\nfunc main() {\n\tctx := context.Background()\n\n\t// 创建 ChatModel\n\tchatModel, err := deepseek.NewChatModel(ctx, &deepseek.ChatModelConfig{\n\t\tAPIKey: os.Getenv(\"DEEPSEEK_API_KEY\"),\n\t\tModel: \"deepseek-chat\",\n\t\tBaseURL: \"https://api.deepseek.com\",\n\t})\n\tif err != nil {\n\t\tlog.Fatalf(\"创建失败: %v\", err)\n\t}\n\n\t// 构建消息\n\tmessages := []*schema.Message{\n\t\tschema.SystemMessage(\"你是一个懂得哲学的程序员。\"),\n\t\tschema.UserMessage(\"什么是存在主义?\"),\n\t}\n\n\t// 生成响应\n\tresponse, err := chatModel.Generate(ctx, messages)\n\tif err != nil {\n\t\tlog.Fatalf(\"生成失败: %v\", err)\n\t}\n\n\tfmt.Printf(\"回答:\\\\n%s\\\\n\", response.Content)\n}\n"}}, "lab02/graph/graph_chat.go": {"ec86072535f9835cf7765273291366d07bf9a0bc": {"t": 1785745619.9222474, "n": 6539, "h": "9b229bc7d158818e", "c": "package main\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"log\"\n\t\"os\"\n\n\t\"github.com/cloudwego/eino-ext/components/model/deepseek\"\n\t\"github.com/cloudwego/eino/components/prompt\"\n\t\"github.com/cloudwego/eino/components/tool\"\n\t\"github.com/cloudwego/eino/components/tool/utils\"\n\t\"github.com/cloudwego/eino/compose\"\n\t\"github.com/cloudwego/eino/schema\"\n)\n\n// 工具入参\ntype playerInfoRequest struct {\n\tEmail string `json:\"email\"`\n\tName string `json:\"name\"`\n}\n\n// 工具出参\ntype playerInfoResponse struct {\n\tName string `json:\"name\"`\n\tEmail string `json:\"email\"`\n\tRole string `json:\"role\"` // 后卫/锋线/中锋/教练/爱好者\n\tHeightCM int `json:\"height_cm\"` // 身高\n\tWeightKG int `json:\"weight_kg\"` // 体重\n\tPlayStyle string `json:\"play_style\"` // 风格\n\tWeeklyHours int `json:\"weekly_hours\"` // 每周训练/打球时长\n}\n\nfunc main() {\n\tctx := context.Background()\n\tg := compose.NewGraph[map[string]any, *schema.Message]()\n\n\t// 1) ChatTemplate 节点(篮球主题)\n\tsystemTpl := `你是一名篮球教练与比赛分析师。你需要结合用户的基本信息与训练习惯,\n使用 player_info API,为其补全信息,然后给出适合他的训练计划、位置建议与一套简单战术建议。\n注意:邮箱必须出现,用于查询信息。`\n\n\tchatTpl := prompt.FromMessages(schema.FString,\n\t\tschema.SystemMessage(systemTpl),\n\t\tschema.MessagesPlaceholder(\"histories\", true),\n\t\tschema.UserMessage(\"{user_query}\"),\n\t)\n\n\t// 2) 推荐模板\n\trecommendTpl := `\n你是一名篮球教练与比赛分析师。请结合工具返回的用户信息,为用户输出建议,要求具体、可执行。\n\n--- 训练资源(可选方案库)---\n\n### A. 训练方向库(按位置/风格)\n**1. 后卫(控运与节奏)**\n- 核心:运球对抗、挡拆阅读、急停跳投、突破分球\n- 训练:左右手变向组合、弱侧手终结、1v1 变速\n\n**2. 锋线(持球终结与防守)**\n- 核心:三威胁、低位脚步、协防轮转、错位单打\n- 训练:三分接投+一运、背身转身、closeout 防守\n\n**3. 内线(篮下统治与护框)**\n- 核心:卡位、顺下吃饼、护框、二次进攻\n- 训练:对抗上篮、掩护质量、篮板站位\n\n### B. 一套简单战术(适合大多数业余队)\n- **高位挡拆(P&R)**:持球人借掩护突破/投篮/分球,弱侧埋伏投手\n- **Spain P&R(简化版)**:挡拆后再给顺下人做背掩护,制造错位/空切\n- **5-out(五外)**:拉开空间,强弱侧转移球,靠突破分球创造空位三分\n\n### C. 输出规则\n1) 先总结用户画像(身高体重、风格、每周训练时长)\n2) 给出建议位置与核心技能树(3-5个技能)\n3) 输出一周训练计划(按天、每次45-90分钟)\n4) 给一套战术建议 + 业余局实战注意事项(3条)\n`\n\n\t// 3) DeepSeek ChatModel(环境变量取 key)\n\tapiKey := os.Getenv(\"DEEPSEEK_API_KEY\")\n\tif apiKey == \"\" {\n\t\tlog.Fatal(\"缺少环境变量 DEEPSEEK_API_KEY\")\n\t}\n\n\tchatModel, err := deepseek.NewChatModel(ctx, &deepseek.ChatModelConfig{\n\t\tAPIKey: os.Getenv(\"DEEPSEEK_API_KEY\"),\n\t\tModel: \"deepseek-chat\",\n\t\tBaseURL: \"https://api.deepseek.com\",\n\t})\n\tif err != nil {\n\t\tlog.Fatalf(\"创建 DeepSeek ChatModel 失败: %v\", err)\n\t}\n\n\t// 4) 工具:player_info(mock 示例)\n\tplayerInfoTool := utils.NewTool(\n\t\t&schema.ToolInfo{\n\t\t\tName: \"player_info\",\n\t\t\tDesc: \"根据用户的姓名和邮箱,查询用户的篮球相关信息(位置倾向、身体数据、打球习惯等)\",\n\t\t\tParamsOneOf: schema.NewParamsOneOfByParams(map[string]*schema.ParameterInfo{\n\t\t\t\t\"name\": {Type: \"string\", Desc: \"用户的姓名\"},\n\t\t\t\t\"email\": {Type: \"string\", Desc: \"用户的邮箱\"},\n\t\t\t}),\n\t\t},\n\t\tfunc(ctx context.Context, input *playerInfoRequest) (*playerInfoResponse, error) {\n\t\t\t// demo:随便 mock 一份\n\t\t\treturn &playerInfoResponse{\n\t\t\t\tName: input.Name,\n\t\t\t\tEmail: input.Email,\n\t\t\t\tRole: \"锋线\",\n\t\t\t\tHeightCM: 182,\n\t\t\t\tWeightKG: 78,\n\t\t\t\tPlayStyle: \"偏投射+无球空切,偶尔持球突破\",\n\t\t\t\tWeeklyHours: 4,\n\t\t\t}, nil\n\t\t},\n\t)\n\n\t// 5) 绑定工具到模型(让模型能产生 tool_calls)\n\tinfo, err := playerInfoTool.Info(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tif err := chatModel.BindTools([]*schema.ToolInfo{info}); err != nil {\n\t\tpanic(err)\n\t}\n\n\t// 6) ToolsNode\n\ttoolsNode, err := compose.NewToolNode(ctx, &compose.ToolsNodeConfig{\n\t\tTools: []tool.BaseTool{playerInfoTool},\n\t})\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\t// 7) Lambda:从 toolsNode 输出 messages 中提取工具结果 -> 转成普通 user 文本\n\t// 这样第二次调用 ChatModel 时不携带 role=tool,避免 400。\n\textractToolOps := func(\n\t\tctx context.Context,\n\t\tinput *schema.StreamReader[[]*schema.Message],\n\t) (*schema.StreamReader[*schema.Message], error) {\n\t\treturn schema.StreamReaderWithConvert(input, func(msgs []*schema.Message) (*schema.Message, error) {\n\t\t\tif len(msgs) == 0 {\n\t\t\t\treturn nil, errors.New(\"no messages from tools node\")\n\t\t\t}\n\n\t\t\t// 这里用一个更“保守”的策略:把 toolsNode 输出的所有 message 内容拼起来,\n\t\t\t// 作为纯文本喂给下一轮(不依赖 Role 字段是否叫 Role)\n\t\t\ttype msgLite struct {\n\t\t\t\tRole string `json:\"role,omitempty\"`\n\t\t\t\tContent string `json:\"content,omitempty\"`\n\t\t\t}\n\t\t\tlites := make([]msgLite, 0, len(msgs))\n\t\t\tfor _, m := range msgs {\n\t\t\t\tif m == nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tlites = append(lites, msgLite{\n\t\t\t\t\tRole: string(m.Role), // schema.Message 的 Role 字段\n\t\t\t\t\tContent: m.Content, // Content 一般都有\n\t\t\t\t})\n\t\t\t}\n\t\t\tb, _ := json.MarshalIndent(lites, \"\", \" \")\n\n\t\t\ttext := \"工具执行完成,返回信息如下(结构化摘要):\\n\" + string(b)\n\t\t\treturn schema.UserMessage(text), nil\n\t\t}), nil\n\t}\n\textractToolLambda := compose.TransformableLambda[[]*schema.Message, *schema.Message](extractToolOps)\n\n\t// 8) Lambda:构造第二次模型输入:system(recommendTpl) + user(工具结果文本)\n\tbuildPromptOps := func(\n\t\tctx context.Context,\n\t\tinput *schema.StreamReader[*schema.Message],\n\t) (*schema.StreamReader[[]*schema.Message], error) {\n\t\treturn schema.StreamReaderWithConvert(input, func(m *schema.Message) ([]*schema.Message, error) {\n\t\t\tif m == nil {\n\t\t\t\treturn nil, errors.New(\"nil message\")\n\t\t\t}\n\t\t\tout := make([]*schema.Message, 0, 2)\n\t\t\tout = append(out, schema.SystemMessage(recommendTpl))\n\t\t\tout = append(out, m)\n\t\t\treturn out, nil\n\t\t}), nil\n\t}\n\tbuildPromptLambda := compose.TransformableLambda[*schema.Message, []*schema.Message](buildPromptOps)\n\n\t// 9) Graph 编排\n\tconst (\n\t\tpromptNodeKey = \"prompt\"\n\t\tchatNodeKey = \"chat\"\n\t\ttoolsNodeKey = \"tools\"\n\t\textractNodeKey = \"extract_tool_result\"\n\t\tlambdaPromptNodeKey = \"build_recommend_prompt\"\n\t\trecommendChatNodeKey = \"chat_recommend\"\n\t)\n\n\t_ = g.AddChatTemplateNode(promptNodeKey, chatTpl)\n\t_ = g.AddChatModelNode(chatNodeKey, chatModel)\n\t_ = g.AddToolsNode(toolsNodeKey, toolsNode)\n\t_ = g.AddLambdaNode(extractNodeKey, extractToolLambda)\n\t_ = g.AddLambdaNode(lambdaPromptNodeKey, buildPromptLambda)\n\t_ = g.AddChatModelNode(recommendChatNodeKey, chatModel)\n\n\t_ = g.AddEdge(compose.START, promptNodeKey)\n\t_ = g.AddEdge(promptNodeKey, chatNodeKey)\n\t_ = g.AddEdge(chatNodeKey, toolsNodeKey)\n\t_ = g.AddEdge(toolsNodeKey, extractNodeKey)\n\t_ = g.AddEdge(extractNodeKey, lambdaPromptNodeKey)\n\t_ = g.AddEdge(lambdaPromptNodeKey, recommendChatNodeKey)\n\t_ = g.AddEdge(recommendChatNodeKey, compose.END)\n\n\t// 10) 编译运行\n\trunnable, err := g.Compile(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\toutput, err := runnable.Invoke(ctx, map[string]any{\n\t\t\"histories\": []*schema.Message{},\n\t\t\"user_query\": \"我叫 morning, 邮箱是 lumworn@gmail.com。我的目标是提升实战表现,帮我制定训练计划并推荐适合的位置和打法。\",\n\t})\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tprintln(\"=====================思考内容====================\")\n\tif output.ReasoningContent != \"\" {\n\t\tprintln(output.ReasoningContent)\n\t}\n\tprintln(\"=========================================\")\n\tif output.Content != \"\" {\n\t\tprintln(output.Content)\n\t}\n}\n"}, "7667a546f07e14eb7d1491c11164ad046bfb7ec8": {"t": 1785754917.3407314, "n": 6539, "h": "9b229bc7d158818e", "c": "package main\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"log\"\n\t\"os\"\n\n\t\"github.com/cloudwego/eino-ext/components/model/deepseek\"\n\t\"github.com/cloudwego/eino/components/prompt\"\n\t\"github.com/cloudwego/eino/components/tool\"\n\t\"github.com/cloudwego/eino/components/tool/utils\"\n\t\"github.com/cloudwego/eino/compose\"\n\t\"github.com/cloudwego/eino/schema\"\n)\n\n// 工具入参\ntype playerInfoRequest struct {\n\tEmail string `json:\"email\"`\n\tName string `json:\"name\"`\n}\n\n// 工具出参\ntype playerInfoResponse struct {\n\tName string `json:\"name\"`\n\tEmail string `json:\"email\"`\n\tRole string `json:\"role\"` // 后卫/锋线/中锋/教练/爱好者\n\tHeightCM int `json:\"height_cm\"` // 身高\n\tWeightKG int `json:\"weight_kg\"` // 体重\n\tPlayStyle string `json:\"play_style\"` // 风格\n\tWeeklyHours int `json:\"weekly_hours\"` // 每周训练/打球时长\n}\n\nfunc main() {\n\tctx := context.Background()\n\tg := compose.NewGraph[map[string]any, *schema.Message]()\n\n\t// 1) ChatTemplate 节点(篮球主题)\n\tsystemTpl := `你是一名篮球教练与比赛分析师。你需要结合用户的基本信息与训练习惯,\n使用 player_info API,为其补全信息,然后给出适合他的训练计划、位置建议与一套简单战术建议。\n注意:邮箱必须出现,用于查询信息。`\n\n\tchatTpl := prompt.FromMessages(schema.FString,\n\t\tschema.SystemMessage(systemTpl),\n\t\tschema.MessagesPlaceholder(\"histories\", true),\n\t\tschema.UserMessage(\"{user_query}\"),\n\t)\n\n\t// 2) 推荐模板\n\trecommendTpl := `\n你是一名篮球教练与比赛分析师。请结合工具返回的用户信息,为用户输出建议,要求具体、可执行。\n\n--- 训练资源(可选方案库)---\n\n### A. 训练方向库(按位置/风格)\n**1. 后卫(控运与节奏)**\n- 核心:运球对抗、挡拆阅读、急停跳投、突破分球\n- 训练:左右手变向组合、弱侧手终结、1v1 变速\n\n**2. 锋线(持球终结与防守)**\n- 核心:三威胁、低位脚步、协防轮转、错位单打\n- 训练:三分接投+一运、背身转身、closeout 防守\n\n**3. 内线(篮下统治与护框)**\n- 核心:卡位、顺下吃饼、护框、二次进攻\n- 训练:对抗上篮、掩护质量、篮板站位\n\n### B. 一套简单战术(适合大多数业余队)\n- **高位挡拆(P&R)**:持球人借掩护突破/投篮/分球,弱侧埋伏投手\n- **Spain P&R(简化版)**:挡拆后再给顺下人做背掩护,制造错位/空切\n- **5-out(五外)**:拉开空间,强弱侧转移球,靠突破分球创造空位三分\n\n### C. 输出规则\n1) 先总结用户画像(身高体重、风格、每周训练时长)\n2) 给出建议位置与核心技能树(3-5个技能)\n3) 输出一周训练计划(按天、每次45-90分钟)\n4) 给一套战术建议 + 业余局实战注意事项(3条)\n`\n\n\t// 3) DeepSeek ChatModel(环境变量取 key)\n\tapiKey := os.Getenv(\"DEEPSEEK_API_KEY\")\n\tif apiKey == \"\" {\n\t\tlog.Fatal(\"缺少环境变量 DEEPSEEK_API_KEY\")\n\t}\n\n\tchatModel, err := deepseek.NewChatModel(ctx, &deepseek.ChatModelConfig{\n\t\tAPIKey: os.Getenv(\"DEEPSEEK_API_KEY\"),\n\t\tModel: \"deepseek-chat\",\n\t\tBaseURL: \"https://api.deepseek.com\",\n\t})\n\tif err != nil {\n\t\tlog.Fatalf(\"创建 DeepSeek ChatModel 失败: %v\", err)\n\t}\n\n\t// 4) 工具:player_info(mock 示例)\n\tplayerInfoTool := utils.NewTool(\n\t\t&schema.ToolInfo{\n\t\t\tName: \"player_info\",\n\t\t\tDesc: \"根据用户的姓名和邮箱,查询用户的篮球相关信息(位置倾向、身体数据、打球习惯等)\",\n\t\t\tParamsOneOf: schema.NewParamsOneOfByParams(map[string]*schema.ParameterInfo{\n\t\t\t\t\"name\": {Type: \"string\", Desc: \"用户的姓名\"},\n\t\t\t\t\"email\": {Type: \"string\", Desc: \"用户的邮箱\"},\n\t\t\t}),\n\t\t},\n\t\tfunc(ctx context.Context, input *playerInfoRequest) (*playerInfoResponse, error) {\n\t\t\t// demo:随便 mock 一份\n\t\t\treturn &playerInfoResponse{\n\t\t\t\tName: input.Name,\n\t\t\t\tEmail: input.Email,\n\t\t\t\tRole: \"锋线\",\n\t\t\t\tHeightCM: 182,\n\t\t\t\tWeightKG: 78,\n\t\t\t\tPlayStyle: \"偏投射+无球空切,偶尔持球突破\",\n\t\t\t\tWeeklyHours: 4,\n\t\t\t}, nil\n\t\t},\n\t)\n\n\t// 5) 绑定工具到模型(让模型能产生 tool_calls)\n\tinfo, err := playerInfoTool.Info(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tif err := chatModel.BindTools([]*schema.ToolInfo{info}); err != nil {\n\t\tpanic(err)\n\t}\n\n\t// 6) ToolsNode\n\ttoolsNode, err := compose.NewToolNode(ctx, &compose.ToolsNodeConfig{\n\t\tTools: []tool.BaseTool{playerInfoTool},\n\t})\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\t// 7) Lambda:从 toolsNode 输出 messages 中提取工具结果 -> 转成普通 user 文本\n\t// 这样第二次调用 ChatModel 时不携带 role=tool,避免 400。\n\textractToolOps := func(\n\t\tctx context.Context,\n\t\tinput *schema.StreamReader[[]*schema.Message],\n\t) (*schema.StreamReader[*schema.Message], error) {\n\t\treturn schema.StreamReaderWithConvert(input, func(msgs []*schema.Message) (*schema.Message, error) {\n\t\t\tif len(msgs) == 0 {\n\t\t\t\treturn nil, errors.New(\"no messages from tools node\")\n\t\t\t}\n\n\t\t\t// 这里用一个更“保守”的策略:把 toolsNode 输出的所有 message 内容拼起来,\n\t\t\t// 作为纯文本喂给下一轮(不依赖 Role 字段是否叫 Role)\n\t\t\ttype msgLite struct {\n\t\t\t\tRole string `json:\"role,omitempty\"`\n\t\t\t\tContent string `json:\"content,omitempty\"`\n\t\t\t}\n\t\t\tlites := make([]msgLite, 0, len(msgs))\n\t\t\tfor _, m := range msgs {\n\t\t\t\tif m == nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tlites = append(lites, msgLite{\n\t\t\t\t\tRole: string(m.Role), // schema.Message 的 Role 字段\n\t\t\t\t\tContent: m.Content, // Content 一般都有\n\t\t\t\t})\n\t\t\t}\n\t\t\tb, _ := json.MarshalIndent(lites, \"\", \" \")\n\n\t\t\ttext := \"工具执行完成,返回信息如下(结构化摘要):\\n\" + string(b)\n\t\t\treturn schema.UserMessage(text), nil\n\t\t}), nil\n\t}\n\textractToolLambda := compose.TransformableLambda[[]*schema.Message, *schema.Message](extractToolOps)\n\n\t// 8) Lambda:构造第二次模型输入:system(recommendTpl) + user(工具结果文本)\n\tbuildPromptOps := func(\n\t\tctx context.Context,\n\t\tinput *schema.StreamReader[*schema.Message],\n\t) (*schema.StreamReader[[]*schema.Message], error) {\n\t\treturn schema.StreamReaderWithConvert(input, func(m *schema.Message) ([]*schema.Message, error) {\n\t\t\tif m == nil {\n\t\t\t\treturn nil, errors.New(\"nil message\")\n\t\t\t}\n\t\t\tout := make([]*schema.Message, 0, 2)\n\t\t\tout = append(out, schema.SystemMessage(recommendTpl))\n\t\t\tout = append(out, m)\n\t\t\treturn out, nil\n\t\t}), nil\n\t}\n\tbuildPromptLambda := compose.TransformableLambda[*schema.Message, []*schema.Message](buildPromptOps)\n\n\t// 9) Graph 编排\n\tconst (\n\t\tpromptNodeKey = \"prompt\"\n\t\tchatNodeKey = \"chat\"\n\t\ttoolsNodeKey = \"tools\"\n\t\textractNodeKey = \"extract_tool_result\"\n\t\tlambdaPromptNodeKey = \"build_recommend_prompt\"\n\t\trecommendChatNodeKey = \"chat_recommend\"\n\t)\n\n\t_ = g.AddChatTemplateNode(promptNodeKey, chatTpl)\n\t_ = g.AddChatModelNode(chatNodeKey, chatModel)\n\t_ = g.AddToolsNode(toolsNodeKey, toolsNode)\n\t_ = g.AddLambdaNode(extractNodeKey, extractToolLambda)\n\t_ = g.AddLambdaNode(lambdaPromptNodeKey, buildPromptLambda)\n\t_ = g.AddChatModelNode(recommendChatNodeKey, chatModel)\n\n\t_ = g.AddEdge(compose.START, promptNodeKey)\n\t_ = g.AddEdge(promptNodeKey, chatNodeKey)\n\t_ = g.AddEdge(chatNodeKey, toolsNodeKey)\n\t_ = g.AddEdge(toolsNodeKey, extractNodeKey)\n\t_ = g.AddEdge(extractNodeKey, lambdaPromptNodeKey)\n\t_ = g.AddEdge(lambdaPromptNodeKey, recommendChatNodeKey)\n\t_ = g.AddEdge(recommendChatNodeKey, compose.END)\n\n\t// 10) 编译运行\n\trunnable, err := g.Compile(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\toutput, err := runnable.Invoke(ctx, map[string]any{\n\t\t\"histories\": []*schema.Message{},\n\t\t\"user_query\": \"我叫 morning, 邮箱是 lumworn@gmail.com。我的目标是提升实战表现,帮我制定训练计划并推荐适合的位置和打法。\",\n\t})\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tprintln(\"=====================思考内容====================\")\n\tif output.ReasoningContent != \"\" {\n\t\tprintln(output.ReasoningContent)\n\t}\n\tprintln(\"=========================================\")\n\tif output.Content != \"\" {\n\t\tprintln(output.Content)\n\t}\n}\n"}}, "lab02/chain/chain_chat.go": {"ec86072535f9835cf7765273291366d07bf9a0bc": {"t": 1785745619.9265215, "n": 6326, "h": "061595c85c69e88a", "c": "package main\n\nimport (\n\t\"context\"\n\t\"log\"\n\t\"os\"\n\n\t\"github.com/cloudwego/eino-ext/components/model/deepseek\"\n\t\"github.com/cloudwego/eino/components/prompt\"\n\t\"github.com/cloudwego/eino/components/tool\"\n\t\"github.com/cloudwego/eino/components/tool/utils\"\n\t\"github.com/cloudwego/eino/compose\"\n\t\"github.com/cloudwego/eino/schema\"\n)\n\n// 工具入参\ntype playerInfoRequest struct {\n\tName string `json:\"name\"`\n\tEmail string `json:\"email\"`\n}\n\n// 工具出参\ntype playerInfoResponse struct {\n\tName string `json:\"name\"`\n\tEmail string `json:\"email\"`\n\tRole string `json:\"role\"` // 后卫/锋线/中锋/教练/爱好者\n\tHeightCM int `json:\"height_cm\"` // 身高\n\tWeightKG int `json:\"weight_kg\"` // 体重\n\tPlayStyle string `json:\"play_style\"` // 风格\n\tWeeklyHours int `json:\"weekly_hours\"` // 每周训练/打球时长\n}\n\nfunc main() {\n\tctx := context.Background()\n\n\t// 1) 篮球主题:ChatTemplate\n\tsystemTpl := `你是一名篮球教练与比赛分析师。你需要结合用户的基本信息与训练习惯,\n使用 player_info 工具补全信息,然后给出适合他的训练计划/位置建议/一套简单战术建议。\n注意:邮箱必须出现,用于查询信息。`\n\n\tchatTpl := prompt.FromMessages(schema.FString,\n\t\tschema.SystemMessage(systemTpl),\n\t\tschema.MessagesPlaceholder(\"histories\", true),\n\t\tschema.UserMessage(\"{user_query}\"),\n\t)\n\n\t// 2) 推荐模板(工具回来的信息 + 固定“篮球知识库/规则”)\n\trecommendTpl := `\n你是一名篮球教练与比赛分析师。请结合工具返回的用户信息,为用户输出建议,要求具体、可执行。\n\n--- 训练资源(可选方案库)---\n\n### A. 训练方向库(按位置/风格)\n**1. 后卫(控运与节奏)**\n- 核心:运球对抗、挡拆阅读、急停跳投、突破分球\n- 训练:左右手变向组合、弱侧手终结、1v1 变速\n\n**2. 锋线(持球终结与防守)**\n- 核心:三威胁、低位脚步、协防轮转、错位单打\n- 训练:三分接投+一运、背身转身、closeout 防守\n\n**3. 内线(篮下统治与护框)**\n- 核心:卡位、顺下吃饼、护框、二次进攻\n- 训练:对抗上篮、掩护质量、篮板站位\n\n### B. 一套简单战术(适合大多数业余队)\n- **高位挡拆(P&R)**:持球人借掩护突破/投篮/分球,弱侧埋伏投手\n- **Spain P&R(简化版)**:挡拆后再给顺下人做背掩护,制造错位/空切\n- **5-out(五外)**:拉开空间,强弱侧转移球,靠突破分球创造空位三分\n\n### C. 输出规则\n1) 先总结用户画像(身高体重、风格、每周训练时长)\n2) 给出建议位置与核心技能树(3-5个技能)\n3) 输出一周训练计划(按天、每次45-90分钟)\n4) 给一套战术建议 + 业余局实战注意事项(3条)\n\n`\n\n\t// 3) 创建 DeepSeek ChatModel(从环境变量取 key)\n\tapiKey := os.Getenv(\"DEEPSEEK_API_KEY\")\n\tif apiKey == \"\" {\n\t\tlog.Fatal(\"缺少环境变量 DEEPSEEK_API_KEY\")\n\t}\n\n\tchatModel, err := deepseek.NewChatModel(ctx, &deepseek.ChatModelConfig{\n\t\tAPIKey: os.Getenv(\"DEEPSEEK_API_KEY\"), // 获取环境变量中的 APIKey 配置\n\t\tModel: \"deepseek-chat\", // 指定使用的模型名称\n\t\tBaseURL: \"https://api.deepseek.com\", // 自选的 API 服务器地址\n\t})\n\tif err != nil {\n\t\tlog.Fatalf(\"创建 DeepSeek ChatModel 失败: %v\", err)\n\t}\n\n\t// 4) 创建工具:player_info(示例 mock:实际接你们的用户系统即可)\n\tplayerInfoTool := utils.NewTool(\n\t\t&schema.ToolInfo{\n\t\t\tName: \"player_info\",\n\t\t\tDesc: \"根据用户的姓名和邮箱,查询用户的篮球相关信息(位置倾向、身体数据、打球习惯等)\",\n\t\t\tParamsOneOf: schema.NewParamsOneOfByParams(map[string]*schema.ParameterInfo{\n\t\t\t\t\"name\": {\n\t\t\t\t\tType: \"string\",\n\t\t\t\t\tDesc: \"用户姓名\",\n\t\t\t\t},\n\t\t\t\t\"email\": {\n\t\t\t\t\tType: \"string\",\n\t\t\t\t\tDesc: \"用户邮箱\",\n\t\t\t\t},\n\t\t\t}),\n\t\t},\n\t\t// mock 实现方式\n\t\tfunc(ctx context.Context, input *playerInfoRequest) (output *playerInfoResponse, err error) {\n\t\t\t// demo:按邮箱域名/名字随便 mock,一般你会改成查数据库/服务\n\t\t\treturn &playerInfoResponse{\n\t\t\t\tName: input.Name,\n\t\t\t\tEmail: input.Email,\n\t\t\t\tRole: \"锋线\",\n\t\t\t\tHeightCM: 182,\n\t\t\t\tWeightKG: 78,\n\t\t\t\tPlayStyle: \"偏投射+无球空切,偶尔持球突破\",\n\t\t\t\tWeeklyHours: 4,\n\t\t\t}, nil\n\t\t},\n\t)\n\n\t// 5) 绑定工具到模型(让模型能发 tool_calls)\n\tinfo, err := playerInfoTool.Info(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tif err := chatModel.BindTools([]*schema.ToolInfo{info}); err != nil {\n\t\tpanic(err)\n\t}\n\n\t// 6) ToolsNode\n\ttoolsNode, err := compose.NewToolNode(ctx, &compose.ToolsNodeConfig{\n\t\tTools: []tool.BaseTool{playerInfoTool},\n\t})\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\t\n\t// 7) lambda:从 toolsNode 输出中提取“工具返回内容”,转成普通 user 文本(避免 role=tool)\n\ttoolToTextOps := func(\n\t\tctx context.Context,\n\t\tinput *schema.StreamReader[[]*schema.Message],\n\t) (output *schema.StreamReader[*schema.Message], err error) {\n\t\treturn schema.StreamReaderWithConvert(input, func(msgs []*schema.Message) (*schema.Message, error) {\n\t\t\t// 从 toolsNode 输出里找 role=tool 的消息,把它的 Content 摘出来\n\t\t\t// 注意:不同版本 schema.Message 里 role 字段名可能不同(Role/MessageType),下面按常见 Role 写\n\t\t\tvar toolContents []string\n\t\t\tfor _, m := range msgs {\n\t\t\t\tif m == nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif m.Role == \"tool\" { // <- 如果你编译报错,说明字段名不对,见下方“字段不一致怎么办”\n\t\t\t\t\ttoolContents = append(toolContents, m.Content)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// 没找到 tool 内容也没关系,给一个兜底文本,避免空\n\t\t\ttext := \"工具未返回有效信息。\"\n\t\t\tif len(toolContents) > 0 {\n\t\t\t\t// 多个工具结果就拼起来\n\t\t\t\ttext = \"工具返回的用户信息如下:\\n\" + \"- \" + toolContents[0]\n\t\t\t\tfor i := 1; i < len(toolContents); i++ {\n\t\t\t\t\ttext += \"\\n- \" + toolContents[i]\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// 这里返回一个“普通 user 消息”,后面再拼 recommendTpl\n\t\t\treturn schema.UserMessage(text), nil\n\t\t}), nil\n\t}\n\tlambdaToolToText := compose.TransformableLambda[[]*schema.Message, *schema.Message](toolToTextOps)\n\n\t// 8) lambda:构造第二次模型的输入:system(recommendTpl) + user(工具结果文本)\n\tpromptTransformOps := func(\n\t\tctx context.Context,\n\t\tinput *schema.StreamReader[*schema.Message],\n\t) (output *schema.StreamReader[[]*schema.Message], err error) {\n\t\treturn schema.StreamReaderWithConvert(input, func(m *schema.Message) ([]*schema.Message, error) {\n\t\t\tout := make([]*schema.Message, 0, 2)\n\t\t\tout = append(out, schema.SystemMessage(recommendTpl))\n\t\t\tout = append(out, m)\n\t\t\treturn out, nil\n\t\t}), nil\n\t}\n\tlambdaPrompt := compose.TransformableLambda[*schema.Message, []*schema.Message](promptTransformOps)\n\n\t// 9) Chain 编排:template -> chat -> tools -> lambda -> lambdaPrompt -> chat\n\tchain := compose.NewChain[map[string]any, *schema.Message]()\n\tchain.\n\t\tAppendChatTemplate(chatTpl).\n\t\tAppendChatModel(chatModel).\n\t\tAppendToolsNode(toolsNode).\n\t\tAppendLambda(lambdaToolToText).\n\t\tAppendLambda(lambdaPrompt).\n\t\tAppendChatModel(chatModel)\n\n\t// 10) 编译运行\n\trunnable, err := chain.Compile(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\toutput, err := runnable.Invoke(ctx, map[string]any{\n\t\t\"histories\": []*schema.Message{},\n\t\t\"user_query\": \"我叫 morning, 邮箱是 lumworn@gmail.com。我的目标是提升实战表现,帮我制定训练计划并推荐适合的位置和打法。\",\n\t})\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tprintln(\"=====================思考内容====================\")\n\tif output.ReasoningContent != \"\" {\n\t\tprintln(output.ReasoningContent)\n\t}\n\tprintln(\"=========================================\")\n\tif output.Content != \"\" {\n\t\tprintln(output.Content)\n\t}\n\n\t// 可选:token 用量(如果 deepseek 的 response meta 有 usage)\n\tif output.ResponseMeta != nil && output.ResponseMeta.Usage != nil {\n\t\tprintln(\"\\nToken 使用统计:\")\n\t\tprintln(\" 输入 Token:\", int(output.ResponseMeta.Usage.PromptTokens))\n\t\tprintln(\" 输出 Token:\", int(output.ResponseMeta.Usage.CompletionTokens))\n\t\tprintln(\" 总计 Token:\", int(output.ResponseMeta.Usage.TotalTokens))\n\t}\n}\n"}, "713a7a4e11dae1c194364b7583723f470cd10feb": {"t": 1785754913.4302676, "n": 6326, "h": "061595c85c69e88a", "c": "package main\n\nimport (\n\t\"context\"\n\t\"log\"\n\t\"os\"\n\n\t\"github.com/cloudwego/eino-ext/components/model/deepseek\"\n\t\"github.com/cloudwego/eino/components/prompt\"\n\t\"github.com/cloudwego/eino/components/tool\"\n\t\"github.com/cloudwego/eino/components/tool/utils\"\n\t\"github.com/cloudwego/eino/compose\"\n\t\"github.com/cloudwego/eino/schema\"\n)\n\n// 工具入参\ntype playerInfoRequest struct {\n\tName string `json:\"name\"`\n\tEmail string `json:\"email\"`\n}\n\n// 工具出参\ntype playerInfoResponse struct {\n\tName string `json:\"name\"`\n\tEmail string `json:\"email\"`\n\tRole string `json:\"role\"` // 后卫/锋线/中锋/教练/爱好者\n\tHeightCM int `json:\"height_cm\"` // 身高\n\tWeightKG int `json:\"weight_kg\"` // 体重\n\tPlayStyle string `json:\"play_style\"` // 风格\n\tWeeklyHours int `json:\"weekly_hours\"` // 每周训练/打球时长\n}\n\nfunc main() {\n\tctx := context.Background()\n\n\t// 1) 篮球主题:ChatTemplate\n\tsystemTpl := `你是一名篮球教练与比赛分析师。你需要结合用户的基本信息与训练习惯,\n使用 player_info 工具补全信息,然后给出适合他的训练计划/位置建议/一套简单战术建议。\n注意:邮箱必须出现,用于查询信息。`\n\n\tchatTpl := prompt.FromMessages(schema.FString,\n\t\tschema.SystemMessage(systemTpl),\n\t\tschema.MessagesPlaceholder(\"histories\", true),\n\t\tschema.UserMessage(\"{user_query}\"),\n\t)\n\n\t// 2) 推荐模板(工具回来的信息 + 固定“篮球知识库/规则”)\n\trecommendTpl := `\n你是一名篮球教练与比赛分析师。请结合工具返回的用户信息,为用户输出建议,要求具体、可执行。\n\n--- 训练资源(可选方案库)---\n\n### A. 训练方向库(按位置/风格)\n**1. 后卫(控运与节奏)**\n- 核心:运球对抗、挡拆阅读、急停跳投、突破分球\n- 训练:左右手变向组合、弱侧手终结、1v1 变速\n\n**2. 锋线(持球终结与防守)**\n- 核心:三威胁、低位脚步、协防轮转、错位单打\n- 训练:三分接投+一运、背身转身、closeout 防守\n\n**3. 内线(篮下统治与护框)**\n- 核心:卡位、顺下吃饼、护框、二次进攻\n- 训练:对抗上篮、掩护质量、篮板站位\n\n### B. 一套简单战术(适合大多数业余队)\n- **高位挡拆(P&R)**:持球人借掩护突破/投篮/分球,弱侧埋伏投手\n- **Spain P&R(简化版)**:挡拆后再给顺下人做背掩护,制造错位/空切\n- **5-out(五外)**:拉开空间,强弱侧转移球,靠突破分球创造空位三分\n\n### C. 输出规则\n1) 先总结用户画像(身高体重、风格、每周训练时长)\n2) 给出建议位置与核心技能树(3-5个技能)\n3) 输出一周训练计划(按天、每次45-90分钟)\n4) 给一套战术建议 + 业余局实战注意事项(3条)\n\n`\n\n\t// 3) 创建 DeepSeek ChatModel(从环境变量取 key)\n\tapiKey := os.Getenv(\"DEEPSEEK_API_KEY\")\n\tif apiKey == \"\" {\n\t\tlog.Fatal(\"缺少环境变量 DEEPSEEK_API_KEY\")\n\t}\n\n\tchatModel, err := deepseek.NewChatModel(ctx, &deepseek.ChatModelConfig{\n\t\tAPIKey: os.Getenv(\"DEEPSEEK_API_KEY\"), // 获取环境变量中的 APIKey 配置\n\t\tModel: \"deepseek-chat\", // 指定使用的模型名称\n\t\tBaseURL: \"https://api.deepseek.com\", // 自选的 API 服务器地址\n\t})\n\tif err != nil {\n\t\tlog.Fatalf(\"创建 DeepSeek ChatModel 失败: %v\", err)\n\t}\n\n\t// 4) 创建工具:player_info(示例 mock:实际接你们的用户系统即可)\n\tplayerInfoTool := utils.NewTool(\n\t\t&schema.ToolInfo{\n\t\t\tName: \"player_info\",\n\t\t\tDesc: \"根据用户的姓名和邮箱,查询用户的篮球相关信息(位置倾向、身体数据、打球习惯等)\",\n\t\t\tParamsOneOf: schema.NewParamsOneOfByParams(map[string]*schema.ParameterInfo{\n\t\t\t\t\"name\": {\n\t\t\t\t\tType: \"string\",\n\t\t\t\t\tDesc: \"用户姓名\",\n\t\t\t\t},\n\t\t\t\t\"email\": {\n\t\t\t\t\tType: \"string\",\n\t\t\t\t\tDesc: \"用户邮箱\",\n\t\t\t\t},\n\t\t\t}),\n\t\t},\n\t\t// mock 实现方式\n\t\tfunc(ctx context.Context, input *playerInfoRequest) (output *playerInfoResponse, err error) {\n\t\t\t// demo:按邮箱域名/名字随便 mock,一般你会改成查数据库/服务\n\t\t\treturn &playerInfoResponse{\n\t\t\t\tName: input.Name,\n\t\t\t\tEmail: input.Email,\n\t\t\t\tRole: \"锋线\",\n\t\t\t\tHeightCM: 182,\n\t\t\t\tWeightKG: 78,\n\t\t\t\tPlayStyle: \"偏投射+无球空切,偶尔持球突破\",\n\t\t\t\tWeeklyHours: 4,\n\t\t\t}, nil\n\t\t},\n\t)\n\n\t// 5) 绑定工具到模型(让模型能发 tool_calls)\n\tinfo, err := playerInfoTool.Info(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tif err := chatModel.BindTools([]*schema.ToolInfo{info}); err != nil {\n\t\tpanic(err)\n\t}\n\n\t// 6) ToolsNode\n\ttoolsNode, err := compose.NewToolNode(ctx, &compose.ToolsNodeConfig{\n\t\tTools: []tool.BaseTool{playerInfoTool},\n\t})\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\t\n\t// 7) lambda:从 toolsNode 输出中提取“工具返回内容”,转成普通 user 文本(避免 role=tool)\n\ttoolToTextOps := func(\n\t\tctx context.Context,\n\t\tinput *schema.StreamReader[[]*schema.Message],\n\t) (output *schema.StreamReader[*schema.Message], err error) {\n\t\treturn schema.StreamReaderWithConvert(input, func(msgs []*schema.Message) (*schema.Message, error) {\n\t\t\t// 从 toolsNode 输出里找 role=tool 的消息,把它的 Content 摘出来\n\t\t\t// 注意:不同版本 schema.Message 里 role 字段名可能不同(Role/MessageType),下面按常见 Role 写\n\t\t\tvar toolContents []string\n\t\t\tfor _, m := range msgs {\n\t\t\t\tif m == nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif m.Role == \"tool\" { // <- 如果你编译报错,说明字段名不对,见下方“字段不一致怎么办”\n\t\t\t\t\ttoolContents = append(toolContents, m.Content)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// 没找到 tool 内容也没关系,给一个兜底文本,避免空\n\t\t\ttext := \"工具未返回有效信息。\"\n\t\t\tif len(toolContents) > 0 {\n\t\t\t\t// 多个工具结果就拼起来\n\t\t\t\ttext = \"工具返回的用户信息如下:\\n\" + \"- \" + toolContents[0]\n\t\t\t\tfor i := 1; i < len(toolContents); i++ {\n\t\t\t\t\ttext += \"\\n- \" + toolContents[i]\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// 这里返回一个“普通 user 消息”,后面再拼 recommendTpl\n\t\t\treturn schema.UserMessage(text), nil\n\t\t}), nil\n\t}\n\tlambdaToolToText := compose.TransformableLambda[[]*schema.Message, *schema.Message](toolToTextOps)\n\n\t// 8) lambda:构造第二次模型的输入:system(recommendTpl) + user(工具结果文本)\n\tpromptTransformOps := func(\n\t\tctx context.Context,\n\t\tinput *schema.StreamReader[*schema.Message],\n\t) (output *schema.StreamReader[[]*schema.Message], err error) {\n\t\treturn schema.StreamReaderWithConvert(input, func(m *schema.Message) ([]*schema.Message, error) {\n\t\t\tout := make([]*schema.Message, 0, 2)\n\t\t\tout = append(out, schema.SystemMessage(recommendTpl))\n\t\t\tout = append(out, m)\n\t\t\treturn out, nil\n\t\t}), nil\n\t}\n\tlambdaPrompt := compose.TransformableLambda[*schema.Message, []*schema.Message](promptTransformOps)\n\n\t// 9) Chain 编排:template -> chat -> tools -> lambda -> lambdaPrompt -> chat\n\tchain := compose.NewChain[map[string]any, *schema.Message]()\n\tchain.\n\t\tAppendChatTemplate(chatTpl).\n\t\tAppendChatModel(chatModel).\n\t\tAppendToolsNode(toolsNode).\n\t\tAppendLambda(lambdaToolToText).\n\t\tAppendLambda(lambdaPrompt).\n\t\tAppendChatModel(chatModel)\n\n\t// 10) 编译运行\n\trunnable, err := chain.Compile(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\toutput, err := runnable.Invoke(ctx, map[string]any{\n\t\t\"histories\": []*schema.Message{},\n\t\t\"user_query\": \"我叫 morning, 邮箱是 lumworn@gmail.com。我的目标是提升实战表现,帮我制定训练计划并推荐适合的位置和打法。\",\n\t})\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tprintln(\"=====================思考内容====================\")\n\tif output.ReasoningContent != \"\" {\n\t\tprintln(output.ReasoningContent)\n\t}\n\tprintln(\"=========================================\")\n\tif output.Content != \"\" {\n\t\tprintln(output.Content)\n\t}\n\n\t// 可选:token 用量(如果 deepseek 的 response meta 有 usage)\n\tif output.ResponseMeta != nil && output.ResponseMeta.Usage != nil {\n\t\tprintln(\"\\nToken 使用统计:\")\n\t\tprintln(\" 输入 Token:\", int(output.ResponseMeta.Usage.PromptTokens))\n\t\tprintln(\" 输出 Token:\", int(output.ResponseMeta.Usage.CompletionTokens))\n\t\tprintln(\" 总计 Token:\", int(output.ResponseMeta.Usage.TotalTokens))\n\t}\n}\n"}}, "lab02/workflow/workflow_chat.go": {"ec86072535f9835cf7765273291366d07bf9a0bc": {"t": 1785745620.3411632, "n": 6038, "h": "1c1c4d2a86deca27", "c": "package main\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"log\"\n\t\"os\"\n\n\t\"github.com/cloudwego/eino-ext/components/model/deepseek\"\n\t\"github.com/cloudwego/eino/components/prompt\"\n\t\"github.com/cloudwego/eino/components/tool\"\n\t\"github.com/cloudwego/eino/components/tool/utils\"\n\t\"github.com/cloudwego/eino/compose\"\n\t\"github.com/cloudwego/eino/schema\"\n)\n\n// 工具入参\ntype playerInfoRequest struct {\n\tEmail string `json:\"email\"`\n\tName string `json:\"name\"`\n}\n\n// 工具出参\ntype playerInfoResponse struct {\n\tName string `json:\"name\"`\n\tEmail string `json:\"email\"`\n\tRole string `json:\"role\"` // 后卫/锋线/中锋/教练/爱好者\n\tHeightCM int `json:\"height_cm\"` // 身高\n\tWeightKG int `json:\"weight_kg\"` // 体重\n\tPlayStyle string `json:\"play_style\"` // 风格\n\tWeeklyHours int `json:\"weekly_hours\"` // 每周训练/打球时长\n}\n\nfunc main() {\n\tctx := context.Background()\n\n\t// 创建 Workflow 编排\n\twf := compose.NewWorkflow[map[string]any, *schema.Message]()\n\n\t// 1) 系统提示词模板(篮球主题)\n\tsystemTpl := `你是一名篮球教练与比赛分析师。你需要结合用户的基本信息与训练习惯,\n使用 player_info API 补全用户画像,然后给出:位置建议、核心技能树、一周训练计划、以及一套简单战术建议。\n注意:邮箱必须出现,用于查询信息。`\n\n\tchatTpl := prompt.FromMessages(schema.FString,\n\t\tschema.SystemMessage(systemTpl),\n\t\tschema.MessagesPlaceholder(\"histories\", true),\n\t\tschema.UserMessage(\"{user_query}\"),\n\t)\n\n\t// 2) 推荐模板\n\trecommendTpl := `\n你是一名篮球教练与比赛分析师。请结合“工具返回的用户信息”,为用户输出建议,要求具体、可执行。\n\n--- 训练资源(可选方案库)---\n\n### A. 训练方向库(按位置/风格)\n**1. 后卫(控运与节奏)**\n- 核心:运球对抗、挡拆阅读、急停跳投、突破分球\n- 训练:左右手变向组合、弱侧手终结、1v1 变速\n\n**2. 锋线(持球终结与防守)**\n- 核心:三威胁、低位脚步、协防轮转、错位单打\n- 训练:三分接投+一运、背身转身、closeout 防守\n\n**3. 内线(篮下统治与护框)**\n- 核心:卡位、顺下吃饼、护框、二次进攻\n- 训练:对抗上篮、掩护质量、篮板站位\n\n### B. 一套简单战术(适合大多数业余队)\n- **高位挡拆(P&R)**:持球人借掩护突破/投篮/分球,弱侧埋伏投手\n- **Spain P&R(简化版)**:挡拆后再给顺下人做背掩护,制造错位/空切\n- **5-out(五外)**:拉开空间,强弱侧转移球,靠突破分球创造空位三分\n\n### C. 输出格式要求\n1) 先总结用户画像(身高体重、风格、每周训练时长)\n2) 给出建议位置与核心技能树(3-5个技能)\n3) 输出一周训练计划(按天、每次45-90分钟)\n4) 给一套战术建议 + 业余局实战注意事项(3条)\n`\n\n\t// 3) DeepSeek ChatModel(环境变量取 key)\n\tapiKey := os.Getenv(\"DEEPSEEK_API_KEY\")\n\tif apiKey == \"\" {\n\t\tlog.Fatal(\"缺少环境变量 DEEPSEEK_API_KEY\")\n\t}\n\n\tchatModel, err := deepseek.NewChatModel(ctx, &deepseek.ChatModelConfig{\n\t\tAPIKey: os.Getenv(\"DEEPSEEK_API_KEY\"),\n\t\tModel: \"deepseek-chat\",\n\t\tBaseURL: \"https://api.deepseek.com\",\n\t})\n\tif err != nil {\n\t\tlog.Fatalf(\"创建 DeepSeek ChatModel 失败: %v\", err)\n\t}\n\n\t// 4) 创建工具:player_info(mock 示例)\n\tplayerInfoTool := utils.NewTool(\n\t\t&schema.ToolInfo{\n\t\t\tName: \"player_info\",\n\t\t\tDesc: \"根据用户的姓名和邮箱,查询用户的篮球相关信息(位置倾向、身体数据、打球习惯等)\",\n\t\t\tParamsOneOf: schema.NewParamsOneOfByParams(map[string]*schema.ParameterInfo{\n\t\t\t\t\"name\": {Type: \"string\", Desc: \"用户的姓名\"},\n\t\t\t\t\"email\": {Type: \"string\", Desc: \"用户的邮箱\"},\n\t\t\t}),\n\t\t},\n\t\tfunc(ctx context.Context, input *playerInfoRequest) (*playerInfoResponse, error) {\n\t\t\t// demo:你可以替换成真实查询\n\t\t\treturn &playerInfoResponse{\n\t\t\t\tName: input.Name,\n\t\t\t\tEmail: input.Email,\n\t\t\t\tRole: \"锋线\",\n\t\t\t\tHeightCM: 182,\n\t\t\t\tWeightKG: 78,\n\t\t\t\tPlayStyle: \"偏投射+无球空切,偶尔持球突破\",\n\t\t\t\tWeeklyHours: 4,\n\t\t\t}, nil\n\t\t},\n\t)\n\n\t// 5) 绑定工具到模型(让模型能发 tool_calls)\n\tinfo, err := playerInfoTool.Info(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tif err := chatModel.BindTools([]*schema.ToolInfo{info}); err != nil {\n\t\tpanic(err)\n\t}\n\n\t// 6) 工具节点\n\ttoolsNode, err := compose.NewToolNode(ctx, &compose.ToolsNodeConfig{\n\t\tTools: []tool.BaseTool{playerInfoTool},\n\t})\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\t// 7) 转换:把 toolsNode 输出的 []*schema.Message -> 提炼成一个普通 user message(避免 role=tool 进第二次模型)\n\ttoolToTextOps := func(\n\t\tctx context.Context,\n\t\tinput *schema.StreamReader[[]*schema.Message],\n\t) (*schema.StreamReader[*schema.Message], error) {\n\t\treturn schema.StreamReaderWithConvert(input, func(msgs []*schema.Message) (*schema.Message, error) {\n\t\t\tif len(msgs) == 0 {\n\t\t\t\treturn nil, errors.New(\"no message\")\n\t\t\t}\n\n\t\t\t// 不依赖 Role 字段:把内容统一汇总成 JSON 文本\n\t\t\ttype lite struct {\n\t\t\t\tContent string `json:\"content,omitempty\"`\n\t\t\t\t// 如果你确认 schema.Message 有 Role 字段,可加上 Role: m.Role\n\t\t\t}\n\t\t\tlites := make([]lite, 0, len(msgs))\n\t\t\tfor _, m := range msgs {\n\t\t\t\tif m == nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tlites = append(lites, lite{Content: m.Content})\n\t\t\t}\n\n\t\t\tb, _ := json.MarshalIndent(lites, \"\", \" \")\n\t\t\ttext := \"工具返回的用户信息(汇总):\\n\" + string(b)\n\n\t\t\t// 关键:返回普通 user 消息,而不是 tool 消息\n\t\t\treturn schema.UserMessage(text), nil\n\t\t}), nil\n\t}\n\tlambdaToolToText := compose.TransformableLambda[[]*schema.Message, *schema.Message](toolToTextOps)\n\n\t// 8) 再转换:把 *schema.Message -> []*schema.Message(system recommendTpl + user(工具结果文本))\n\tpromptTransformOps := func(\n\t\tctx context.Context,\n\t\tinput *schema.StreamReader[*schema.Message],\n\t) (*schema.StreamReader[[]*schema.Message], error) {\n\t\treturn schema.StreamReaderWithConvert(input, func(m *schema.Message) ([]*schema.Message, error) {\n\t\t\tif m == nil {\n\t\t\t\treturn nil, errors.New(\"nil message\")\n\t\t\t}\n\t\t\tout := make([]*schema.Message, 0, 2)\n\t\t\tout = append(out, schema.SystemMessage(recommendTpl))\n\t\t\tout = append(out, m)\n\t\t\treturn out, nil\n\t\t}), nil\n\t}\n\tlambdaPrompt := compose.TransformableLambda[*schema.Message, []*schema.Message](promptTransformOps)\n\n\t// 9) 添加节点到 Workflow\n\twf.AddChatTemplateNode(\"prompt\", chatTpl).AddInput(compose.START)\n\twf.AddChatModelNode(\"chat\", chatModel).AddInput(\"prompt\")\n\twf.AddToolsNode(\"tools\", toolsNode).AddInput(\"chat\")\n\n\t// 注意:这里用新的 lambdaToolToText,替代你原来的“取第一条 message”\n\twf.AddLambdaNode(\"tool_to_text\", lambdaToolToText).AddInput(\"tools\")\n\n\twf.AddLambdaNode(\"prompt_transform\", lambdaPrompt).AddInput(\"tool_to_text\")\n\twf.AddChatModelNode(\"chat_recommend\", chatModel).AddInput(\"prompt_transform\")\n\twf.End().AddInput(\"chat_recommend\")\n\n\t// 10) 编译 & 运行\n\trunnable, err := wf.Compile(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\toutput, err := runnable.Invoke(ctx, map[string]any{\n\t\t\"histories\": []*schema.Message{},\n\t\t\"user_query\": \"我叫 morning, 邮箱是 lumworn@gmail.com。我的目标是提升实战表现,帮我制定训练计划并推荐适合的位置和打法。\",\n\t})\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tprintln(\"=====================思考内容====================\")\n\tif output.ReasoningContent != \"\" {\n\t\tprintln(output.ReasoningContent)\n\t}\n\tprintln(\"=========================================\")\n\tif output.Content != \"\" {\n\t\tprintln(output.Content)\n\t}\n}\n"}, "2749bb4f4f4955b607ee8562141422823d261698": {"t": 1785754917.8710268, "n": 6038, "h": "1c1c4d2a86deca27", "c": "package main\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"log\"\n\t\"os\"\n\n\t\"github.com/cloudwego/eino-ext/components/model/deepseek\"\n\t\"github.com/cloudwego/eino/components/prompt\"\n\t\"github.com/cloudwego/eino/components/tool\"\n\t\"github.com/cloudwego/eino/components/tool/utils\"\n\t\"github.com/cloudwego/eino/compose\"\n\t\"github.com/cloudwego/eino/schema\"\n)\n\n// 工具入参\ntype playerInfoRequest struct {\n\tEmail string `json:\"email\"`\n\tName string `json:\"name\"`\n}\n\n// 工具出参\ntype playerInfoResponse struct {\n\tName string `json:\"name\"`\n\tEmail string `json:\"email\"`\n\tRole string `json:\"role\"` // 后卫/锋线/中锋/教练/爱好者\n\tHeightCM int `json:\"height_cm\"` // 身高\n\tWeightKG int `json:\"weight_kg\"` // 体重\n\tPlayStyle string `json:\"play_style\"` // 风格\n\tWeeklyHours int `json:\"weekly_hours\"` // 每周训练/打球时长\n}\n\nfunc main() {\n\tctx := context.Background()\n\n\t// 创建 Workflow 编排\n\twf := compose.NewWorkflow[map[string]any, *schema.Message]()\n\n\t// 1) 系统提示词模板(篮球主题)\n\tsystemTpl := `你是一名篮球教练与比赛分析师。你需要结合用户的基本信息与训练习惯,\n使用 player_info API 补全用户画像,然后给出:位置建议、核心技能树、一周训练计划、以及一套简单战术建议。\n注意:邮箱必须出现,用于查询信息。`\n\n\tchatTpl := prompt.FromMessages(schema.FString,\n\t\tschema.SystemMessage(systemTpl),\n\t\tschema.MessagesPlaceholder(\"histories\", true),\n\t\tschema.UserMessage(\"{user_query}\"),\n\t)\n\n\t// 2) 推荐模板\n\trecommendTpl := `\n你是一名篮球教练与比赛分析师。请结合“工具返回的用户信息”,为用户输出建议,要求具体、可执行。\n\n--- 训练资源(可选方案库)---\n\n### A. 训练方向库(按位置/风格)\n**1. 后卫(控运与节奏)**\n- 核心:运球对抗、挡拆阅读、急停跳投、突破分球\n- 训练:左右手变向组合、弱侧手终结、1v1 变速\n\n**2. 锋线(持球终结与防守)**\n- 核心:三威胁、低位脚步、协防轮转、错位单打\n- 训练:三分接投+一运、背身转身、closeout 防守\n\n**3. 内线(篮下统治与护框)**\n- 核心:卡位、顺下吃饼、护框、二次进攻\n- 训练:对抗上篮、掩护质量、篮板站位\n\n### B. 一套简单战术(适合大多数业余队)\n- **高位挡拆(P&R)**:持球人借掩护突破/投篮/分球,弱侧埋伏投手\n- **Spain P&R(简化版)**:挡拆后再给顺下人做背掩护,制造错位/空切\n- **5-out(五外)**:拉开空间,强弱侧转移球,靠突破分球创造空位三分\n\n### C. 输出格式要求\n1) 先总结用户画像(身高体重、风格、每周训练时长)\n2) 给出建议位置与核心技能树(3-5个技能)\n3) 输出一周训练计划(按天、每次45-90分钟)\n4) 给一套战术建议 + 业余局实战注意事项(3条)\n`\n\n\t// 3) DeepSeek ChatModel(环境变量取 key)\n\tapiKey := os.Getenv(\"DEEPSEEK_API_KEY\")\n\tif apiKey == \"\" {\n\t\tlog.Fatal(\"缺少环境变量 DEEPSEEK_API_KEY\")\n\t}\n\n\tchatModel, err := deepseek.NewChatModel(ctx, &deepseek.ChatModelConfig{\n\t\tAPIKey: os.Getenv(\"DEEPSEEK_API_KEY\"),\n\t\tModel: \"deepseek-chat\",\n\t\tBaseURL: \"https://api.deepseek.com\",\n\t})\n\tif err != nil {\n\t\tlog.Fatalf(\"创建 DeepSeek ChatModel 失败: %v\", err)\n\t}\n\n\t// 4) 创建工具:player_info(mock 示例)\n\tplayerInfoTool := utils.NewTool(\n\t\t&schema.ToolInfo{\n\t\t\tName: \"player_info\",\n\t\t\tDesc: \"根据用户的姓名和邮箱,查询用户的篮球相关信息(位置倾向、身体数据、打球习惯等)\",\n\t\t\tParamsOneOf: schema.NewParamsOneOfByParams(map[string]*schema.ParameterInfo{\n\t\t\t\t\"name\": {Type: \"string\", Desc: \"用户的姓名\"},\n\t\t\t\t\"email\": {Type: \"string\", Desc: \"用户的邮箱\"},\n\t\t\t}),\n\t\t},\n\t\tfunc(ctx context.Context, input *playerInfoRequest) (*playerInfoResponse, error) {\n\t\t\t// demo:你可以替换成真实查询\n\t\t\treturn &playerInfoResponse{\n\t\t\t\tName: input.Name,\n\t\t\t\tEmail: input.Email,\n\t\t\t\tRole: \"锋线\",\n\t\t\t\tHeightCM: 182,\n\t\t\t\tWeightKG: 78,\n\t\t\t\tPlayStyle: \"偏投射+无球空切,偶尔持球突破\",\n\t\t\t\tWeeklyHours: 4,\n\t\t\t}, nil\n\t\t},\n\t)\n\n\t// 5) 绑定工具到模型(让模型能发 tool_calls)\n\tinfo, err := playerInfoTool.Info(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tif err := chatModel.BindTools([]*schema.ToolInfo{info}); err != nil {\n\t\tpanic(err)\n\t}\n\n\t// 6) 工具节点\n\ttoolsNode, err := compose.NewToolNode(ctx, &compose.ToolsNodeConfig{\n\t\tTools: []tool.BaseTool{playerInfoTool},\n\t})\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\t// 7) 转换:把 toolsNode 输出的 []*schema.Message -> 提炼成一个普通 user message(避免 role=tool 进第二次模型)\n\ttoolToTextOps := func(\n\t\tctx context.Context,\n\t\tinput *schema.StreamReader[[]*schema.Message],\n\t) (*schema.StreamReader[*schema.Message], error) {\n\t\treturn schema.StreamReaderWithConvert(input, func(msgs []*schema.Message) (*schema.Message, error) {\n\t\t\tif len(msgs) == 0 {\n\t\t\t\treturn nil, errors.New(\"no message\")\n\t\t\t}\n\n\t\t\t// 不依赖 Role 字段:把内容统一汇总成 JSON 文本\n\t\t\ttype lite struct {\n\t\t\t\tContent string `json:\"content,omitempty\"`\n\t\t\t\t// 如果你确认 schema.Message 有 Role 字段,可加上 Role: m.Role\n\t\t\t}\n\t\t\tlites := make([]lite, 0, len(msgs))\n\t\t\tfor _, m := range msgs {\n\t\t\t\tif m == nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tlites = append(lites, lite{Content: m.Content})\n\t\t\t}\n\n\t\t\tb, _ := json.MarshalIndent(lites, \"\", \" \")\n\t\t\ttext := \"工具返回的用户信息(汇总):\\n\" + string(b)\n\n\t\t\t// 关键:返回普通 user 消息,而不是 tool 消息\n\t\t\treturn schema.UserMessage(text), nil\n\t\t}), nil\n\t}\n\tlambdaToolToText := compose.TransformableLambda[[]*schema.Message, *schema.Message](toolToTextOps)\n\n\t// 8) 再转换:把 *schema.Message -> []*schema.Message(system recommendTpl + user(工具结果文本))\n\tpromptTransformOps := func(\n\t\tctx context.Context,\n\t\tinput *schema.StreamReader[*schema.Message],\n\t) (*schema.StreamReader[[]*schema.Message], error) {\n\t\treturn schema.StreamReaderWithConvert(input, func(m *schema.Message) ([]*schema.Message, error) {\n\t\t\tif m == nil {\n\t\t\t\treturn nil, errors.New(\"nil message\")\n\t\t\t}\n\t\t\tout := make([]*schema.Message, 0, 2)\n\t\t\tout = append(out, schema.SystemMessage(recommendTpl))\n\t\t\tout = append(out, m)\n\t\t\treturn out, nil\n\t\t}), nil\n\t}\n\tlambdaPrompt := compose.TransformableLambda[*schema.Message, []*schema.Message](promptTransformOps)\n\n\t// 9) 添加节点到 Workflow\n\twf.AddChatTemplateNode(\"prompt\", chatTpl).AddInput(compose.START)\n\twf.AddChatModelNode(\"chat\", chatModel).AddInput(\"prompt\")\n\twf.AddToolsNode(\"tools\", toolsNode).AddInput(\"chat\")\n\n\t// 注意:这里用新的 lambdaToolToText,替代你原来的“取第一条 message”\n\twf.AddLambdaNode(\"tool_to_text\", lambdaToolToText).AddInput(\"tools\")\n\n\twf.AddLambdaNode(\"prompt_transform\", lambdaPrompt).AddInput(\"tool_to_text\")\n\twf.AddChatModelNode(\"chat_recommend\", chatModel).AddInput(\"prompt_transform\")\n\twf.End().AddInput(\"chat_recommend\")\n\n\t// 10) 编译 & 运行\n\trunnable, err := wf.Compile(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\toutput, err := runnable.Invoke(ctx, map[string]any{\n\t\t\"histories\": []*schema.Message{},\n\t\t\"user_query\": \"我叫 morning, 邮箱是 lumworn@gmail.com。我的目标是提升实战表现,帮我制定训练计划并推荐适合的位置和打法。\",\n\t})\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tprintln(\"=====================思考内容====================\")\n\tif output.ReasoningContent != \"\" {\n\t\tprintln(output.ReasoningContent)\n\t}\n\tprintln(\"=========================================\")\n\tif output.Content != \"\" {\n\t\tprintln(output.Content)\n\t}\n}\n"}}, "lab10/case/tool_set.go": {"ec86072535f9835cf7765273291366d07bf9a0bc": {"t": 1785745620.4144654, "n": 2844, "h": "78a80a6ab61646f3", "c": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\t\"time\"\n\n\t\"github.com/cloudwego/eino-ext/components/model/deepseek\"\n\t\"github.com/cloudwego/eino/components/model\"\n\t\"github.com/cloudwego/eino/components/tool\"\n\t\"github.com/cloudwego/eino/components/tool/utils\"\n\t\"github.com/cloudwego/eino/compose\"\n\t\"github.com/cloudwego/eino/schema\"\n)\n\nfunc main() {\n\tctx := context.Background()\n\n\t// 1. 创建多个工具\n\t// 计算器工具\n\tcalculator := utils.NewTool(\n\t\t&schema.ToolInfo{\n\t\t\tName: \"calculator\",\n\t\t\tDesc: \"执行数学计算\",\n\t\t\tParamsOneOf: schema.NewParamsOneOfByParams(map[string]*schema.ParameterInfo{\n\t\t\t\t\"expression\": {\n\t\t\t\t\tType: \"string\",\n\t\t\t\t\tDesc: \"数学表达式,例如: 10 + 20\",\n\t\t\t\t\tRequired: true,\n\t\t\t\t},\n\t\t\t}),\n\t\t},\n\t\tfunc(ctx context.Context, params map[string]any) (string, error) {\n\t\t\t// 简化实现:只处理简单加法\n\t\t\treturn \"30\", nil\n\t\t},\n\t)\n\n\t// 时间工具\n\ttimeTool := utils.NewTool(\n\t\t&schema.ToolInfo{\n\t\t\tName: \"get_time\",\n\t\t\tDesc: \"获取当前时间\",\n\t\t\tParamsOneOf: schema.NewParamsOneOfByParams(map[string]*schema.ParameterInfo{}),\n\t\t},\n\t\tfunc(ctx context.Context, params map[string]any) (string, error) {\n\t\t\treturn time.Now().Format(\"2006-01-02 15:04:05\"), nil\n\t\t},\n\t)\n\n\t// 2. 创建 ChatModel(支持 Function Calling)\n\tchatModel, err := deepseek.NewChatModel(ctx, &deepseek.ChatModelConfig{\n\t\tAPIKey: os.Getenv(\"DEEPSEEK_API_KEY\"),\n\t\tModel: \"deepseek-chat\",\n\t\tBaseURL: \"https://api.deepseek.com\",\n\t})\n\tif err != nil {\n\t\tlog.Fatalf(\"创建模型失败: %v\", err)\n\t}\n\n\t// 3. 创建 ToolsNode\n\ttoolsNode, err := compose.NewToolNode(ctx, &compose.ToolsNodeConfig{\n\t\tTools: []tool.BaseTool{calculator, timeTool},\n\t})\n\tif err != nil {\n\t\tlog.Fatalf(\"创建 ToolsNode 失败: %v\", err)\n\t}\n\n\t// 4. 获取工具信息列表\n\tcalcInfo, err := calculator.Info(ctx)\n\tif err != nil {\n\t\tlog.Fatalf(\"获取工具信息失败: %v\", err)\n\t}\n\ttimeInfo, err := timeTool.Info(ctx)\n\tif err != nil {\n\t\tlog.Fatalf(\"获取工具信息失败: %v\", err)\n\t}\n\n\ttoolInfos := []*schema.ToolInfo{calcInfo, timeInfo}\n\n\t// 测试多个场景\n\ttestCases := []string{\n\t\t\"现在几点了?\",\n\t\t\"帮我计算 10 + 20 等于多少\",\n\t\t\"你好,请介绍一下你自己\",\n\t}\n\n\tfor i, question := range testCases {\n\t\tfmt.Printf(\"\\\\n========== 测试 %d ==========\\\\n\", i+1)\n\t\tfmt.Printf(\"问题: %s\\\\n\", question)\n\n\t\tmessages := []*schema.Message{\n\t\t\tschema.UserMessage(question),\n\t\t}\n\n\t\t// AI 决定使用工具\n\t\tresponse, err := chatModel.Generate(ctx, messages,\n\t\t\tmodel.WithTools(toolInfos),\n\t\t)\n\t\tif err != nil {\n\t\t\tlog.Printf(\"生成失败: %v\", err)\n\t\t\tcontinue\n\t\t}\n\n\t\tfmt.Printf(\"AI 的决策:\\\\n\")\n\t\tif len(response.ToolCalls) > 0 {\n\t\t\tfor _, toolCall := range response.ToolCalls {\n\t\t\t\tfmt.Printf(\" ✓ 使用工具: %s\\\\n\", toolCall.Function.Name)\n\t\t\t\tfmt.Printf(\" 参数: %s\\\\n\", toolCall.Function.Arguments)\n\n\t\t\t\t// 执行工具\n\t\t\t\ttoolResult, err := toolsNode.Invoke(ctx, response)\n\t\t\t\tif err != nil {\n\t\t\t\t\tlog.Printf(\" 工具执行失败: %v\", err)\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tfmt.Printf(\" 结果: %+v\\\\n\", toolResult)\n\t\t\t}\n\t\t} else {\n\t\t\tfmt.Printf(\" ✓ 直接回答: %s\\\\n\", response.Content)\n\t\t}\n\t}\n}\n"}, "1bc30839daf93de0794c258576348eed5ec9da62": {"t": 1785754918.6899152, "n": 2844, "h": "78a80a6ab61646f3", "c": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\t\"time\"\n\n\t\"github.com/cloudwego/eino-ext/components/model/deepseek\"\n\t\"github.com/cloudwego/eino/components/model\"\n\t\"github.com/cloudwego/eino/components/tool\"\n\t\"github.com/cloudwego/eino/components/tool/utils\"\n\t\"github.com/cloudwego/eino/compose\"\n\t\"github.com/cloudwego/eino/schema\"\n)\n\nfunc main() {\n\tctx := context.Background()\n\n\t// 1. 创建多个工具\n\t// 计算器工具\n\tcalculator := utils.NewTool(\n\t\t&schema.ToolInfo{\n\t\t\tName: \"calculator\",\n\t\t\tDesc: \"执行数学计算\",\n\t\t\tParamsOneOf: schema.NewParamsOneOfByParams(map[string]*schema.ParameterInfo{\n\t\t\t\t\"expression\": {\n\t\t\t\t\tType: \"string\",\n\t\t\t\t\tDesc: \"数学表达式,例如: 10 + 20\",\n\t\t\t\t\tRequired: true,\n\t\t\t\t},\n\t\t\t}),\n\t\t},\n\t\tfunc(ctx context.Context, params map[string]any) (string, error) {\n\t\t\t// 简化实现:只处理简单加法\n\t\t\treturn \"30\", nil\n\t\t},\n\t)\n\n\t// 时间工具\n\ttimeTool := utils.NewTool(\n\t\t&schema.ToolInfo{\n\t\t\tName: \"get_time\",\n\t\t\tDesc: \"获取当前时间\",\n\t\t\tParamsOneOf: schema.NewParamsOneOfByParams(map[string]*schema.ParameterInfo{}),\n\t\t},\n\t\tfunc(ctx context.Context, params map[string]any) (string, error) {\n\t\t\treturn time.Now().Format(\"2006-01-02 15:04:05\"), nil\n\t\t},\n\t)\n\n\t// 2. 创建 ChatModel(支持 Function Calling)\n\tchatModel, err := deepseek.NewChatModel(ctx, &deepseek.ChatModelConfig{\n\t\tAPIKey: os.Getenv(\"DEEPSEEK_API_KEY\"),\n\t\tModel: \"deepseek-chat\",\n\t\tBaseURL: \"https://api.deepseek.com\",\n\t})\n\tif err != nil {\n\t\tlog.Fatalf(\"创建模型失败: %v\", err)\n\t}\n\n\t// 3. 创建 ToolsNode\n\ttoolsNode, err := compose.NewToolNode(ctx, &compose.ToolsNodeConfig{\n\t\tTools: []tool.BaseTool{calculator, timeTool},\n\t})\n\tif err != nil {\n\t\tlog.Fatalf(\"创建 ToolsNode 失败: %v\", err)\n\t}\n\n\t// 4. 获取工具信息列表\n\tcalcInfo, err := calculator.Info(ctx)\n\tif err != nil {\n\t\tlog.Fatalf(\"获取工具信息失败: %v\", err)\n\t}\n\ttimeInfo, err := timeTool.Info(ctx)\n\tif err != nil {\n\t\tlog.Fatalf(\"获取工具信息失败: %v\", err)\n\t}\n\n\ttoolInfos := []*schema.ToolInfo{calcInfo, timeInfo}\n\n\t// 测试多个场景\n\ttestCases := []string{\n\t\t\"现在几点了?\",\n\t\t\"帮我计算 10 + 20 等于多少\",\n\t\t\"你好,请介绍一下你自己\",\n\t}\n\n\tfor i, question := range testCases {\n\t\tfmt.Printf(\"\\\\n========== 测试 %d ==========\\\\n\", i+1)\n\t\tfmt.Printf(\"问题: %s\\\\n\", question)\n\n\t\tmessages := []*schema.Message{\n\t\t\tschema.UserMessage(question),\n\t\t}\n\n\t\t// AI 决定使用工具\n\t\tresponse, err := chatModel.Generate(ctx, messages,\n\t\t\tmodel.WithTools(toolInfos),\n\t\t)\n\t\tif err != nil {\n\t\t\tlog.Printf(\"生成失败: %v\", err)\n\t\t\tcontinue\n\t\t}\n\n\t\tfmt.Printf(\"AI 的决策:\\\\n\")\n\t\tif len(response.ToolCalls) > 0 {\n\t\t\tfor _, toolCall := range response.ToolCalls {\n\t\t\t\tfmt.Printf(\" ✓ 使用工具: %s\\\\n\", toolCall.Function.Name)\n\t\t\t\tfmt.Printf(\" 参数: %s\\\\n\", toolCall.Function.Arguments)\n\n\t\t\t\t// 执行工具\n\t\t\t\ttoolResult, err := toolsNode.Invoke(ctx, response)\n\t\t\t\tif err != nil {\n\t\t\t\t\tlog.Printf(\" 工具执行失败: %v\", err)\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tfmt.Printf(\" 结果: %+v\\\\n\", toolResult)\n\t\t\t}\n\t\t} else {\n\t\t\tfmt.Printf(\" ✓ 直接回答: %s\\\\n\", response.Content)\n\t\t}\n\t}\n}\n"}}, "lab03/case/tran_assistant.go": {"ec86072535f9835cf7765273291366d07bf9a0bc": {"t": 1785746055.547555, "n": 2999, "h": "90242fac5ee6f399", "c": "package main\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/cloudwego/eino-ext/components/model/deepseek\"\n\t\"github.com/cloudwego/eino/schema\"\n)\n\n// Translator 基于 Deepseek 的翻译助手\ntype Translator struct {\n\tchatModel *deepseek.ChatModel\n}\n\n// TranslatorConfig 翻译器配置\ntype TranslatorConfig struct {\n\tAPIKey string\n\tModel string\n\tBaseURL string\n\tTimeout time.Duration\n\tRetries int\n}\n\n// NewTranslator 创建一个新的翻译器实例\nfunc NewTranslator(cfg TranslatorConfig) (*Translator, error) {\n\tif strings.TrimSpace(cfg.APIKey) == \"\" {\n\t\treturn nil, errors.New(\"missing api key\")\n\t}\n\tif cfg.Model == \"\" {\n\t\tcfg.Model = \"deepseek-chat\"\n\t}\n\tif cfg.BaseURL == \"\" {\n\t\tcfg.BaseURL = \"https://api.deepseek.com\"\n\t}\n\tif cfg.Timeout <= 0 {\n\t\tcfg.Timeout = 30 * time.Second\n\t}\n\tif cfg.Retries < 0 {\n\t\tcfg.Retries = 0\n\t}\n\n\t// 建模时可用 Background;真正超时控制在 Translate 时做\n\tchatModel, err := deepseek.NewChatModel(context.Background(), &deepseek.ChatModelConfig{\n\t\tAPIKey: cfg.APIKey,\n\t\tModel: cfg.Model,\n\t\tBaseURL: cfg.BaseURL,\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &Translator{chatModel: chatModel}, nil\n}\n\n// Translate 翻译文本到目标语言\nfunc (t *Translator) Translate(ctx context.Context, text, targetLang string) (string, error) {\n\ttext = strings.TrimSpace(text)\n\ttargetLang = strings.TrimSpace(targetLang)\n\tif text == \"\" {\n\t\treturn \"\", errors.New(\"empty text\")\n\t}\n\tif targetLang == \"\" {\n\t\treturn \"\", errors.New(\"empty target language\")\n\t}\n\n\t// 超时控制\n\tctx, cancel := context.WithTimeout(ctx, 30*time.Second)\n\tdefer cancel()\n\n\t// 更严格的提示词:只输出译文;保留格式;不添加引号/解释\n\tsystem := fmt.Sprintf(\n\t\t\"你是一个专业翻译引擎。将用户输入翻译成%s。\"+\n\t\t\t\"要求:只输出译文,不要解释;保留原有换行与列表格式;不要添加引号;不要输出多余内容。\",\n\t\ttargetLang,\n\t)\n\n\tmessages := []*schema.Message{\n\t\tschema.SystemMessage(system),\n\t\tschema.UserMessage(text),\n\t}\n\n\t// 简单重试(网络抖动/429 等情况你可再细化错误判断)\n\tvar lastErr error\n\tfor attempt := 0; attempt <= 2; attempt++ { // 默认 3 次(你可配置)\n\t\tresp, err := t.chatModel.Generate(ctx, messages)\n\t\tif err == nil {\n\t\t\treturn strings.TrimSpace(resp.Content), nil\n\t\t}\n\t\tlastErr = err\n\n\t\t// ctx 超时/取消就别重试\n\t\tif errors.Is(ctx.Err(), context.DeadlineExceeded) || errors.Is(ctx.Err(), context.Canceled) {\n\t\t\tbreak\n\t\t}\n\n\t\t// 退避\n\t\ttime.Sleep(time.Duration(attempt+1) * 300 * time.Millisecond)\n\t}\n\treturn \"\", lastErr\n}\n\nfunc main() {\n\tapiKey := os.Getenv(\"DEEPSEEK_API_KEY\")\n\ttranslator, err := NewTranslator(TranslatorConfig{\n\t\tAPIKey: apiKey,\n\t\tModel: \"deepseek-chat\",\n\t\tBaseURL: \"https://api.deepseek.com\",\n\t\tTimeout: 30 * time.Second,\n\t\tRetries: 2,\n\t})\n\tif err != nil {\n\t\tlog.Fatalf(\"创建翻译器失败: %v\", err)\n\t}\n\n\ttests := []struct {\n\t\tcontent string\n\t\ttarget string\n\t}{\n\t\t{\"Hello, how are you?\", \"中文\"},\n\t\t{\"Eino 是一个强大的 AI 开发框架\", \"English\"},\n\t\t{\"Les roses sont rouges\", \"中文\"},\n\t\t{\"- item1\\n- item2\\n\", \"中文\"},\n\t}\n\n\tfor _, item := range tests {\n\t\tresult, err := translator.Translate(context.Background(), item.content, item.target)\n\t\tif err != nil {\n\t\t\tlog.Printf(\"翻译失败: %v\", err)\n\t\t\tcontinue\n\t\t}\n\t\tfmt.Printf(\"原文: %s\\n翻译: %s\\n\\n\", item.content, result)\n\t}\n}\n"}, "8eb627cbcbdd4698ba7ab35067e21d4ba10a77b4": {"t": 1785754917.0132692, "n": 2999, "h": "90242fac5ee6f399", "c": "package main\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/cloudwego/eino-ext/components/model/deepseek\"\n\t\"github.com/cloudwego/eino/schema\"\n)\n\n// Translator 基于 Deepseek 的翻译助手\ntype Translator struct {\n\tchatModel *deepseek.ChatModel\n}\n\n// TranslatorConfig 翻译器配置\ntype TranslatorConfig struct {\n\tAPIKey string\n\tModel string\n\tBaseURL string\n\tTimeout time.Duration\n\tRetries int\n}\n\n// NewTranslator 创建一个新的翻译器实例\nfunc NewTranslator(cfg TranslatorConfig) (*Translator, error) {\n\tif strings.TrimSpace(cfg.APIKey) == \"\" {\n\t\treturn nil, errors.New(\"missing api key\")\n\t}\n\tif cfg.Model == \"\" {\n\t\tcfg.Model = \"deepseek-chat\"\n\t}\n\tif cfg.BaseURL == \"\" {\n\t\tcfg.BaseURL = \"https://api.deepseek.com\"\n\t}\n\tif cfg.Timeout <= 0 {\n\t\tcfg.Timeout = 30 * time.Second\n\t}\n\tif cfg.Retries < 0 {\n\t\tcfg.Retries = 0\n\t}\n\n\t// 建模时可用 Background;真正超时控制在 Translate 时做\n\tchatModel, err := deepseek.NewChatModel(context.Background(), &deepseek.ChatModelConfig{\n\t\tAPIKey: cfg.APIKey,\n\t\tModel: cfg.Model,\n\t\tBaseURL: cfg.BaseURL,\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &Translator{chatModel: chatModel}, nil\n}\n\n// Translate 翻译文本到目标语言\nfunc (t *Translator) Translate(ctx context.Context, text, targetLang string) (string, error) {\n\ttext = strings.TrimSpace(text)\n\ttargetLang = strings.TrimSpace(targetLang)\n\tif text == \"\" {\n\t\treturn \"\", errors.New(\"empty text\")\n\t}\n\tif targetLang == \"\" {\n\t\treturn \"\", errors.New(\"empty target language\")\n\t}\n\n\t// 超时控制\n\tctx, cancel := context.WithTimeout(ctx, 30*time.Second)\n\tdefer cancel()\n\n\t// 更严格的提示词:只输出译文;保留格式;不添加引号/解释\n\tsystem := fmt.Sprintf(\n\t\t\"你是一个专业翻译引擎。将用户输入翻译成%s。\"+\n\t\t\t\"要求:只输出译文,不要解释;保留原有换行与列表格式;不要添加引号;不要输出多余内容。\",\n\t\ttargetLang,\n\t)\n\n\tmessages := []*schema.Message{\n\t\tschema.SystemMessage(system),\n\t\tschema.UserMessage(text),\n\t}\n\n\t// 简单重试(网络抖动/429 等情况你可再细化错误判断)\n\tvar lastErr error\n\tfor attempt := 0; attempt <= 2; attempt++ { // 默认 3 次(你可配置)\n\t\tresp, err := t.chatModel.Generate(ctx, messages)\n\t\tif err == nil {\n\t\t\treturn strings.TrimSpace(resp.Content), nil\n\t\t}\n\t\tlastErr = err\n\n\t\t// ctx 超时/取消就别重试\n\t\tif errors.Is(ctx.Err(), context.DeadlineExceeded) || errors.Is(ctx.Err(), context.Canceled) {\n\t\t\tbreak\n\t\t}\n\n\t\t// 退避\n\t\ttime.Sleep(time.Duration(attempt+1) * 300 * time.Millisecond)\n\t}\n\treturn \"\", lastErr\n}\n\nfunc main() {\n\tapiKey := os.Getenv(\"DEEPSEEK_API_KEY\")\n\ttranslator, err := NewTranslator(TranslatorConfig{\n\t\tAPIKey: apiKey,\n\t\tModel: \"deepseek-chat\",\n\t\tBaseURL: \"https://api.deepseek.com\",\n\t\tTimeout: 30 * time.Second,\n\t\tRetries: 2,\n\t})\n\tif err != nil {\n\t\tlog.Fatalf(\"创建翻译器失败: %v\", err)\n\t}\n\n\ttests := []struct {\n\t\tcontent string\n\t\ttarget string\n\t}{\n\t\t{\"Hello, how are you?\", \"中文\"},\n\t\t{\"Eino 是一个强大的 AI 开发框架\", \"English\"},\n\t\t{\"Les roses sont rouges\", \"中文\"},\n\t\t{\"- item1\\n- item2\\n\", \"中文\"},\n\t}\n\n\tfor _, item := range tests {\n\t\tresult, err := translator.Translate(context.Background(), item.content, item.target)\n\t\tif err != nil {\n\t\t\tlog.Printf(\"翻译失败: %v\", err)\n\t\t\tcontinue\n\t\t}\n\t\tfmt.Printf(\"原文: %s\\n翻译: %s\\n\\n\", item.content, result)\n\t}\n}\n"}}, "lab03/error/error_handling.go": {"ec86072535f9835cf7765273291366d07bf9a0bc": {"t": 1785746055.6515546, "n": 1521, "h": "a9387bcb6b3d1054", "c": "package main\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\t\"time\"\n\n\t\"github.com/cloudwego/eino-ext/components/model/deepseek\"\n\t\"github.com/cloudwego/eino/schema\"\n)\n\n// generateWithRetry 尝试多次调用 ChatModel 的 Generate 方法,直到成功或达到最大重试次数。\nfunc generateWithRetry(ctx context.Context, chatModel *deepseek.ChatModel, messages []*schema.Message, maxRetries int) (*schema.Message, error) {\n\t// 记录最后一次错误\n\tvar lastErr error\n\n\t// 重试逻辑\n\tfor i := 0; i < maxRetries; i++ {\n\t\tresponse, err := chatModel.Generate(ctx, messages)\n\t\tif err == nil {\n\t\t\treturn response, nil\n\t\t}\n\n\t\tlastErr = err\n\t\tlog.Printf(\"尝试 %d/%d 失败: %v\", i+1, maxRetries, err)\n\n\t\t// 指数退避\n\t\tif i < maxRetries-1 {\n\t\t\tbackoff := time.Duration(1<<uint(i)) * time.Second\n\t\t\tlog.Printf(\"等待 %v 后重试...\", backoff)\n\t\t\ttime.Sleep(backoff)\n\t\t}\n\t}\n\n\treturn nil, fmt.Errorf(\"重试 %d 次后仍然失败: %w\", maxRetries, lastErr)\n}\n\nfunc main() {\n\tctx := context.Background()\n\n\t// 创建 Deepseek ChatModel 实例\n\tchatModel, err := deepseek.NewChatModel(ctx, &deepseek.ChatModelConfig{\n\t\tAPIKey: os.Getenv(\"DEEPSEEK_API_KEY\"),\n\t\tModel: \"deepseek-chat\",\n\t\tBaseURL: \"https://api.deepseek.com\",\n\t\t// 设置超时\n\t\tTimeout: 30 * time.Second,\n\t})\n\tif err != nil {\n\t\tlog.Fatalf(\"创建失败: %v\", err)\n\t}\n\n\tmessages := []*schema.Message{\n\t\tschema.UserMessage(\"你好\"),\n\t}\n\n\t// 带重试的生成\n\tresponse, err := generateWithRetry(ctx, chatModel, messages, 3)\n\tif err != nil {\n\t\tif errors.Is(err, context.DeadlineExceeded) {\n\t\t\tlog.Fatalf(\"请求超时\")\n\t\t}\n\t\tlog.Fatalf(\"生成失败: %v\", err)\n\t}\n\n\tfmt.Printf(\"成功! 回答: %s\\\\n\", response.Content)\n}\n"}, "7138626c4f5a63973ea8e9dff2686ebf42d1e763": {"t": 1785754914.0313032, "n": 1521, "h": "a9387bcb6b3d1054", "c": "package main\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\t\"time\"\n\n\t\"github.com/cloudwego/eino-ext/components/model/deepseek\"\n\t\"github.com/cloudwego/eino/schema\"\n)\n\n// generateWithRetry 尝试多次调用 ChatModel 的 Generate 方法,直到成功或达到最大重试次数。\nfunc generateWithRetry(ctx context.Context, chatModel *deepseek.ChatModel, messages []*schema.Message, maxRetries int) (*schema.Message, error) {\n\t// 记录最后一次错误\n\tvar lastErr error\n\n\t// 重试逻辑\n\tfor i := 0; i < maxRetries; i++ {\n\t\tresponse, err := chatModel.Generate(ctx, messages)\n\t\tif err == nil {\n\t\t\treturn response, nil\n\t\t}\n\n\t\tlastErr = err\n\t\tlog.Printf(\"尝试 %d/%d 失败: %v\", i+1, maxRetries, err)\n\n\t\t// 指数退避\n\t\tif i < maxRetries-1 {\n\t\t\tbackoff := time.Duration(1<<uint(i)) * time.Second\n\t\t\tlog.Printf(\"等待 %v 后重试...\", backoff)\n\t\t\ttime.Sleep(backoff)\n\t\t}\n\t}\n\n\treturn nil, fmt.Errorf(\"重试 %d 次后仍然失败: %w\", maxRetries, lastErr)\n}\n\nfunc main() {\n\tctx := context.Background()\n\n\t// 创建 Deepseek ChatModel 实例\n\tchatModel, err := deepseek.NewChatModel(ctx, &deepseek.ChatModelConfig{\n\t\tAPIKey: os.Getenv(\"DEEPSEEK_API_KEY\"),\n\t\tModel: \"deepseek-chat\",\n\t\tBaseURL: \"https://api.deepseek.com\",\n\t\t// 设置超时\n\t\tTimeout: 30 * time.Second,\n\t})\n\tif err != nil {\n\t\tlog.Fatalf(\"创建失败: %v\", err)\n\t}\n\n\tmessages := []*schema.Message{\n\t\tschema.UserMessage(\"你好\"),\n\t}\n\n\t// 带重试的生成\n\tresponse, err := generateWithRetry(ctx, chatModel, messages, 3)\n\tif err != nil {\n\t\tif errors.Is(err, context.DeadlineExceeded) {\n\t\t\tlog.Fatalf(\"请求超时\")\n\t\t}\n\t\tlog.Fatalf(\"生成失败: %v\", err)\n\t}\n\n\tfmt.Printf(\"成功! 回答: %s\\\\n\", response.Content)\n}\n"}}, "lab11/vision/vision_qa.go": {"ec86072535f9835cf7765273291366d07bf9a0bc": {"t": 1785746057.9317224, "n": 3780, "h": "3517488df5a554b8", "c": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\n\t\"github.com/NuyoahCh/einotelos/lab11/common\"\n\t\"github.com/cloudwego/eino-ext/components/model/deepseek\"\n\t\"github.com/cloudwego/eino/components/model\"\n\t\"github.com/cloudwego/eino/schema\"\n)\n\n// VisionQA 演示视觉问答(Visual Question Answering)功能\n// 用户可以针对图像提出具体问题,模型会基于图像内容给出答案\nfunc main() {\n\tctx := context.Background()\n\n\t// 创建支持视觉的 ChatModel\n\tchatModel, err := deepseek.NewChatModel(ctx, &deepseek.ChatModelConfig{\n\t\tAPIKey: os.Getenv(\"DEEPSEEK_API_KEY\"),\n\t\tModel: \"deepseek-chat\",\n\t\tBaseURL: \"https://api.deepseek.com\",\n\t})\n\tif err != nil {\n\t\tlog.Fatalf(\"创建 ChatModel 实例失败: %v\", err)\n\t}\n\n\t// 示例图像 URL(请替换为实际图像)\n\timageURL := \"https://example.com/product-image.jpg\"\n\n\t// 场景 1:产品识别和描述\n\tfmt.Println(\"=== 场景 1: 产品识别 ===\")\n\tproductQuestions := []string{\n\t\t\"这是什么产品?\",\n\t\t\"这个产品的主要特点是什么?\",\n\t\t\"产品的颜色和材质看起来如何?\",\n\t\t\"这个产品适合什么场景使用?\",\n\t}\n\taskQuestionsAboutImage(ctx, chatModel, imageURL, productQuestions, \"你是一个产品分析专家。\")\n\n\t// 场景 2:场景理解\n\tfmt.Println(\"\\n=== 场景 2: 场景理解 ===\")\n\tsceneURL := \"https://example.com/street-scene.jpg\"\n\tsceneQuestions := []string{\n\t\t\"这是什么地方?\",\n\t\t\"图片中有多少人?\",\n\t\t\"天气情况如何?\",\n\t\t\"这个场景可能是在什么时间拍摄的?\",\n\t}\n\taskQuestionsAboutImage(ctx, chatModel, sceneURL, sceneQuestions, \"你是一个场景分析专家。\")\n\n\t// 场景 3:文字识别(OCR)\n\tfmt.Println(\"\\n=== 场景 3: 图像中的文字识别 ===\")\n\ttextImageURL := \"https://example.com/text-image.jpg\"\n\tocrQuestions := []string{\n\t\t\"图片中有哪些文字?请全部列出。\",\n\t\t\"文字的语言是什么?\",\n\t\t\"文字内容的主题是什么?\",\n\t}\n\taskQuestionsAboutImage(ctx, chatModel, textImageURL, ocrQuestions, \"你是一个 OCR 专家,擅长识别图像中的文字。\")\n\n\t// 场景 4:比较多张图片\n\tfmt.Println(\"\\n=== 场景 4: 多图比较 ===\")\n\tcompareImages(ctx, chatModel)\n}\n\n// askQuestionsAboutImage 针对单张图像提出多个问题\nfunc askQuestionsAboutImage(ctx context.Context, chatModel model.ChatModel, imageURL string, questions []string, systemPrompt string) {\n\tfor i, question := range questions {\n\t\tmessages := []*schema.Message{\n\t\t\tschema.SystemMessage(systemPrompt),\n\t\t\t{\n\t\t\t\tRole: schema.User,\n\t\t\t\tUserInputMultiContent: []schema.MessageInputPart{\n\t\t\t\t\tcommon.NewTextPart(question),\n\t\t\t\t\tcommon.NewImageURLPart(imageURL),\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\tresponse, err := chatModel.Generate(ctx, messages)\n\t\tif err != nil {\n\t\t\tlog.Printf(\"问题 %d 处理失败: %v\", i+1, err)\n\t\t\tcontinue\n\t\t}\n\n\t\tfmt.Printf(\"\\nQ%d: %s\\n\", i+1, question)\n\t\tfmt.Printf(\"A%d: %s\\n\", i+1, response.Content)\n\t}\n}\n\n// compareImages 比较多张图片的内容\nfunc compareImages(ctx context.Context, chatModel model.ChatModel) {\n\timage1URL := \"https://example.com/image1.jpg\"\n\timage2URL := \"https://example.com/image2.jpg\"\n\n\tmessages := []*schema.Message{\n\t\tschema.SystemMessage(\"你是一个图像比较专家,能够分析和比较多张图片的异同。\"),\n\t\t{\n\t\t\tRole: schema.User,\n\t\t\tUserInputMultiContent: []schema.MessageInputPart{\n\t\t\t\tcommon.NewTextPart(\"请比较这两张图片,说明它们的相似之处和不同之处:\"),\n\t\t\t\tcommon.NewImageURLPart(image1URL),\n\t\t\t\tcommon.NewImageURLPart(image2URL),\n\t\t\t},\n\t\t},\n\t}\n\n\tresponse, err := chatModel.Generate(ctx, messages)\n\tif err != nil {\n\t\tlog.Printf(\"图像比较失败: %v\", err)\n\t\treturn\n\t}\n\n\tfmt.Printf(\"\\n图像比较结果:\\n%s\\n\", response.Content)\n}\n\n// 使用本地图像文件的示例(需要转换为 Base64)\nfunc useLocalImage(ctx context.Context, chatModel model.ChatModel, imagePath string) {\n\t// 读取本地图像文件\n\timageData, err := os.ReadFile(imagePath)\n\tif err != nil {\n\t\tlog.Printf(\"读取图像文件失败: %v\", err)\n\t\treturn\n\t}\n\n\t// 将图像转换为 Base64 编码\n\t// 注意:实际使用时需要添加适当的 MIME 类型前缀\n\t// 例如:data:image/jpeg;base64,<base64_data>\n\tbase64Image := fmt.Sprintf(\"data:image/jpeg;base64,%s\", imageData)\n\n\tmessages := []*schema.Message{\n\t\tschema.SystemMessage(\"你是一个图像分析助手。\"),\n\t\t{\n\t\t\tRole: schema.User,\n\t\t\tUserInputMultiContent: []schema.MessageInputPart{\n\t\t\t\tcommon.NewTextPart(\"请描述这张图片。\"),\n\t\t\t\tcommon.NewImageURLPart(base64Image),\n\t\t\t},\n\t\t},\n\t}\n\n\tresponse, err := chatModel.Generate(ctx, messages)\n\tif err != nil {\n\t\tlog.Printf(\"处理本地图像失败: %v\", err)\n\t\treturn\n\t}\n\n\tfmt.Printf(\"本地图像分析结果:\\n%s\\n\", response.Content)\n}\n"}, "79507ffc4738d48cf819a38907ab88533cd68212": {"t": 1785754915.2028797, "n": 3780, "h": "3517488df5a554b8", "c": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\n\t\"github.com/NuyoahCh/einotelos/lab11/common\"\n\t\"github.com/cloudwego/eino-ext/components/model/deepseek\"\n\t\"github.com/cloudwego/eino/components/model\"\n\t\"github.com/cloudwego/eino/schema\"\n)\n\n// VisionQA 演示视觉问答(Visual Question Answering)功能\n// 用户可以针对图像提出具体问题,模型会基于图像内容给出答案\nfunc main() {\n\tctx := context.Background()\n\n\t// 创建支持视觉的 ChatModel\n\tchatModel, err := deepseek.NewChatModel(ctx, &deepseek.ChatModelConfig{\n\t\tAPIKey: os.Getenv(\"DEEPSEEK_API_KEY\"),\n\t\tModel: \"deepseek-chat\",\n\t\tBaseURL: \"https://api.deepseek.com\",\n\t})\n\tif err != nil {\n\t\tlog.Fatalf(\"创建 ChatModel 实例失败: %v\", err)\n\t}\n\n\t// 示例图像 URL(请替换为实际图像)\n\timageURL := \"https://example.com/product-image.jpg\"\n\n\t// 场景 1:产品识别和描述\n\tfmt.Println(\"=== 场景 1: 产品识别 ===\")\n\tproductQuestions := []string{\n\t\t\"这是什么产品?\",\n\t\t\"这个产品的主要特点是什么?\",\n\t\t\"产品的颜色和材质看起来如何?\",\n\t\t\"这个产品适合什么场景使用?\",\n\t}\n\taskQuestionsAboutImage(ctx, chatModel, imageURL, productQuestions, \"你是一个产品分析专家。\")\n\n\t// 场景 2:场景理解\n\tfmt.Println(\"\\n=== 场景 2: 场景理解 ===\")\n\tsceneURL := \"https://example.com/street-scene.jpg\"\n\tsceneQuestions := []string{\n\t\t\"这是什么地方?\",\n\t\t\"图片中有多少人?\",\n\t\t\"天气情况如何?\",\n\t\t\"这个场景可能是在什么时间拍摄的?\",\n\t}\n\taskQuestionsAboutImage(ctx, chatModel, sceneURL, sceneQuestions, \"你是一个场景分析专家。\")\n\n\t// 场景 3:文字识别(OCR)\n\tfmt.Println(\"\\n=== 场景 3: 图像中的文字识别 ===\")\n\ttextImageURL := \"https://example.com/text-image.jpg\"\n\tocrQuestions := []string{\n\t\t\"图片中有哪些文字?请全部列出。\",\n\t\t\"文字的语言是什么?\",\n\t\t\"文字内容的主题是什么?\",\n\t}\n\taskQuestionsAboutImage(ctx, chatModel, textImageURL, ocrQuestions, \"你是一个 OCR 专家,擅长识别图像中的文字。\")\n\n\t// 场景 4:比较多张图片\n\tfmt.Println(\"\\n=== 场景 4: 多图比较 ===\")\n\tcompareImages(ctx, chatModel)\n}\n\n// askQuestionsAboutImage 针对单张图像提出多个问题\nfunc askQuestionsAboutImage(ctx context.Context, chatModel model.ChatModel, imageURL string, questions []string, systemPrompt string) {\n\tfor i, question := range questions {\n\t\tmessages := []*schema.Message{\n\t\t\tschema.SystemMessage(systemPrompt),\n\t\t\t{\n\t\t\t\tRole: schema.User,\n\t\t\t\tUserInputMultiContent: []schema.MessageInputPart{\n\t\t\t\t\tcommon.NewTextPart(question),\n\t\t\t\t\tcommon.NewImageURLPart(imageURL),\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\tresponse, err := chatModel.Generate(ctx, messages)\n\t\tif err != nil {\n\t\t\tlog.Printf(\"问题 %d 处理失败: %v\", i+1, err)\n\t\t\tcontinue\n\t\t}\n\n\t\tfmt.Printf(\"\\nQ%d: %s\\n\", i+1, question)\n\t\tfmt.Printf(\"A%d: %s\\n\", i+1, response.Content)\n\t}\n}\n\n// compareImages 比较多张图片的内容\nfunc compareImages(ctx context.Context, chatModel model.ChatModel) {\n\timage1URL := \"https://example.com/image1.jpg\"\n\timage2URL := \"https://example.com/image2.jpg\"\n\n\tmessages := []*schema.Message{\n\t\tschema.SystemMessage(\"你是一个图像比较专家,能够分析和比较多张图片的异同。\"),\n\t\t{\n\t\t\tRole: schema.User,\n\t\t\tUserInputMultiContent: []schema.MessageInputPart{\n\t\t\t\tcommon.NewTextPart(\"请比较这两张图片,说明它们的相似之处和不同之处:\"),\n\t\t\t\tcommon.NewImageURLPart(image1URL),\n\t\t\t\tcommon.NewImageURLPart(image2URL),\n\t\t\t},\n\t\t},\n\t}\n\n\tresponse, err := chatModel.Generate(ctx, messages)\n\tif err != nil {\n\t\tlog.Printf(\"图像比较失败: %v\", err)\n\t\treturn\n\t}\n\n\tfmt.Printf(\"\\n图像比较结果:\\n%s\\n\", response.Content)\n}\n\n// 使用本地图像文件的示例(需要转换为 Base64)\nfunc useLocalImage(ctx context.Context, chatModel model.ChatModel, imagePath string) {\n\t// 读取本地图像文件\n\timageData, err := os.ReadFile(imagePath)\n\tif err != nil {\n\t\tlog.Printf(\"读取图像文件失败: %v\", err)\n\t\treturn\n\t}\n\n\t// 将图像转换为 Base64 编码\n\t// 注意:实际使用时需要添加适当的 MIME 类型前缀\n\t// 例如:data:image/jpeg;base64,<base64_data>\n\tbase64Image := fmt.Sprintf(\"data:image/jpeg;base64,%s\", imageData)\n\n\tmessages := []*schema.Message{\n\t\tschema.SystemMessage(\"你是一个图像分析助手。\"),\n\t\t{\n\t\t\tRole: schema.User,\n\t\t\tUserInputMultiContent: []schema.MessageInputPart{\n\t\t\t\tcommon.NewTextPart(\"请描述这张图片。\"),\n\t\t\t\tcommon.NewImageURLPart(base64Image),\n\t\t\t},\n\t\t},\n\t}\n\n\tresponse, err := chatModel.Generate(ctx, messages)\n\tif err != nil {\n\t\tlog.Printf(\"处理本地图像失败: %v\", err)\n\t\treturn\n\t}\n\n\tfmt.Printf(\"本地图像分析结果:\\n%s\\n\", response.Content)\n}\n"}}, "lab.md": {"ec86072535f9835cf7765273291366d07bf9a0bc": {"t": 1785747212.0689375, "n": 5269, "h": "1ac7cdb2c1b23f85", "c": "# 实验环境配置指南\n\n## 实验环境\n\n本项目是基于 CloudWeGo Eino 框架的大语言模型应用开发教学项目,包含多个实验模块,涵盖从基础聊天到高级 RAG 应用的完整学习路径。\n\n### 开发环境\n\n#### 1. 访问项目地址\n\n- GitHub 仓库: [https://github.com/NuyoahCh/einotelos](https://github.com/NuyoahCh/einotelos)\n- CloudWeGo Eino 官方文档: [https://rcn3ahrrdvjj.feishu.cn/wiki/space/7582137522705140933](https://rcn3ahrrdvjj.feishu.cn/wiki/space/7582137522705140933)\n\n#### 2. 安装 Golang SDK\n\n- **版本要求**: Go 1.18+(推荐使用 Go 1.24.10)\n- **下载地址**: [https://go.dev/dl/](https://go.dev/dl/)\n- **安装验证**:\n ```bash\n go version\n # 输出示例: go version go1.24.10 darwin/amd64\n ```\n\n#### 3. 推荐使用 IDE\n\n- **Visual Studio Code** (推荐)\n - 安装 Go 扩展: `Go` by Google\n - 支持代码高亮、自动补全、调试等功能\n- **GoLand** by JetBrains\n- 其他支持 Go 的代码编辑器\n\n#### 4. 推荐使用 OS\n\n- **Linux** (推荐)\n- **macOS** \n- **Windows** (需安装 Git Bash 或 WSL)\n\n#### 5. Fork 代码仓库\n\n- 访问项目主页,点击右上角 Fork 按钮\n- 将项目克隆到自己的账户下,方便自由完成构建和实验\n\n#### 6. Clone 代码到本地(**位置务必为**)\n\n```bash\n# 克隆到指定路径\ncd $GOPATH/src/github.com/{your_username}/\ngit clone https://github.com/{your_username}/einotelos.git\ncd einotelos\n```\n\n如果没有设置 `$GOPATH`,可以使用默认路径:\n```bash\ncd ~/go/src/github.com/{your_username}/\n```\n\n#### 7. 安装项目依赖\n\n```bash\n# 下载并安装项目依赖包\ngo mod download\ngo mod tidy\n```\n\n#### 8. 配置环境变量\n\n本项目需要配置 API Key 才能正常运行。支持多种 LLM 服务商:\n\n##### DeepSeek(推荐入门使用)\n```bash\nexport DEEPSEEK_API_KEY=\"your_deepseek_api_key\"\n```\n- 注册地址: [https://platform.deepseek.com/](https://platform.deepseek.com/)\n- 新用户赠送免费额度\n\n##### 火山引擎 ARK(推荐使用)\n```bash\nexport ARK_API_KEY=\"your_ark_api_key\"\nexport ARK_MODEL_NAME=\"your_model_name\"\n```\n- 注册地址: 火山引擎控制台\n\n##### Ollama(本地部署,可选)\n```bash\n# 如果使用本地 Ollama 服务\n# 下载安装: https://ollama.ai/\nollama serve\n```\n\n**环境变量持久化(推荐)**\n\n为避免每次重启终端都要重新设置,建议将环境变量添加到配置文件:\n\n```bash\n# macOS/Linux (使用 zsh)\necho 'export DEEPSEEK_API_KEY=\"your_deepseek_api_key\"' >> ~/.zshrc\nsource ~/.zshrc\n\n# macOS/Linux (使用 bash)\necho 'export DEEPSEEK_API_KEY=\"your_deepseek_api_key\"' >> ~/.bashrc\nsource ~/.bashrc\n```\n\n---\n\n## 目录组织\n\n### 核心模块\n\n- **lab01/** - 聊天快速入门,演示最基础的对话功能\n- **lab02/** - 工作流与链式调用\n - `chain/` - 链式调用模式\n - `graph/` - 图式工作流\n - `workflow/` - 工作流编排\n- **lab03/** - 生成配置与错误处理\n - `generate/` - 单次、多次、流式生成\n - `callback/` - 回调函数配置\n - `error/` - 错误处理机制\n - `case/` - 翻译助手实战案例\n- **lab04/** - 提示词工程\n - `complex/` - 复杂逻辑提示词\n - `multi/` - 多类型消息\n - `multiplex/` - 模型多路复用\n - `replace/` - 变量替换\n- **lab05/** - 文档加载与解析\n - `loader/` - 文档加载器(本地、URL、S3)\n - `parser/` - 文档解析器(PDF、HTML、Text)\n - `transformer/` - 文档切分器(Markdown、递归、语义)\n - `extparser/` - 扩展解析器\n - `case/` - 文档处理案例\n- **lab06/** - 向量化与检索\n - `text/` - 文本向量化\n - `document/` - 文档向量化\n - `memory/` - 记忆向量化\n - `case/` - 文档问答案例\n- **lab07/** - Lambda 函数\n - `basic/` - 基础 Lambda 功能\n - `option/` - Lambda 配置选项\n - `stream/` - 流式 Lambda\n- **lab08/** - 索引器\n - `alone/` - 独立索引器\n - `arrange/` - 编排索引器\n- **lab09/** - 检索增强生成(RAG)\n - `retrieval_augment.go` - RAG 完整实现\n- **lab10/** - 工具调用\n - `interface/` - 计算器等接口工具\n - `weather/` - 天气查询工具\n - `db/` - 数据库工具\n - `file/` - 文件操作工具\n - `newtool/` - 自定义工具\n - `case/` - 工具集成案例\n\n### 辅助目录\n\n- **einox/** - 实战项目主入口(待实现)\n- **output/** - 各实验的输出结果和文档\n- **go.mod** - Go 模块依赖配置\n- **LICENSE** - 开源许可证\n- **README.md** - 项目说明文档\n\n---\n\n## 实验步骤\n\n### 1. 跟着设计文档已完成一遍\n\n建议按照以下顺序学习各个实验模块:\n\n1. **Lab 01 - 快速入门**: 理解基本的 ChatModel 使用方式\n2. **Lab 02 - 工作流**: 学习如何编排复杂的对话流程\n3. **Lab 03 - 生成配置**: 掌握不同的生成模式和错误处理\n4. **Lab 04 - 提示词工程**: 学习高效的提示词设计\n5. **Lab 05 - 文档处理**: 了解文档加载、解析、切分的完整流程\n6. **Lab 06 - 向量化**: 掌握向量嵌入和相似度检索\n7. **Lab 07 - Lambda**: 学习函数式编程在 LLM 中的应用\n8. **Lab 08 - 索引器**: 构建高效的文档索引系统\n9. **Lab 09 - RAG**: 实现完整的检索增强生成应用\n10. **Lab 10 - 工具调用**: 让 LLM 能够调用外部工具\n\n### 2. 运行实验代码\n\n每个实验都可以独立运行。以 Lab 01 为例:\n\n```bash\n# 进入项目根目录\ncd /path/to/einotelos\n\n# 运行 Lab 01 快速入门\ncd lab01\ngo run chat_quickstart.go\n```\n\n**常见运行方式:**\n\n```bash\n# 方式一:直接运行单个文件\ngo run lab01/chat_quickstart.go\n\n# 方式二:进入目录后运行\ncd lab01\ngo run chat_quickstart.go\n\n# 方式三:构建后运行(推荐生产环境)\ngo build -o bin/chat_quickstart lab01/chat_quickstart.go\n./bin/chat_quickstart\n```\n\n### 3. 修改参数进行调试\n\n在运行过程中,可以:\n\n- 修改提示词内容,观察输出变化\n- 调整模型参数(temperature、max_tokens 等)\n- 切换不同的模型进行对比测试\n- 添加日志输出,跟踪程序执行流程\n\n### 4. 最后自己完全手写实现一遍\n\n为了深入理解框架原理:\n\n- 不看原有代码,根据需求从零开始编写\n- 实现过程中遇到问题,查阅官方文档\n- 对比自己的实现与示例代码的差异\n- 思考不同实现方式的优劣\n\n### 5. 完成后查看 output 目录的文档\n\n每个实验的 `output/` 目录下都有对应的 Markdown 文档:\n\n- 包含实验的详细说明\n- 代码的关键知识点解析\n- 常见问题和解决方案\n- 进阶学习建议\n\n---\n\n## 常见问题排查\n\n### 依赖安装失败\n\n```bash\n# 清理缓存后重新安装\ngo clean -modcache\ngo mod download\n```\n\n### 运行时提示 API Key 错误\n\n```bash\n# 检查环境变量是否设置\necho $DEEPSEEK_API_KEY\n\n# 如果为空,重新设置\nexport DEEPSEEK_API_KEY=\"your_api_key\"\n```\n\n### 网络连接问题\n\n```bash\n# 配置 Go 模块代理(中国大陆用户推荐)\ngo env -w GOPROXY=https://goproxy.cn,direct\n```\n\n### 代码提示和补全不生效\n\n在 VS Code 中:\n1. 安装 Go 扩展\n2. 执行命令:`Go: Install/Update Tools`\n3. 重启 VS Code\n\n---\n\n## 进阶学习资源\n\n- **CloudWeGo Eino 官方文档**: [https://rcn3ahrrdvjj.feishu.cn/wiki/space/7582137522705140933](https://rcn3ahrrdvjj.feishu.cn/wiki/space/7582137522705140933)\n- **Go 语言官方文档**: [https://go.dev/doc/](https://go.dev/doc/)\n- **LangChain 概念参考**: [https://python.langchain.com/](https://python.langchain.com/)\n\n---\n\n## 贡献指南\n\n欢迎提交 Issue 和 Pull Request!\n\n1. Fork 本仓库\n2. 创建特性分支 (`git checkout -b feature/AmazingFeature`)\n3. 提交更改 (`git commit -m 'Add some AmazingFeature'`)\n4. 推送到分支 (`git push origin feature/AmazingFeature`)\n5. 开启 Pull Request\n\n---\n\n## 联系方式\n\n如有问题,欢迎通过以下方式联系:\n\n- 提交 GitHub Issue\n- 参考官方文档寻求帮助\n- 加入 CloudWeGo 社区讨论\n\n祝你学习愉快!🚀\n"}, "bc0d96e3b395a8d4a6b7ffd2945761b5225d6bcb": {"t": 1785754917.639428, "n": 5269, "h": "1ac7cdb2c1b23f85", "c": "# 实验环境配置指南\n\n## 实验环境\n\n本项目是基于 CloudWeGo Eino 框架的大语言模型应用开发教学项目,包含多个实验模块,涵盖从基础聊天到高级 RAG 应用的完整学习路径。\n\n### 开发环境\n\n#### 1. 访问项目地址\n\n- GitHub 仓库: [https://github.com/NuyoahCh/einotelos](https://github.com/NuyoahCh/einotelos)\n- CloudWeGo Eino 官方文档: [https://rcn3ahrrdvjj.feishu.cn/wiki/space/7582137522705140933](https://rcn3ahrrdvjj.feishu.cn/wiki/space/7582137522705140933)\n\n#### 2. 安装 Golang SDK\n\n- **版本要求**: Go 1.18+(推荐使用 Go 1.24.10)\n- **下载地址**: [https://go.dev/dl/](https://go.dev/dl/)\n- **安装验证**:\n ```bash\n go version\n # 输出示例: go version go1.24.10 darwin/amd64\n ```\n\n#### 3. 推荐使用 IDE\n\n- **Visual Studio Code** (推荐)\n - 安装 Go 扩展: `Go` by Google\n - 支持代码高亮、自动补全、调试等功能\n- **GoLand** by JetBrains\n- 其他支持 Go 的代码编辑器\n\n#### 4. 推荐使用 OS\n\n- **Linux** (推荐)\n- **macOS** \n- **Windows** (需安装 Git Bash 或 WSL)\n\n#### 5. Fork 代码仓库\n\n- 访问项目主页,点击右上角 Fork 按钮\n- 将项目克隆到自己的账户下,方便自由完成构建和实验\n\n#### 6. Clone 代码到本地(**位置务必为**)\n\n```bash\n# 克隆到指定路径\ncd $GOPATH/src/github.com/{your_username}/\ngit clone https://github.com/{your_username}/einotelos.git\ncd einotelos\n```\n\n如果没有设置 `$GOPATH`,可以使用默认路径:\n```bash\ncd ~/go/src/github.com/{your_username}/\n```\n\n#### 7. 安装项目依赖\n\n```bash\n# 下载并安装项目依赖包\ngo mod download\ngo mod tidy\n```\n\n#### 8. 配置环境变量\n\n本项目需要配置 API Key 才能正常运行。支持多种 LLM 服务商:\n\n##### DeepSeek(推荐入门使用)\n```bash\nexport DEEPSEEK_API_KEY=\"your_deepseek_api_key\"\n```\n- 注册地址: [https://platform.deepseek.com/](https://platform.deepseek.com/)\n- 新用户赠送免费额度\n\n##### 火山引擎 ARK(推荐使用)\n```bash\nexport ARK_API_KEY=\"your_ark_api_key\"\nexport ARK_MODEL_NAME=\"your_model_name\"\n```\n- 注册地址: 火山引擎控制台\n\n##### Ollama(本地部署,可选)\n```bash\n# 如果使用本地 Ollama 服务\n# 下载安装: https://ollama.ai/\nollama serve\n```\n\n**环境变量持久化(推荐)**\n\n为避免每次重启终端都要重新设置,建议将环境变量添加到配置文件:\n\n```bash\n# macOS/Linux (使用 zsh)\necho 'export DEEPSEEK_API_KEY=\"your_deepseek_api_key\"' >> ~/.zshrc\nsource ~/.zshrc\n\n# macOS/Linux (使用 bash)\necho 'export DEEPSEEK_API_KEY=\"your_deepseek_api_key\"' >> ~/.bashrc\nsource ~/.bashrc\n```\n\n---\n\n## 目录组织\n\n### 核心模块\n\n- **lab01/** - 聊天快速入门,演示最基础的对话功能\n- **lab02/** - 工作流与链式调用\n - `chain/` - 链式调用模式\n - `graph/` - 图式工作流\n - `workflow/` - 工作流编排\n- **lab03/** - 生成配置与错误处理\n - `generate/` - 单次、多次、流式生成\n - `callback/` - 回调函数配置\n - `error/` - 错误处理机制\n - `case/` - 翻译助手实战案例\n- **lab04/** - 提示词工程\n - `complex/` - 复杂逻辑提示词\n - `multi/` - 多类型消息\n - `multiplex/` - 模型多路复用\n - `replace/` - 变量替换\n- **lab05/** - 文档加载与解析\n - `loader/` - 文档加载器(本地、URL、S3)\n - `parser/` - 文档解析器(PDF、HTML、Text)\n - `transformer/` - 文档切分器(Markdown、递归、语义)\n - `extparser/` - 扩展解析器\n - `case/` - 文档处理案例\n- **lab06/** - 向量化与检索\n - `text/` - 文本向量化\n - `document/` - 文档向量化\n - `memory/` - 记忆向量化\n - `case/` - 文档问答案例\n- **lab07/** - Lambda 函数\n - `basic/` - 基础 Lambda 功能\n - `option/` - Lambda 配置选项\n - `stream/` - 流式 Lambda\n- **lab08/** - 索引器\n - `alone/` - 独立索引器\n - `arrange/` - 编排索引器\n- **lab09/** - 检索增强生成(RAG)\n - `retrieval_augment.go` - RAG 完整实现\n- **lab10/** - 工具调用\n - `interface/` - 计算器等接口工具\n - `weather/` - 天气查询工具\n - `db/` - 数据库工具\n - `file/` - 文件操作工具\n - `newtool/` - 自定义工具\n - `case/` - 工具集成案例\n\n### 辅助目录\n\n- **einox/** - 实战项目主入口(待实现)\n- **output/** - 各实验的输出结果和文档\n- **go.mod** - Go 模块依赖配置\n- **LICENSE** - 开源许可证\n- **README.md** - 项目说明文档\n\n---\n\n## 实验步骤\n\n### 1. 跟着设计文档已完成一遍\n\n建议按照以下顺序学习各个实验模块:\n\n1. **Lab 01 - 快速入门**: 理解基本的 ChatModel 使用方式\n2. **Lab 02 - 工作流**: 学习如何编排复杂的对话流程\n3. **Lab 03 - 生成配置**: 掌握不同的生成模式和错误处理\n4. **Lab 04 - 提示词工程**: 学习高效的提示词设计\n5. **Lab 05 - 文档处理**: 了解文档加载、解析、切分的完整流程\n6. **Lab 06 - 向量化**: 掌握向量嵌入和相似度检索\n7. **Lab 07 - Lambda**: 学习函数式编程在 LLM 中的应用\n8. **Lab 08 - 索引器**: 构建高效的文档索引系统\n9. **Lab 09 - RAG**: 实现完整的检索增强生成应用\n10. **Lab 10 - 工具调用**: 让 LLM 能够调用外部工具\n\n### 2. 运行实验代码\n\n每个实验都可以独立运行。以 Lab 01 为例:\n\n```bash\n# 进入项目根目录\ncd /path/to/einotelos\n\n# 运行 Lab 01 快速入门\ncd lab01\ngo run chat_quickstart.go\n```\n\n**常见运行方式:**\n\n```bash\n# 方式一:直接运行单个文件\ngo run lab01/chat_quickstart.go\n\n# 方式二:进入目录后运行\ncd lab01\ngo run chat_quickstart.go\n\n# 方式三:构建后运行(推荐生产环境)\ngo build -o bin/chat_quickstart lab01/chat_quickstart.go\n./bin/chat_quickstart\n```\n\n### 3. 修改参数进行调试\n\n在运行过程中,可以:\n\n- 修改提示词内容,观察输出变化\n- 调整模型参数(temperature、max_tokens 等)\n- 切换不同的模型进行对比测试\n- 添加日志输出,跟踪程序执行流程\n\n### 4. 最后自己完全手写实现一遍\n\n为了深入理解框架原理:\n\n- 不看原有代码,根据需求从零开始编写\n- 实现过程中遇到问题,查阅官方文档\n- 对比自己的实现与示例代码的差异\n- 思考不同实现方式的优劣\n\n### 5. 完成后查看 output 目录的文档\n\n每个实验的 `output/` 目录下都有对应的 Markdown 文档:\n\n- 包含实验的详细说明\n- 代码的关键知识点解析\n- 常见问题和解决方案\n- 进阶学习建议\n\n---\n\n## 常见问题排查\n\n### 依赖安装失败\n\n```bash\n# 清理缓存后重新安装\ngo clean -modcache\ngo mod download\n```\n\n### 运行时提示 API Key 错误\n\n```bash\n# 检查环境变量是否设置\necho $DEEPSEEK_API_KEY\n\n# 如果为空,重新设置\nexport DEEPSEEK_API_KEY=\"your_api_key\"\n```\n\n### 网络连接问题\n\n```bash\n# 配置 Go 模块代理(中国大陆用户推荐)\ngo env -w GOPROXY=https://goproxy.cn,direct\n```\n\n### 代码提示和补全不生效\n\n在 VS Code 中:\n1. 安装 Go 扩展\n2. 执行命令:`Go: Install/Update Tools`\n3. 重启 VS Code\n\n---\n\n## 进阶学习资源\n\n- **CloudWeGo Eino 官方文档**: [https://rcn3ahrrdvjj.feishu.cn/wiki/space/7582137522705140933](https://rcn3ahrrdvjj.feishu.cn/wiki/space/7582137522705140933)\n- **Go 语言官方文档**: [https://go.dev/doc/](https://go.dev/doc/)\n- **LangChain 概念参考**: [https://python.langchain.com/](https://python.langchain.com/)\n\n---\n\n## 贡献指南\n\n欢迎提交 Issue 和 Pull Request!\n\n1. Fork 本仓库\n2. 创建特性分支 (`git checkout -b feature/AmazingFeature`)\n3. 提交更改 (`git commit -m 'Add some AmazingFeature'`)\n4. 推送到分支 (`git push origin feature/AmazingFeature`)\n5. 开启 Pull Request\n\n---\n\n## 联系方式\n\n如有问题,欢迎通过以下方式联系:\n\n- 提交 GitHub Issue\n- 参考官方文档寻求帮助\n- 加入 CloudWeGo 社区讨论\n\n祝你学习愉快!🚀\n"}}, "lab11/multimodal/multimodal_chat.go": {"ec86072535f9835cf7765273291366d07bf9a0bc": {"t": 1786068293.4012809, "n": 5872, "h": "ed5299abb1b871c9", "c": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\n\t\"github.com/NuyoahCh/einotelos/lab11/common\"\n\t\"github.com/cloudwego/eino-ext/components/model/deepseek\"\n\t\"github.com/cloudwego/eino/components/model\"\n\t\"github.com/cloudwego/eino/schema\"\n)\n\n// MultimodalChat 演示图文混合的多模态对话\n// 在一次对话中可以同时包含文本、图像等多种类型的内容\nfunc main() {\n\tctx := context.Background()\n\n\t// 创建 ChatModel 实例\n\tchatModel, err := deepseek.NewChatModel(ctx, &deepseek.ChatModelConfig{\n\t\tAPIKey: os.Getenv(\"DEEPSEEK_API_KEY\"),\n\t\tModel: \"deepseek-chat\",\n\t\tBaseURL: \"https://api.deepseek.com\",\n\t})\n\tif err != nil {\n\t\tlog.Fatalf(\"创建 ChatModel 实例失败: %v\", err)\n\t}\n\n\t// 示例 1:图文结合的产品咨询\n\tfmt.Println(\"=== 示例 1: 图文结合的产品咨询 ===\")\n\tproductConsultation(ctx, chatModel)\n\n\t// 示例 2:多图文档分析\n\tfmt.Println(\"\\n=== 示例 2: 多图文档分析 ===\")\n\tdocumentAnalysis(ctx, chatModel)\n\n\t// 示例 3:图像编辑建议\n\tfmt.Println(\"\\n=== 示例 3: 图像编辑建议 ===\")\n\timageEditingSuggestions(ctx, chatModel)\n\n\t// 示例 4:复杂的多模态工作流\n\tfmt.Println(\"\\n=== 示例 4: 复杂的多模态工作流 ===\")\n\tcomplexMultimodalWorkflow(ctx, chatModel)\n}\n\n// productConsultation 产品咨询场景:用户上传产品图片并咨询相关问题\nfunc productConsultation(ctx context.Context, chatModel model.ChatModel) {\n\tproductImageURL := \"https://example.com/laptop.jpg\"\n\n\t// 构建多模态消息\n\tmessages := []*schema.Message{\n\t\tschema.SystemMessage(\"你是一个专业的产品顾问,能够根据产品图片提供详细的咨询服务。\"),\n\t\t{\n\t\t\tRole: schema.User,\n\t\t\tUserInputMultiContent: []schema.MessageInputPart{\n\t\t\t\tcommon.NewTextPart(\"我想了解这款笔记本电脑:\"),\n\t\t\t\tcommon.NewImageURLPart(productImageURL),\n\t\t\t\tcommon.NewTextPart(\"请告诉我:\\n1. 这是什么品牌和型号?\\n2. 主要配置如何?\\n3. 适合什么用途?\\n4. 大概的价格区间?\"),\n\t\t\t},\n\t\t},\n\t}\n\n\tresponse, err := chatModel.Generate(ctx, messages)\n\tif err != nil {\n\t\tlog.Printf(\"产品咨询失败: %v\", err)\n\t\treturn\n\t}\n\n\tfmt.Printf(\"产品顾问回复:\\n%s\\n\", response.Content)\n\n\t// 继续追问\n\tmessages = append(messages, response)\n\tmessages = append(messages, schema.UserMessage(\"这款笔记本和 MacBook Pro 相比有什么优缺点?\"))\n\n\tresponse2, err := chatModel.Generate(ctx, messages)\n\tif err != nil {\n\t\tlog.Printf(\"追问失败: %v\", err)\n\t\treturn\n\t}\n\n\tfmt.Printf(\"\\n追问回复:\\n%s\\n\", response2.Content)\n}\n\n// documentAnalysis 文档分析场景:分析包含多张图片的文档\nfunc documentAnalysis(ctx context.Context, chatModel model.ChatModel) {\n\tpage1URL := \"https://example.com/doc-page1.jpg\"\n\tpage2URL := \"https://example.com/doc-page2.jpg\"\n\tpage3URL := \"https://example.com/doc-page3.jpg\"\n\n\tmessages := []*schema.Message{\n\t\tschema.SystemMessage(\"你是一个文档分析专家,能够理解和总结多页文档的内容。\"),\n\t\t{\n\t\t\tRole: schema.User,\n\t\t\tUserInputMultiContent: []schema.MessageInputPart{\n\t\t\t\tcommon.NewTextPart(\"这是一份技术文档的三页内容,请帮我总结主要内容:\"),\n\t\t\t\tcommon.NewImageURLPart(page1URL),\n\t\t\t\tcommon.NewImageURLPart(page2URL),\n\t\t\t\tcommon.NewImageURLPart(page3URL),\n\t\t\t\tcommon.NewTextPart(\"请提供:\\n1. 文档的主题\\n2. 关键技术点\\n3. 重要的数据或图表\\n4. 结论或建议\"),\n\t\t\t},\n\t\t},\n\t}\n\n\tresponse, err := chatModel.Generate(ctx, messages)\n\tif err != nil {\n\t\tlog.Printf(\"文档分析失败: %v\", err)\n\t\treturn\n\t}\n\n\tfmt.Printf(\"文档分析结果:\\n%s\\n\", response.Content)\n}\n\n// imageEditingSuggestions 图像编辑建议场景\nfunc imageEditingSuggestions(ctx context.Context, chatModel model.ChatModel) {\n\tphotoURL := \"https://example.com/photo.jpg\"\n\n\tmessages := []*schema.Message{\n\t\tschema.SystemMessage(\"你是一个专业的摄影师和图像编辑专家。\"),\n\t\t{\n\t\t\tRole: schema.User,\n\t\t\tUserInputMultiContent: []schema.MessageInputPart{\n\t\t\t\tcommon.NewTextPart(\"我拍了这张照片,但感觉不够理想:\"),\n\t\t\t\tcommon.NewImageURLPart(photoURL),\n\t\t\t\tcommon.NewTextPart(\"请给我一些改进建议:\\n1. 构图方面\\n2. 光线和色彩\\n3. 后期处理建议\\n4. 如何让照片更有吸引力\"),\n\t\t\t},\n\t\t},\n\t}\n\n\tresponse, err := chatModel.Generate(ctx, messages)\n\tif err != nil {\n\t\tlog.Printf(\"获取编辑建议失败: %v\", err)\n\t\treturn\n\t}\n\n\tfmt.Printf(\"图像编辑建议:\\n%s\\n\", response.Content)\n}\n\n// complexMultimodalWorkflow 复杂的多模态工作流示例\nfunc complexMultimodalWorkflow(ctx context.Context, chatModel model.ChatModel) {\n\t// 场景:设计评审工作流\n\t// 1. 上传设计稿\n\t// 2. AI 分析设计\n\t// 3. 提出修改建议\n\t// 4. 上传修改后的版本\n\t// 5. 对比分析\n\n\tdesignV1URL := \"https://example.com/design-v1.jpg\"\n\tdesignV2URL := \"https://example.com/design-v2.jpg\"\n\n\t// 第一步:初始设计评审\n\tmessages := []*schema.Message{\n\t\tschema.SystemMessage(\"你是一个资深的 UI/UX 设计师,擅长评审和改进设计。\"),\n\t\t{\n\t\t\tRole: schema.User,\n\t\t\tUserInputMultiContent: []schema.MessageInputPart{\n\t\t\t\tcommon.NewTextPart(\"这是我们 App 的首页设计初稿:\"),\n\t\t\t\tcommon.NewImageURLPart(designV1URL),\n\t\t\t\tcommon.NewTextPart(\"请从以下角度评审:\\n1. 视觉层次\\n2. 用户体验\\n3. 色彩搭配\\n4. 可访问性\"),\n\t\t\t},\n\t\t},\n\t}\n\n\tresponse1, err := chatModel.Generate(ctx, messages)\n\tif err != nil {\n\t\tlog.Printf(\"设计评审失败: %v\", err)\n\t\treturn\n\t}\n\n\tfmt.Printf(\"初始设计评审:\\n%s\\n\", response1.Content)\n\n\t// 第二步:根据反馈修改后的版本对比\n\tmessages = append(messages, response1)\n\tmessages = append(messages, &schema.Message{\n\t\tRole: schema.User,\n\t\tUserInputMultiContent: []schema.MessageInputPart{\n\t\t\tcommon.NewTextPart(\"根据你的建议,我做了修改。这是新版本:\"),\n\t\t\tcommon.NewImageURLPart(designV2URL),\n\t\t\tcommon.NewTextPart(\"请对比两个版本,说明改进之处和还需要优化的地方。\"),\n\t\t},\n\t})\n\n\tresponse2, err := chatModel.Generate(ctx, messages)\n\tif err != nil {\n\t\tlog.Printf(\"版本对比失败: %v\", err)\n\t\treturn\n\t}\n\n\tfmt.Printf(\"\\n版本对比分析:\\n%s\\n\", response2.Content)\n\n\t// 第三步:最终建议\n\tmessages = append(messages, response2)\n\tmessages = append(messages, schema.UserMessage(\"基于当前版本,请给出最终的优化建议和实施优先级。\"))\n\n\tresponse3, err := chatModel.Generate(ctx, messages)\n\tif err != nil {\n\t\tlog.Printf(\"获取最终建议失败: %v\", err)\n\t\treturn\n\t}\n\n\tfmt.Printf(\"\\n最终优化建议:\\n%s\\n\", response3.Content)\n}\n\n// streamingMultimodalChat 流式多模态对话示例\nfunc streamingMultimodalChat(ctx context.Context, chatModel model.ChatModel) {\n\timageURL := \"https://example.com/complex-scene.jpg\"\n\n\tmessages := []*schema.Message{\n\t\tschema.SystemMessage(\"你是一个详细的场景描述专家。\"),\n\t\t{\n\t\t\tRole: schema.User,\n\t\t\tUserInputMultiContent: []schema.MessageInputPart{\n\t\t\t\tcommon.NewTextPart(\"请详细描述这个场景,包括所有细节:\"),\n\t\t\t\tcommon.NewImageURLPart(imageURL),\n\t\t\t},\n\t\t},\n\t}\n\n\t// 使用流式生成获取实时响应\n\tfmt.Println(\"开始流式生成响应...\")\n\n\tstream, err := chatModel.Stream(ctx, messages)\n\tif err != nil {\n\t\tlog.Printf(\"启动流式生成失败: %v\", err)\n\t\treturn\n\t}\n\tdefer stream.Close()\n\n\t// 逐块接收和显示响应\n\tfor {\n\t\tchunk, err := stream.Recv()\n\t\tif err != nil {\n\t\t\tbreak\n\t\t}\n\t\tfmt.Print(chunk.Content)\n\t}\n\tfmt.Println()\n}\n"}}, "lab06/case/docs_question.go": {"ec86072535f9835cf7765273291366d07bf9a0bc": {"t": 1786068296.6831746, "n": 4282, "h": "1e5de35d8d663d85", "c": "package main\n\nimport (\n\t\"bufio\"\n\t\"context\"\n\t\"fmt\"\n\t\"log\"\n\t\"math\"\n\t\"os\"\n\t\"sort\"\n\t\"strings\"\n\n\t\"github.com/cloudwego/eino-ext/components/embedding/ark\"\n\t\"github.com/cloudwego/eino-ext/components/model/deepseek\"\n\t\"github.com/cloudwego/eino/schema\"\n)\n\n// DocumentQA 文档问答系统\ntype DocumentQA struct {\n\tembedder *ark.Embedder\n\tchatModel *deepseek.ChatModel\n\tdocuments []*schema.Document\n\tvectors [][]float64\n}\n\nfunc NewDocumentQA(arkAPIKey, arkModel, deepseekAPIKey string) (*DocumentQA, error) {\n\tctx := context.Background()\n\n\t// 创建 ARK Embedding\n\tembedder, err := ark.NewEmbedder(ctx, &ark.EmbeddingConfig{\n\t\tAPIKey: arkAPIKey,\n\t\tModel: arkModel,\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// 创建 DeepSeek ChatModel\n\tchatModel, err := deepseek.NewChatModel(ctx, &deepseek.ChatModelConfig{\n\t\tAPIKey: deepseekAPIKey,\n\t\tModel: \"deepseek-chat\",\n\t\tBaseURL: \"https://api.deepseek.com\",\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &DocumentQA{\n\t\tembedder: embedder,\n\t\tchatModel: chatModel,\n\t}, nil\n}\n\n// LoadDocuments 加载文档\nfunc (qa *DocumentQA) LoadDocuments(ctx context.Context, docs []*schema.Document) error {\n\tfmt.Printf(\"正在加载 %d 个文档...\\\\n\", len(docs))\n\n\t// 提取文本内容\n\ttexts := make([]string, len(docs))\n\tfor i, doc := range docs {\n\t\ttexts[i] = doc.Content\n\t}\n\n\t// 向量化\n\tvectors, err := qa.embedder.EmbedStrings(ctx, texts)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tqa.documents = docs\n\tqa.vectors = vectors\n\n\tfmt.Printf(\"成功加载并向量化 %d 个文档\\\\n\", len(docs))\n\treturn nil\n}\n\n// Query 查询\nfunc (qa *DocumentQA) Query(ctx context.Context, question string) (string, error) {\n\t// 1. 向量化问题\n\tquestionVectors, err := qa.embedder.EmbedStrings(ctx, []string{question})\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tquestionVector := questionVectors[0]\n\n\t// 2. 计算相似度,找到最相关的文档\n\ttype docScore struct {\n\t\tdoc *schema.Document\n\t\tscore float64\n\t}\n\n\tscores := make([]docScore, len(qa.documents))\n\tfor i := range qa.documents {\n\t\tsimilarity := cosineSimilarity(questionVector, qa.vectors[i])\n\t\tscores[i] = docScore{\n\t\t\tdoc: qa.documents[i],\n\t\t\tscore: similarity,\n\t\t}\n\t}\n\n\t// 3. 排序,取 Top 3\n\tsort.Slice(scores, func(i, j int) bool {\n\t\treturn scores[i].score > scores[j].score\n\t})\n\n\ttopDocs := make([]*schema.Document, 0, 3)\n\tfor i := 0; i < 3 && i < len(scores); i++ {\n\t\tif scores[i].score > 0.7 { // 相似度阈值\n\t\t\ttopDocs = append(topDocs, scores[i].doc)\n\t\t}\n\t}\n\n\tif len(topDocs) == 0 {\n\t\treturn \"抱歉,我在文档中找不到相关信息。\", nil\n\t}\n\n\t// 4. 构建上下文\n\tcontext := \"相关文档内容:\\\\n\\\\n\"\n\tfor i, doc := range topDocs {\n\t\tcontext += fmt.Sprintf(\"%d. %s\\\\n\\\\n\", i+1, doc.Content)\n\t}\n\n\t// 5. 生成回答\n\tmessages := []*schema.Message{\n\t\tschema.SystemMessage(fmt.Sprintf(`你是一个专业的文档问答助手。\n请根据以下文档内容回答用户问题。如果文档中没有相关信息,请如实告知。\n\n%s`, context)),\n\t\tschema.UserMessage(question),\n\t}\n\n\tresponse, err := qa.chatModel.Generate(ctx, messages)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\treturn response.Content, nil\n}\n\nfunc main() {\n\t// 创建问答系统\n\tqa, err := NewDocumentQA(\n\t\tos.Getenv(\"ARK_API_KEY\"),\n\t\tos.Getenv(\"ARK_EMBEDDING_MODEL\"),\n\t\tos.Getenv(\"DEEPSEEK_API_KEY\"),\n\t)\n\tif err != nil {\n\t\tlog.Fatalf(\"创建问答系统失败: %v\", err)\n\t}\n\n\t// 加载知识库\n\tdocs := []*schema.Document{\n\t\t{Content: \"Eino 是基于 Go 语言的 AI 应用开发框架,由字节跳动开源。\"},\n\t\t{Content: \"Eino 提供了 ChatModel、Embedding、Retriever 等丰富的组件。\"},\n\t\t{Content: \"Eino 支持 Chain 和 Graph 两种编排方式,可以灵活组合组件。\"},\n\t\t{Content: \"React Agent 是 Eino 中的智能代理,能够自主调用工具完成任务。\"},\n\t\t{Content: \"Eino 支持多种大模型,包括 OpenAI、ARK、Ollama 等。\"},\n\t}\n\n\tctx := context.Background()\n\tif err := qa.LoadDocuments(ctx, docs); err != nil {\n\t\tlog.Fatalf(\"加载文档失败: %v\", err)\n\t}\n\n\t// 交互式问答\n\tscanner := bufio.NewScanner(os.Stdin)\n\tfmt.Println(\"\\\\n=== 文档问答系统(输入 'exit' 退出)===\")\n\n\tfor {\n\t\tfmt.Print(\"问题: \")\n\t\tif !scanner.Scan() {\n\t\t\tbreak\n\t\t}\n\n\t\tquestion := strings.TrimSpace(scanner.Text())\n\t\tif question == \"exit\" {\n\t\t\tfmt.Println(\"再见!\")\n\t\t\tbreak\n\t\t}\n\n\t\tif question == \"\" {\n\t\t\tcontinue\n\t\t}\n\n\t\tanswer, err := qa.Query(ctx, question)\n\t\tif err != nil {\n\t\t\tfmt.Printf(\"查询失败: %v\\\\n\", err)\n\t\t\tcontinue\n\t\t}\n\n\t\tfmt.Printf(\"\\\\n回答: %s\\\\n\\\\n\", answer)\n\t}\n}\n\nfunc cosineSimilarity(a, b []float64) float64 {\n\t// 实现同前面的示例\n\tif len(a) != len(b) {\n\t\treturn 0\n\t}\n\n\tvar dotProduct, normA, normB float64\n\tfor i := range a {\n\t\tdotProduct += a[i] * b[i]\n\t\tnormA += a[i] * a[i]\n\t\tnormB += b[i] * b[i]\n\t}\n\n\tif normA == 0 || normB == 0 {\n\t\treturn 0\n\t}\n\n\treturn dotProduct / (math.Sqrt(normA) * math.Sqrt(normB))\n}\n"}}}} |