Files
hack/tools/scripts/llm-key-hunter/results/_cache/content/celanwang_einotelos.json
T
OpenCode 68a9071cf1 feat: reconcile local master with origin (exa/hot-platform hunters, leaks ledger, latest results)
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.
2026-08-07 12:53:47 +08:00

1 line
135 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{"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"}}}}