1 line
23 KiB
JSON
1 line
23 KiB
JSON
{"repo": "bigwhite/experiments", "files": {"mcp-go-sdk/agent/main.go": {"e3a40e4b0ecbf5d6f148d949363617d6f430110f": {"t": 1785745615.1103988, "n": 6575, "h": "8186b0dcbe5f5c7d", "c": "package main\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\t\"os/exec\"\n\t\"strings\"\n\n\t\"github.com/modelcontextprotocol/go-sdk/mcp\"\n\t\"github.com/openai/openai-go\"\n\t\"github.com/openai/openai-go/option\"\n)\n\n// serverConfig 结构体用于管理不同 MCP 服务的连接信息\ntype serverConfig struct {\n\tServerCmd string // 用于 stdio 服务\n\tHTTPAddr string // 用于 http 服务\n}\n\n// toolRegistry 映射对 LLM 友好的工具别名到其服务配置\nvar toolRegistry = map[string]serverConfig{\n\t\"greet\": {ServerCmd: \"go run ../greeter/main.go\"},\n\t\"add\": {HTTPAddr: \"http://localhost:8080/math\"},\n\t\"list_files\": {ServerCmd: \"go run ../fileserver/main.go\"},\n\t\"read_file\": {ServerCmd: \"go run ../fileserver/main.go\"},\n}\n\n// invokeMCPTool 是 Agent 的核心函数,负责直接与 MCP 服务通信\nfunc invokeMCPTool(toolAlias string, arguments map[string]interface{}) (string, error) {\n\tconfig, ok := toolRegistry[toolAlias]\n\tif !ok {\n\t\treturn \"\", fmt.Errorf(\"unknown tool alias: %s\", toolAlias)\n\t}\n\n\t// 1. 将 LLM 友好的别名和参数,转换为真正的 MCP 请求\n\tmcpToolName := toolAlias\n\tmcpArguments := arguments\n\tif toolAlias == \"list_files\" {\n\t\tmcpToolName = \"resources/read\"\n\t\tmcpArguments = map[string]interface{}{\"uri\": \"mcp://fs/list\"}\n\t} else if toolAlias == \"read_file\" {\n\t\tmcpToolName = \"resources/read\"\n\t\tif filename, ok := arguments[\"filename\"].(string); ok {\n\t\t\tmcpArguments = map[string]interface{}{\"uri\": \"file:///\" + filename}\n\t\t} else {\n\t\t\treturn \"\", fmt.Errorf(\"tool 'read_file' requires a 'filename' argument\")\n\t\t}\n\t}\n\n\t// 2. 创建 MCP 客户端实例\n\tclient := mcp.NewClient(\"go-agent\", \"1.0\", nil)\n\n\t// 3. 根据配置选择并创建 Transport\n\tvar transport mcp.Transport\n\tif config.ServerCmd != \"\" {\n\t\tcmdParts := strings.Fields(config.ServerCmd)\n\t\ttransport = mcp.NewCommandTransport(exec.Command(cmdParts[0], cmdParts[1:]...))\n\t} else {\n\t\ttransport = mcp.NewStreamableClientTransport(config.HTTPAddr, nil)\n\t}\n\n\t// 4. 授权客户端访问本地文件系统(仅对文件服务调用有效)\n\tclient.AddRoots(&mcp.Root{URI: \"file://./\"})\n\n\t// 5. 连接到服务器,建立会话\n\tctx := context.Background()\n\tsession, err := client.Connect(ctx, transport)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to connect to MCP server for tool %s: %w\", toolAlias, err)\n\t}\n\tdefer session.Close() // 每次调用都是一个独立的会话,确保关闭\n\n\t// 6. 执行调用并处理结果\n\tvar resultText string\n\tif mcpToolName == \"resources/read\" {\n\t\tres, err := session.ReadResource(ctx, &mcp.ReadResourceParams{\n\t\t\tURI: mcpArguments[\"uri\"].(string),\n\t\t})\n\t\tif err != nil {\n\t\t\treturn \"\", fmt.Errorf(\"ReadResource failed: %w\", err)\n\t\t}\n\t\tvar sb strings.Builder\n\t\tfor _, c := range res.Contents {\n\t\t\tsb.WriteString(c.Text)\n\t\t}\n\t\tresultText = sb.String()\n\t} else {\n\t\tres, err := session.CallTool(ctx, &mcp.CallToolParams{\n\t\t\tName: mcpToolName,\n\t\t\tArguments: mcpArguments,\n\t\t})\n\t\tif err != nil {\n\t\t\treturn \"\", fmt.Errorf(\"CallTool failed: %w\", err)\n\t\t}\n\t\tif res.IsError {\n\t\t\treturn \"\", fmt.Errorf(\"tool execution failed: %s\", res.Content[0].(*mcp.TextContent).Text)\n\t\t}\n\t\tresultText = res.Content[0].(*mcp.TextContent).Text\n\t}\n\n\treturn resultText, nil\n}\n\nfunc main() {\n\tapiKey := os.Getenv(\"DEEPSEEK_API_KEY\")\n\tif apiKey == \"\" {\n\t\tlog.Fatal(\"DEEPSEEK_API_KEY environment variable not set.\")\n\t}\n\n\tclient := openai.NewClient(\n\t\toption.WithAPIKey(apiKey),\n\t\toption.WithBaseURL(\"https://api.deepseek.com/v1\"),\n\t)\n\n\t// 为所有工具使用合法的名称,特别是为 `resources/read` 创建别名\n\ttools := []openai.ChatCompletionToolParam{\n\t\t{\n\t\t\tFunction: openai.FunctionDefinitionParam{\n\t\t\t\tName: \"greet\",\n\t\t\t\tDescription: openai.String(\"Say hi to someone.\"),\n\t\t\t\tParameters: openai.FunctionParameters{\n\t\t\t\t\t\"type\": \"object\", \"properties\": map[string]interface{}{\"name\": map[string]string{\"type\": \"string\", \"description\": \"Name of the person to greet\"}}, \"required\": []string{\"name\"},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tFunction: openai.FunctionDefinitionParam{\n\t\t\t\tName: \"add\",\n\t\t\t\tDescription: openai.String(\"Add two integers.\"),\n\t\t\t\tParameters: openai.FunctionParameters{\n\t\t\t\t\t\"type\": \"object\", \"properties\": map[string]interface{}{\"A\": map[string]string{\"type\": \"integer\"}, \"B\": map[string]string{\"type\": \"integer\"}}, \"required\": []string{\"A\", \"B\"},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tFunction: openai.FunctionDefinitionParam{\n\t\t\t\tName: \"list_files\",\n\t\t\t\tDescription: openai.String(\"List all non-directory files in the current project directory.\"),\n\t\t\t\tParameters: openai.FunctionParameters{\"type\": \"object\", \"properties\": map[string]interface{}{}},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tFunction: openai.FunctionDefinitionParam{\n\t\t\t\tName: \"read_file\",\n\t\t\t\tDescription: openai.String(\"Read the content of a specific file.\"),\n\t\t\t\tParameters: openai.FunctionParameters{\n\t\t\t\t\t\"type\": \"object\", \"properties\": map[string]interface{}{\"filename\": map[string]string{\"type\": \"string\", \"description\": \"The name of the file to read.\"}}, \"required\": []string{\"filename\"},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\tmessages := []openai.ChatCompletionMessageParamUnion{\n\t\topenai.SystemMessage(\"You are a helpful assistant with access to local tools. You must call tools by using the tool_calls response format. Don't make assumptions about what values to plug into functions. Ask for clarification if a user request is ambiguous.\"),\n\t\topenai.UserMessage(\"Hi, can you greet my friend Alex, add 5 and 7, and then list the files in my project?\"),\n\t}\n\n\tctx := context.Background()\n\n\tfor i := 0; i < 5; i++ {\n\t\tlog.Println(\"--- Sending request to DeepSeek ---\")\n\n\t\tresp, err := client.Chat.Completions.New(ctx, openai.ChatCompletionNewParams{Model: \"deepseek-chat\", Messages: messages, Tools: tools})\n\t\tif err != nil {\n\t\t\tlog.Fatalf(\"ChatCompletion error: %v\\n\", err)\n\t\t}\n\t\tif len(resp.Choices) == 0 {\n\t\t\tlog.Fatal(\"No choices returned from API\")\n\t\t}\n\n\t\tmsg := resp.Choices[0].Message\n\t\tmessages = append(messages, msg.ToParam())\n\n\t\tif msg.ToolCalls != nil {\n\t\t\tfor _, toolCall := range msg.ToolCalls {\n\t\t\t\tfunctionName := toolCall.Function.Name\n\t\t\t\tvar arguments map[string]interface{}\n\t\t\t\tif err := json.Unmarshal([]byte(toolCall.Function.Arguments), &arguments); err != nil {\n\t\t\t\t\tlog.Fatalf(\"Failed to unmarshal function arguments: %v\", err)\n\t\t\t\t}\n\n\t\t\t\tlog.Printf(\"--- LLM wants to call tool: %s with args: %v ---\\n\", functionName, arguments)\n\n\t\t\t\t// 直接调用我们的 Go 函数,该函数内建了 MCP 客户端逻辑\n\t\t\t\ttoolResult, err := invokeMCPTool(functionName, arguments)\n\t\t\t\tif err != nil {\n\t\t\t\t\tlog.Printf(\"Tool call failed: %v\\n\", err)\n\t\t\t\t\ttoolResult = fmt.Sprintf(\"Error executing tool: %v\", err)\n\t\t\t\t}\n\n\t\t\t\tlog.Printf(\"--- Tool result: ---\\n%s\\n---------------------\\n\", toolResult)\n\n\t\t\t\tmessages = append(messages, openai.ToolMessage(toolResult, toolCall.ID))\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\n\t\tlog.Println(\"--- Final response from LLM ---\")\n\t\tlog.Println(msg.Content)\n\t\treturn\n\t}\n\tlog.Println(\"Reached max conversation turns.\")\n}\n"}, "f8477cc0e4cde2b2ff81c9b7c3c12616f31ed4d3": {"t": 1785754864.9806855, "n": 6575, "h": "8186b0dcbe5f5c7d", "c": "package main\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\t\"os/exec\"\n\t\"strings\"\n\n\t\"github.com/modelcontextprotocol/go-sdk/mcp\"\n\t\"github.com/openai/openai-go\"\n\t\"github.com/openai/openai-go/option\"\n)\n\n// serverConfig 结构体用于管理不同 MCP 服务的连接信息\ntype serverConfig struct {\n\tServerCmd string // 用于 stdio 服务\n\tHTTPAddr string // 用于 http 服务\n}\n\n// toolRegistry 映射对 LLM 友好的工具别名到其服务配置\nvar toolRegistry = map[string]serverConfig{\n\t\"greet\": {ServerCmd: \"go run ../greeter/main.go\"},\n\t\"add\": {HTTPAddr: \"http://localhost:8080/math\"},\n\t\"list_files\": {ServerCmd: \"go run ../fileserver/main.go\"},\n\t\"read_file\": {ServerCmd: \"go run ../fileserver/main.go\"},\n}\n\n// invokeMCPTool 是 Agent 的核心函数,负责直接与 MCP 服务通信\nfunc invokeMCPTool(toolAlias string, arguments map[string]interface{}) (string, error) {\n\tconfig, ok := toolRegistry[toolAlias]\n\tif !ok {\n\t\treturn \"\", fmt.Errorf(\"unknown tool alias: %s\", toolAlias)\n\t}\n\n\t// 1. 将 LLM 友好的别名和参数,转换为真正的 MCP 请求\n\tmcpToolName := toolAlias\n\tmcpArguments := arguments\n\tif toolAlias == \"list_files\" {\n\t\tmcpToolName = \"resources/read\"\n\t\tmcpArguments = map[string]interface{}{\"uri\": \"mcp://fs/list\"}\n\t} else if toolAlias == \"read_file\" {\n\t\tmcpToolName = \"resources/read\"\n\t\tif filename, ok := arguments[\"filename\"].(string); ok {\n\t\t\tmcpArguments = map[string]interface{}{\"uri\": \"file:///\" + filename}\n\t\t} else {\n\t\t\treturn \"\", fmt.Errorf(\"tool 'read_file' requires a 'filename' argument\")\n\t\t}\n\t}\n\n\t// 2. 创建 MCP 客户端实例\n\tclient := mcp.NewClient(\"go-agent\", \"1.0\", nil)\n\n\t// 3. 根据配置选择并创建 Transport\n\tvar transport mcp.Transport\n\tif config.ServerCmd != \"\" {\n\t\tcmdParts := strings.Fields(config.ServerCmd)\n\t\ttransport = mcp.NewCommandTransport(exec.Command(cmdParts[0], cmdParts[1:]...))\n\t} else {\n\t\ttransport = mcp.NewStreamableClientTransport(config.HTTPAddr, nil)\n\t}\n\n\t// 4. 授权客户端访问本地文件系统(仅对文件服务调用有效)\n\tclient.AddRoots(&mcp.Root{URI: \"file://./\"})\n\n\t// 5. 连接到服务器,建立会话\n\tctx := context.Background()\n\tsession, err := client.Connect(ctx, transport)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to connect to MCP server for tool %s: %w\", toolAlias, err)\n\t}\n\tdefer session.Close() // 每次调用都是一个独立的会话,确保关闭\n\n\t// 6. 执行调用并处理结果\n\tvar resultText string\n\tif mcpToolName == \"resources/read\" {\n\t\tres, err := session.ReadResource(ctx, &mcp.ReadResourceParams{\n\t\t\tURI: mcpArguments[\"uri\"].(string),\n\t\t})\n\t\tif err != nil {\n\t\t\treturn \"\", fmt.Errorf(\"ReadResource failed: %w\", err)\n\t\t}\n\t\tvar sb strings.Builder\n\t\tfor _, c := range res.Contents {\n\t\t\tsb.WriteString(c.Text)\n\t\t}\n\t\tresultText = sb.String()\n\t} else {\n\t\tres, err := session.CallTool(ctx, &mcp.CallToolParams{\n\t\t\tName: mcpToolName,\n\t\t\tArguments: mcpArguments,\n\t\t})\n\t\tif err != nil {\n\t\t\treturn \"\", fmt.Errorf(\"CallTool failed: %w\", err)\n\t\t}\n\t\tif res.IsError {\n\t\t\treturn \"\", fmt.Errorf(\"tool execution failed: %s\", res.Content[0].(*mcp.TextContent).Text)\n\t\t}\n\t\tresultText = res.Content[0].(*mcp.TextContent).Text\n\t}\n\n\treturn resultText, nil\n}\n\nfunc main() {\n\tapiKey := os.Getenv(\"DEEPSEEK_API_KEY\")\n\tif apiKey == \"\" {\n\t\tlog.Fatal(\"DEEPSEEK_API_KEY environment variable not set.\")\n\t}\n\n\tclient := openai.NewClient(\n\t\toption.WithAPIKey(apiKey),\n\t\toption.WithBaseURL(\"https://api.deepseek.com/v1\"),\n\t)\n\n\t// 为所有工具使用合法的名称,特别是为 `resources/read` 创建别名\n\ttools := []openai.ChatCompletionToolParam{\n\t\t{\n\t\t\tFunction: openai.FunctionDefinitionParam{\n\t\t\t\tName: \"greet\",\n\t\t\t\tDescription: openai.String(\"Say hi to someone.\"),\n\t\t\t\tParameters: openai.FunctionParameters{\n\t\t\t\t\t\"type\": \"object\", \"properties\": map[string]interface{}{\"name\": map[string]string{\"type\": \"string\", \"description\": \"Name of the person to greet\"}}, \"required\": []string{\"name\"},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tFunction: openai.FunctionDefinitionParam{\n\t\t\t\tName: \"add\",\n\t\t\t\tDescription: openai.String(\"Add two integers.\"),\n\t\t\t\tParameters: openai.FunctionParameters{\n\t\t\t\t\t\"type\": \"object\", \"properties\": map[string]interface{}{\"A\": map[string]string{\"type\": \"integer\"}, \"B\": map[string]string{\"type\": \"integer\"}}, \"required\": []string{\"A\", \"B\"},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tFunction: openai.FunctionDefinitionParam{\n\t\t\t\tName: \"list_files\",\n\t\t\t\tDescription: openai.String(\"List all non-directory files in the current project directory.\"),\n\t\t\t\tParameters: openai.FunctionParameters{\"type\": \"object\", \"properties\": map[string]interface{}{}},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tFunction: openai.FunctionDefinitionParam{\n\t\t\t\tName: \"read_file\",\n\t\t\t\tDescription: openai.String(\"Read the content of a specific file.\"),\n\t\t\t\tParameters: openai.FunctionParameters{\n\t\t\t\t\t\"type\": \"object\", \"properties\": map[string]interface{}{\"filename\": map[string]string{\"type\": \"string\", \"description\": \"The name of the file to read.\"}}, \"required\": []string{\"filename\"},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\tmessages := []openai.ChatCompletionMessageParamUnion{\n\t\topenai.SystemMessage(\"You are a helpful assistant with access to local tools. You must call tools by using the tool_calls response format. Don't make assumptions about what values to plug into functions. Ask for clarification if a user request is ambiguous.\"),\n\t\topenai.UserMessage(\"Hi, can you greet my friend Alex, add 5 and 7, and then list the files in my project?\"),\n\t}\n\n\tctx := context.Background()\n\n\tfor i := 0; i < 5; i++ {\n\t\tlog.Println(\"--- Sending request to DeepSeek ---\")\n\n\t\tresp, err := client.Chat.Completions.New(ctx, openai.ChatCompletionNewParams{Model: \"deepseek-chat\", Messages: messages, Tools: tools})\n\t\tif err != nil {\n\t\t\tlog.Fatalf(\"ChatCompletion error: %v\\n\", err)\n\t\t}\n\t\tif len(resp.Choices) == 0 {\n\t\t\tlog.Fatal(\"No choices returned from API\")\n\t\t}\n\n\t\tmsg := resp.Choices[0].Message\n\t\tmessages = append(messages, msg.ToParam())\n\n\t\tif msg.ToolCalls != nil {\n\t\t\tfor _, toolCall := range msg.ToolCalls {\n\t\t\t\tfunctionName := toolCall.Function.Name\n\t\t\t\tvar arguments map[string]interface{}\n\t\t\t\tif err := json.Unmarshal([]byte(toolCall.Function.Arguments), &arguments); err != nil {\n\t\t\t\t\tlog.Fatalf(\"Failed to unmarshal function arguments: %v\", err)\n\t\t\t\t}\n\n\t\t\t\tlog.Printf(\"--- LLM wants to call tool: %s with args: %v ---\\n\", functionName, arguments)\n\n\t\t\t\t// 直接调用我们的 Go 函数,该函数内建了 MCP 客户端逻辑\n\t\t\t\ttoolResult, err := invokeMCPTool(functionName, arguments)\n\t\t\t\tif err != nil {\n\t\t\t\t\tlog.Printf(\"Tool call failed: %v\\n\", err)\n\t\t\t\t\ttoolResult = fmt.Sprintf(\"Error executing tool: %v\", err)\n\t\t\t\t}\n\n\t\t\t\tlog.Printf(\"--- Tool result: ---\\n%s\\n---------------------\\n\", toolResult)\n\n\t\t\t\tmessages = append(messages, openai.ToolMessage(toolResult, toolCall.ID))\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\n\t\tlog.Println(\"--- Final response from LLM ---\")\n\t\tlog.Println(msg.Content)\n\t\treturn\n\t}\n\tlog.Println(\"Reached max conversation turns.\")\n}\n"}}, "ai-app-primer/ch03/hello_ai.go": {"e3a40e4b0ecbf5d6f148d949363617d6f430110f": {"t": 1785746063.086831, "n": 3395, "h": "3d6b8da42f5a8f5a", "c": "package main\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"os\"\n\t\"time\"\n)\n\n// 请求结构体 (简化版,只包含核心字段)\ntype ChatCompletionRequest struct {\n\tModel string `json:\"model\"`\n\tMessages []ChatMessage `json:\"messages\"`\n}\n\ntype ChatMessage struct {\n\tRole string `json:\"role\"`\n\tContent string `json:\"content\"`\n}\n\n// 响应结构体 (简化版,只关注核心信息)\ntype ChatCompletionResponse struct {\n\tID string `json:\"id\"`\n\tChoices []Choice `json:\"choices\"`\n\tUsage UsageInfo `json:\"usage\"`\n\t// 为简化,省略了 object, created, model, system_fingerprint 等\n}\n\ntype Choice struct {\n\tMessage ResponseMessage `json:\"message\"`\n\tFinishReason string `json:\"finish_reason\"`\n\t// 为简化,省略了 index, logprobs 等\n}\n\ntype ResponseMessage struct {\n\tRole string `json:\"role\"`\n\tContent string `json:\"content\"` // 注意:在实际应用中,如果 content 可能为 null (如 tool_calls 时),应使用 *string\n}\n\ntype UsageInfo struct {\n\tPromptTokens int `json:\"prompt_tokens\"`\n\tCompletionTokens int `json:\"completion_tokens\"`\n\tTotalTokens int `json:\"total_tokens\"`\n}\n\nfunc main() {\n\tapiKey := os.Getenv(\"OPENAI_API_KEY\")\n\tif apiKey == \"\" {\n\t\tfmt.Println(\"Error: OPENAI_API_KEY environment variable not set.\")\n\t\treturn\n\t}\n\n\t// DeepSeek API 配置\n\tapiURL := \"https://api.deepseek.com/chat/completions\" // 或者 \"https://api.deepseek.com/v1/chat/completions\"\n\tmodelID := \"deepseek-chat\" // 使用 DeepSeek 的一个聊天模型\n\n\t// 构造请求体\n\trequestPayload := ChatCompletionRequest{\n\t\tModel: modelID,\n\t\tMessages: []ChatMessage{\n\t\t\t{Role: \"system\", Content: \"You are a friendly and helpful assistant.\"}, // System (or Developer) message\n\t\t\t{Role: \"user\", Content: \"Hello, AI! How are you today?\"}, // User message\n\t\t},\n\t}\n\n\trequestBodyBytes, err := json.Marshal(requestPayload)\n\tif err != nil {\n\t\tfmt.Printf(\"Error marshalling request: %v\\n\", err)\n\t\treturn\n\t}\n\n\t// 创建 HTTP 请求\n\treq, err := http.NewRequest(\"POST\", apiURL, bytes.NewBuffer(requestBodyBytes))\n\tif err != nil {\n\t\tfmt.Printf(\"Error creating request: %v\\n\", err)\n\t\treturn\n\t}\n\treq.Header.Set(\"Content-Type\", \"application/json\")\n\treq.Header.Set(\"Authorization\", \"Bearer \"+apiKey)\n\n\t// 发送请求\n\tclient := &http.Client{Timeout: 30 * time.Second}\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\tfmt.Printf(\"Error sending request: %v\\n\", err)\n\t\treturn\n\t}\n\tdefer resp.Body.Close()\n\n\t// 读取响应体\n\tresponseBodyBytes, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\tfmt.Printf(\"Error reading response body: %v\\n\", err)\n\t\treturn\n\t}\n\n\t// 检查 HTTP 状态码\n\tif resp.StatusCode != http.StatusOK {\n\t\tfmt.Printf(\"Error: Non-OK status code: %d\\nResponse: %s\\n\", resp.StatusCode, string(responseBodyBytes))\n\t\treturn\n\t}\n\n\t// 解析 JSON 响应\n\tvar chatResponse ChatCompletionResponse\n\terr = json.Unmarshal(responseBodyBytes, &chatResponse)\n\tif err != nil {\n\t\tfmt.Printf(\"Error unmarshalling response: %v\\n\", err)\n\t\tfmt.Printf(\"Raw Response: %s\\n\", string(responseBodyBytes))\n\t\treturn\n\t}\n\n\t// 打印关键信息\n\tfmt.Println(\"--- AI Response ---\")\n\tif len(chatResponse.Choices) > 0 {\n\t\tfmt.Printf(\"ID: %s\\n\", chatResponse.ID)\n\t\tfmt.Printf(\"Assistant: %s\\n\", chatResponse.Choices[0].Message.Content)\n\t\tfmt.Printf(\"Finish Reason: %s\\n\", chatResponse.Choices[0].FinishReason)\n\t\tfmt.Printf(\"Usage: PromptTokens=%d, CompletionTokens=%d, TotalTokens=%d\\n\",\n\t\t\tchatResponse.Usage.PromptTokens,\n\t\t\tchatResponse.Usage.CompletionTokens,\n\t\t\tchatResponse.Usage.TotalTokens,\n\t\t)\n\t} else {\n\t\tfmt.Println(\"No choices received.\")\n\t}\n}\n"}, "144c54bc410da2ab5a27bade6f92d4309f62a72a": {"t": 1785754863.251787, "n": 3395, "h": "3d6b8da42f5a8f5a", "c": "package main\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"os\"\n\t\"time\"\n)\n\n// 请求结构体 (简化版,只包含核心字段)\ntype ChatCompletionRequest struct {\n\tModel string `json:\"model\"`\n\tMessages []ChatMessage `json:\"messages\"`\n}\n\ntype ChatMessage struct {\n\tRole string `json:\"role\"`\n\tContent string `json:\"content\"`\n}\n\n// 响应结构体 (简化版,只关注核心信息)\ntype ChatCompletionResponse struct {\n\tID string `json:\"id\"`\n\tChoices []Choice `json:\"choices\"`\n\tUsage UsageInfo `json:\"usage\"`\n\t// 为简化,省略了 object, created, model, system_fingerprint 等\n}\n\ntype Choice struct {\n\tMessage ResponseMessage `json:\"message\"`\n\tFinishReason string `json:\"finish_reason\"`\n\t// 为简化,省略了 index, logprobs 等\n}\n\ntype ResponseMessage struct {\n\tRole string `json:\"role\"`\n\tContent string `json:\"content\"` // 注意:在实际应用中,如果 content 可能为 null (如 tool_calls 时),应使用 *string\n}\n\ntype UsageInfo struct {\n\tPromptTokens int `json:\"prompt_tokens\"`\n\tCompletionTokens int `json:\"completion_tokens\"`\n\tTotalTokens int `json:\"total_tokens\"`\n}\n\nfunc main() {\n\tapiKey := os.Getenv(\"OPENAI_API_KEY\")\n\tif apiKey == \"\" {\n\t\tfmt.Println(\"Error: OPENAI_API_KEY environment variable not set.\")\n\t\treturn\n\t}\n\n\t// DeepSeek API 配置\n\tapiURL := \"https://api.deepseek.com/chat/completions\" // 或者 \"https://api.deepseek.com/v1/chat/completions\"\n\tmodelID := \"deepseek-chat\" // 使用 DeepSeek 的一个聊天模型\n\n\t// 构造请求体\n\trequestPayload := ChatCompletionRequest{\n\t\tModel: modelID,\n\t\tMessages: []ChatMessage{\n\t\t\t{Role: \"system\", Content: \"You are a friendly and helpful assistant.\"}, // System (or Developer) message\n\t\t\t{Role: \"user\", Content: \"Hello, AI! How are you today?\"}, // User message\n\t\t},\n\t}\n\n\trequestBodyBytes, err := json.Marshal(requestPayload)\n\tif err != nil {\n\t\tfmt.Printf(\"Error marshalling request: %v\\n\", err)\n\t\treturn\n\t}\n\n\t// 创建 HTTP 请求\n\treq, err := http.NewRequest(\"POST\", apiURL, bytes.NewBuffer(requestBodyBytes))\n\tif err != nil {\n\t\tfmt.Printf(\"Error creating request: %v\\n\", err)\n\t\treturn\n\t}\n\treq.Header.Set(\"Content-Type\", \"application/json\")\n\treq.Header.Set(\"Authorization\", \"Bearer \"+apiKey)\n\n\t// 发送请求\n\tclient := &http.Client{Timeout: 30 * time.Second}\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\tfmt.Printf(\"Error sending request: %v\\n\", err)\n\t\treturn\n\t}\n\tdefer resp.Body.Close()\n\n\t// 读取响应体\n\tresponseBodyBytes, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\tfmt.Printf(\"Error reading response body: %v\\n\", err)\n\t\treturn\n\t}\n\n\t// 检查 HTTP 状态码\n\tif resp.StatusCode != http.StatusOK {\n\t\tfmt.Printf(\"Error: Non-OK status code: %d\\nResponse: %s\\n\", resp.StatusCode, string(responseBodyBytes))\n\t\treturn\n\t}\n\n\t// 解析 JSON 响应\n\tvar chatResponse ChatCompletionResponse\n\terr = json.Unmarshal(responseBodyBytes, &chatResponse)\n\tif err != nil {\n\t\tfmt.Printf(\"Error unmarshalling response: %v\\n\", err)\n\t\tfmt.Printf(\"Raw Response: %s\\n\", string(responseBodyBytes))\n\t\treturn\n\t}\n\n\t// 打印关键信息\n\tfmt.Println(\"--- AI Response ---\")\n\tif len(chatResponse.Choices) > 0 {\n\t\tfmt.Printf(\"ID: %s\\n\", chatResponse.ID)\n\t\tfmt.Printf(\"Assistant: %s\\n\", chatResponse.Choices[0].Message.Content)\n\t\tfmt.Printf(\"Finish Reason: %s\\n\", chatResponse.Choices[0].FinishReason)\n\t\tfmt.Printf(\"Usage: PromptTokens=%d, CompletionTokens=%d, TotalTokens=%d\\n\",\n\t\t\tchatResponse.Usage.PromptTokens,\n\t\t\tchatResponse.Usage.CompletionTokens,\n\t\t\tchatResponse.Usage.TotalTokens,\n\t\t)\n\t} else {\n\t\tfmt.Println(\"No choices received.\")\n\t}\n}\n"}}}} |