16.玩转AI Agent 项目开发
手撸一个AI Agent 对话机器人
准备工作
申请大模型API key(如gpt-4o、qwen-max、deepseek-v3)
Demo curl:其中$DASHSCOPE_API_KEY就是上面申请的API KEY
curl -X POST https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions \ -H "Authorization: Bearer $DASHSCOPE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "qwen-max", "messages": [ { "role": "system", "content": "是一名专业的气象学专家" }, { "role": "user", "content": "今天的天气如何?" }, { "role": "assistant", "content": "", "tool_call_id": "", "tool_calls": [ { "id": "call_f1f5e214aff84e16a32bf9", "type": "function", "index": 0, "function": { "name": "get_current_time", "arguments": "{}" } } ] }, { "role": "tool", "content": "当前时间是:2025-05-16 10:57:45", "tool_call_id": "call_f1f5e214aff84e16a32bf9", "tool_calls": null } ] }'
对话消息类型
系统消息
一般放在调用接口的第一个,模型的目标或角色,是用于大模型输出的提示词{
"role": "system",
"content": "是一名专业的气象学专家"
}
用户消息
用户输入的提问内容{
"role": "user",
"content": "今天的天气如何?"
}
助手消息
大模型回复的消息内容{
"role": "assistant",
"content": "今天的天气如何?"
}
工具消息
工具函数执行的输出结果{
"role": "tool",
"content": "当前时间是:2025-05-16 10:57:45",
"tool_call_id": "call_f1f5e214aff84e16a32bf9",
}
Function calling原理图
核心逻辑说明
初始化消息

工具函数说明

首次调用大模型

处理大模型返回的工具列表






返回数据
{
"code": 0,
"msg": "success",
"data": {
"model": "qwen-max",
"messages": [
{
"role": "system",
"content": "你是著名的气象学专家,请根据用户的问题给出非常专业的回答。",
"tool_call_id": "",
"tool_calls": null
},
{
"role": "user",
"content": "明天天气如何?",
"tool_call_id": "",
"tool_calls": null
},
{
"role": "assistant",
"content": "",
"tool_call_id": "",
"tool_calls": [
{
"id": "call_0ed2ac6a239b4ad98c76b6",
"type": "function",
"index": 0,
"function": {
"name": "get_current_time",
"arguments": "{}"
}
}
]
},
{
"role": "tool",
"content": "当前时间是:2025-05-16 20:28:39",
"tool_call_id": "call_0ed2ac6a239b4ad98c76b6",
"tool_calls": null
},
{
"role": "assistant",
"content": "",
"tool_call_id": "",
"tool_calls": [
{
"id": "call_10724f37620842da8a185b",
"type": "function",
"index": 0,
"function": {
"name": "get_current_weather",
"arguments": "{\"date\": \"2025-05-17\"}"
}
}
]
},
{
"role": "tool",
"content": "当前2025-05-17天气为晴,温度为28度",
"tool_call_id": "call_10724f37620842da8a185b",
"tool_calls": null
},
{
"role": "assistant",
"content": "明天的天气预报显示天气将为晴朗,温度大约在28摄氏度左右。请根据实际需要做好防晒和保持水分。",
"tool_call_id": "",
"tool_calls": null
}
],
"tools": [
{
"type": "function",
"function": {
"name": "get_current_weather",
"description": "用于获取某一天的天气",
"parameters": {
"properties": {
"date": {
"description": "时间,如2025-01-01",
"type": "string"
}
},
"required": [
"date"
],
"type": "object"
}
}
},
{
"type": "function",
"function": {
"name": "get_current_time",
"description": "用于获取当前时间",
"parameters": {
"properties": {},
"type": "object"
}
}
}
],
"stream": false
}
}
手撸代码
main.go
package main import ( "fmt" "go_chat_mcp/app/controller/chat" "log" "net/http" ) func main() { http.HandleFunc("/chat/send", McpSend) fmt.Println("服务器启动在 http://localhost:8080") log.Fatal(http.ListenAndServe(":8080", nil)) } func McpSend(w http.ResponseWriter, r *http.Request) { send := chat.NewSend() send.Send(w, r) }
send.go
package chat import ( "encoding/json" "fmt" "go_chat_mcp/app/common/proto" "go_chat_mcp/app/model" "io" "net/http" ) type Send struct { } func NewSend() *Send { return &Send{} } func (s *Send) Send(w http.ResponseWriter, r *http.Request) { body, err := io.ReadAll(r.Body) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } var sendRequest proto.SendRequest err = json.Unmarshal(body, &sendRequest) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } if sendRequest.MsgId == "" || sendRequest.Msg == "" { http.Error(w, "msg_id or msg is empty", http.StatusBadRequest) return } sendResp, err := model.NewModel(sendRequest).Send() if err != nil { fmt.Println(err.Error()) return } resp := proto.Resp{ Code: 0, Message: "success", Data: sendResp, } respBytes, err := json.Marshal(resp) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } w.WriteHeader(http.StatusOK) w.Write(respBytes) }
model.go
package model import ( "go_chat_mcp/app/common/proto" gpt4o "go_chat_mcp/app/gpt-4o" "go_chat_mcp/app/qwen" ) type Model interface { Send() (resp proto.ModelMsgs, err error) } func NewModel(param proto.SendRequest) Model { switch param.Model { case "gpt-4o": return gpt4o.NewGpt4o(param) case "qwen3-235b-a22b": return qwen.NewQwen(param) case "qwen-max": return qwen.NewQwen(param) } return nil }
qwen.go
package qwen import ( "encoding/json" "fmt" "go_chat_mcp/app/common/proto" "sync" ) type Qwen struct { Msgs proto.QwenMsgs MsgId string } const ( QwenUrl = "https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions" ) var APIKEYs = []string{ "sk-4cd2daxxxxxxxxxxxxxaxxxxxxc1d42fdc", } var qwenMap map[string]proto.QwenMsgs var once sync.Once func init() { once.Do(func() { qwenMap = make(map[string]proto.QwenMsgs) }) } func NewQwen(param proto.SendRequest) *Qwen { msgId := param.MsgId qwen := &Qwen{MsgId: msgId} userMsg := proto.Messages{ Role: "user", Content: param.Msg, } if qwenMsgs, ok := qwenMap[msgId]; ok { qwen.Msgs = qwenMsgs qwen.Msgs.Messages = append(qwen.Msgs.Messages, userMsg) } else { // 初始化会话 qwen.Msgs = proto.QwenMsgs{ Model: param.Model, Messages: append(SystemMsg, userMsg), Tools: qwenTools, Stream: false, EnableThinking: false, } } qwenMap[msgId] = qwen.Msgs return qwen } func (d *Qwen) Send() (resp proto.ModelMsgs, err error) { //记录会话 /**************调用qwen3 API******************/ send1, err := d.PushToQwen() if err != nil { fmt.Println("err 1111", err) return } if len(send1.Choices) == 0 { fmt.Println("err 2222", err) return } d.AddAssistantMessage(send1.Choices[0].Message.Content, send1.Choices[0].Message.Role, send1.Choices[0].Message.ToolCalls) // 处理工具调用 toolCalls1 := send1.Choices[0].Message.ToolCalls fmt.Println("toolCalls", toolCalls1) if len(toolCalls1) > 0 { for _, v := range toolCalls1 { d.AddToolMessage(v) } send2 := proto.SendResponse{} send2, err = d.PushToQwen() if err != nil { fmt.Println("err 3333", err) } if len(send2.Choices) == 0 { fmt.Println("err 4444", err) return } d.AddAssistantMessage(send2.Choices[0].Message.Content, send2.Choices[0].Message.Role, send2.Choices[0].Message.ToolCalls) toolCalls2 := send2.Choices[0].Message.ToolCalls if len(toolCalls2) > 0 { for _, v := range toolCalls2 { d.AddToolMessage(v) } } fmt.Println("d.Msgs 88888888", d.GetMsgJson()) //再次调用大模型 API send3 := proto.SendResponse{} send3, err = d.PushToQwen() if err != nil { fmt.Println("err 4444", err) return } if len(send3.Choices) == 0 { fmt.Println("err 4444", err) return } fmt.Println("d.Msgs 999999999", d.GetMsgJson()) d.AddAssistantMessage(send3.Choices[0].Message.Content, send3.Choices[0].Message.Role, send3.Choices[0].Message.ToolCalls) } fmt.Println("d.Msgs 1000000000", d.GetMsgJson()) modelMsgs := proto.ModelMsgs{ Model: d.Msgs.Model, Messages: d.Msgs.Messages, Tools: d.Msgs.Tools, Stream: d.Msgs.Stream, } return modelMsgs, nil } func (d *Qwen) PushToQwen() (resp proto.SendResponse, err error) { var respBody []byte //再次调用qwen3 API respBody, err = d.HttpQwen(d.Msgs) if err != nil { return resp, err } fmt.Println("respBody 1111", string(respBody)) var sendResponse proto.SendResponse err = json.Unmarshal(respBody, &sendResponse) if err != nil { return sendResponse, err } return sendResponse, nil } func (d *Qwen) AddToolMessage(toolCall proto.ToolCall) { toolResult := "" if tool, ok := ToolsMap[toolCall.Function.Name]; ok { toolResult = tool(toolCall.Function.Arguments) } // 将工具返回结果添加到会话中 d.Msgs.Messages = append(d.Msgs.Messages, proto.Messages{ Role: "tool", Content: toolResult, ToolCallId: toolCall.Id, }) // 更新会话 qwenMap[d.MsgId] = d.Msgs } func (d *Qwen) AddSystemMessage(msg, role string) { // 将工具返回结果添加到会话中 d.Msgs.Messages = append(d.Msgs.Messages, proto.Messages{ Role: role, Content: msg, }) // 更新会话 qwenMap[d.MsgId] = d.Msgs } func (d *Qwen) AddAssistantMessage(msg, role string, toolCall []proto.ToolCall) { // 将工具返回结果添加到会话中 d.Msgs.Messages = append(d.Msgs.Messages, proto.Messages{ Role: role, Content: msg, ToolCalls: toolCall, }) // 更新会话 qwenMap[d.MsgId] = d.Msgs } func (d *Qwen) GetMsgJson() string { msgBytes, _ := json.Marshal(d.Msgs) return string(msgBytes) }
common.go
package qwen import ( "encoding/json" "fmt" "go_chat_mcp/app/common/proto" "io" "net/http" "strings" "time" ) func (d *Qwen) HttpQwen(qwenMsgs proto.QwenMsgs) (respBody []byte, err error) { // 确保非流式调用时EnableThinking为false if !qwenMsgs.Stream { // qwenMsgs.EnableThinking = false } body, err := json.Marshal(qwenMsgs) if err != nil { fmt.Println(err) return } httpReq, err := http.NewRequest("POST", QwenUrl, strings.NewReader(string(body))) if err != nil { fmt.Println(err) return } timeUnix := time.Now().Unix() // 轮询APIKEY 防止被封号 或被限流 APIKey := APIKEYs[timeUnix%int64(len(APIKEYs))] // 设置请求头 httpReq.Header.Add("Content-Type", "application/json") httpReq.Header.Add("Authorization", "Bearer "+APIKey) client := &http.Client{} httpResp, err := client.Do(httpReq) if err != nil { return nil, err } defer httpResp.Body.Close() respBody, err = io.ReadAll(httpResp.Body) if err != nil { return nil, err } return respBody, nil }
system.go
package qwen import "go_chat_mcp/app/common/proto" var SystemMsg = []proto.Messages{ { Role: "system", Content: "你是著名的气象学专家,请根据用户的问题给出非常专业的回答。", }, }
tools.go
package qwen import ( "encoding/json" "fmt" "go_chat_mcp/app/common/proto" "time" ) var qwenTools = []proto.Tool{ { Type: "function", Function: proto.ToolFunction{ Name: GetCurrentWeatherToolName, Description: "用于获取某一天的天气", Parameters: proto.ToolParameters{ "type": "object", "properties": map[string]interface{}{ "date": map[string]interface{}{ "type": "string", "description": "时间,如2025-01-01", }, }, "required": []string{"date"}, }, }, }, { Type: "function", Function: proto.ToolFunction{ Name: GetCurrentTimeToolName, Description: "用于获取当前时间", Parameters: proto.ToolParameters{ "type": "object", "properties": map[string]interface{}{}, }, }, }, } const ( GetCurrentWeatherToolName = "get_current_weather" GetCurrentTimeToolName = "get_current_time" ) var ToolsMap = map[string]func(paramsJson string) string{ GetCurrentWeatherToolName: GetCurrentWeather, GetCurrentTimeToolName: GetCurrentTime, } type GetCurrentWeatherParams struct { Date string `json:"date"` } func GetCurrentWeather(paramsJson string) string { var params GetCurrentWeatherParams err := json.Unmarshal([]byte(paramsJson), ¶ms) if err != nil { return "参数错误" } return fmt.Sprintf("当前%s天气为晴,温度为28度", params.Date) } func GetCurrentTime(paramsJson string) string { return "当前时间是:" + time.Now().Format("2006-01-02 15:04:05") }
作者:admin 创建时间:2025-05-10 22:32
最后编辑:admin 更新时间:2025-05-22 10:29
最后编辑:admin 更新时间:2025-05-22 10:29