DeepSeek API 客户端封装
DeepSeek API 客户端封装构建稳定可靠的大模型通信桥梁本文是《从零构建墨言博客助手》系列的第13章。完整源码请访问https://github.com/2692341798/InkWords引言为什么需要封装API客户端想象一下你要和一位远方的专家DeepSeek模型对话。每次对话都需要找到正确的地址API端点说清楚你的问题构造请求等待专家回答发送请求理解专家的回复解析响应如果每次对话都从头开始做这些步骤不仅效率低下而且容易出错。这就是我们需要封装API客户端的原因——创建一个专业翻译官帮我们处理所有通信细节。一、客户端核心结构设计让我们先看看DeepSeekClient的结构设计// DeepSeekClient 是与DeepSeek API通信的核心客户端typeDeepSeekClientstruct{APIKeystring// 身份验证密钥相当于门禁卡APIURLstring// API地址相当于专家办公室地址Client*http.Client// HTTP客户端相当于快递员}生活化比喻这就像你有一个私人助理DeepSeekClient他知道专家的地址APIURL有门禁卡APIKey并且有自己的交通工具http.Client。创建客户端的工厂函数// NewDeepSeekClient 创建并初始化一个新的DeepSeek客户端funcNewDeepSeekClient(apiKeystring)*DeepSeekClient{returnDeepSeekClient{APIKey:apiKey,// 保存API密钥APIURL:defaultDeepSeekAPIURL,// 使用默认API地址Client:http.Client{},// 创建新的HTTP客户端}}二、消息数据结构定义在与大模型对话时我们需要定义清晰的数据结构。这就像写信要有固定的格式// Message 表示聊天中的一条消息typeMessagestruct{Rolestringjson:role// 角色user(用户)或assistant(助手)Contentstringjson:content// 消息内容}// ChatRequest 表示发送给DeepSeek API的请求typeChatRequeststruct{Modelstringjson:model// 使用的模型名称Messages[]Messagejson:messages// 对话历史Streambooljson:stream// 是否使用流式响应}代码解释json:role是Go语言的标签tag告诉JSON编码器/解码器字段在JSON中的名称Stream字段控制返回方式false表示一次性返回完整结果true表示流式返回三、同步生成模式实现同步模式就像传统的邮件通信发送完整请求等待完整响应。// Generate 调用DeepSeek API并返回完整响应func(c*DeepSeekClient)Generate(ctx context.Context,modelstring,messages[]Message)(string,error){// 1. 构造请求体reqBody:ChatRequest{Model:model,Messages:messages,Stream:false,// 关键设置为非流式}// 2. 将结构体转换为JSON字符串jsonData,err:json.Marshal(reqBody)iferr!nil{return,fmt.Errorf(failed to marshal request body: %w,err)}// 3. 创建HTTP请求req,err:http.NewRequestWithContext(ctx,http.MethodPost,c.APIURL,bytes.NewBuffer(jsonData))iferr!nil{return,fmt.Errorf(failed to create request: %w,err)}// 4. 设置请求头req.Header.Set(Content-Type,application/json)req.Header.Set(Authorization,Bearer c.APIKey)// 5. 发送请求resp,err:c.Client.Do(req)iferr!nil{return,fmt.Errorf(failed to execute request: %w,err)}deferresp.Body.Close()// 确保响应体被关闭// 6. 读取响应bodyBytes,err:io.ReadAll(resp.Body)iferr!nil{return,fmt.Errorf(failed to read response body: %w,err)}// 7. 检查HTTP状态码ifresp.StatusCode!http.StatusOK{return,fmt.Errorf(API request failed with status %d: %s,resp.StatusCode,string(bodyBytes))}// 8. 解析JSON响应varresultstruct{Choices[]struct{Messagestruct{Contentstringjson:content}json:message}json:choices}iferr:json.Unmarshal(bodyBytes,result);err!nil{return,fmt.Errorf(failed to unmarshal response: %w,err)}// 9. 提取内容iflen(result.Choices)0{return,fmt.Errorf(API returned empty choices)}returnresult.Choices[0].Message.Content,nil}关键点解析错误处理每一步都有详细的错误信息使用fmt.Errorf的%w包装错误资源管理使用defer resp.Body.Close()确保网络连接被正确关闭上下文传递ctx参数允许调用者取消长时间运行的请求四、流式生成模式实现流式模式就像实时对话对方一边思考一边回答你也能一边听一边理解。数据通道DeepSeek API客户端数据通道DeepSeek API客户端loop[持续接收]发送流式请求(Streamtrue)开始流式响应发送数据块(Chunk)实时推送内容发送[DONE]信号关闭通道下面是流式生成的核心实现// GenerateStream 调用流式API并实时推送数据块func(c*DeepSeekClient)GenerateStream(ctx context.Context,modelstring,messages[]Message,chunkChanchan-string)(string,error){deferclose(chunkChan)// 确保函数退出时关闭通道// 1. 构造流式请求体reqBody:ChatRequest{Model:model,Messages:messages,Stream:true,// 关键设置为流式}// 2-5步与同步模式相同序列化、创建请求、设置头部、发送请求// ...// 6. 创建缓冲读取器高效读取流数据reader:bufio.NewReader(resp.Body)varfinalFinishReasonstring// 7. 循环读取流数据for{// 检查上下文是否被取消select{case-ctx.Done():return,ctx.Err()default:}// 读取一行数据以\n分隔line,err:reader.ReadBytes(\n)iferr!nil{iferrio.EOF{returnfinalFinishReason,nil}return,fmt.Errorf(failed to read stream: %w,err)}lineStr:strings.TrimSpace(string(line))iflineStr{continue// 跳过空行}// 8. 解析SSE格式data: {json}if!strings.HasPrefix(lineStr,data: ){continue// 跳过非数据行}data:strings.TrimPrefix(lineStr,data: )ifdata[DONE]{returnfinalFinishReason,nil// 流结束}// 9. 解析JSON数据块varchunk ChatCompletionChunkiferr:json.Unmarshal([]byte(data),chunk);err!nil{continue// 跳过解析失败的数据}// 10. 处理有效数据iflen(chunk.Choices)0{content:chunk.Choices[0].Delta.Contentifcontent!{chunkChan-content// 发送到通道}// 检查是否结束ifchunk.Choices[0].FinishReason!nil{finalFinishReason*chunk.Choices[0].FinishReasoniffinalFinishReasonstop||finalFinishReasonlength{returnfinalFinishReason,nil}}}}}流式处理的核心机制SSE协议Server-Sent Events服务器推送事件的标准格式数据格式每行以data:开头[DONE]表示结束通道通信使用Go的channel实现生产者-消费者模式增量更新每次只发送变化的部分delta.content五、实战如何使用客户端场景1同步生成适合短文本packagemainimport(contextfmtlogyour-project/internal/llm)funcmain(){// 1. 创建客户端client:llm.NewDeepSeekClient(your-api-key-here)// 2. 准备对话messages:[]llm.Message{{Role:user,Content:请用Go语言写一个Hello World程序},}// 3. 调用生成ctx:context.Background()response,err:client.Generate(ctx,deepseek-chat,messages)iferr!nil{log.Fatalf(生成失败: %v,err)}// 4. 输出结果fmt.Println(生成的代码:)fmt.Println(response)}场景2流式生成适合长文本packagemainimport(contextfmtlogtimeyour-project/internal/llm)funcmain(){// 1. 创建客户端和通道client:llm.NewDeepSeekClient(your-api-key-here)chunkChan:make(chanstring,100)// 缓冲通道// 2. 准备对话messages:[]llm.Message{{Role:user,Content:写一篇关于人工智能的短文},}// 3. 启动goroutine接收数据gofunc(){forchunk:rangechunkChan{fmt.Print(chunk)// 实时打印}fmt.Println(\n--- 生成完成 ---)}()// 4. 调用流式生成ctx,cancel:context.WithTimeout(context.Background(),30*time.Second)defercancel()_,err:client.GenerateStream(ctx,deepseek-chat,messages,chunkChan)iferr!nil{log.Fatalf(流式生成失败: %v,err)}}六、错误处理最佳实践在我们的实现中错误处理遵循以下原则尽早失败发现问题立即返回错误提供上下文使用fmt.Errorf(failed to ...: %w, err)包装错误资源清理使用defer确保资源释放状态检查检查HTTP状态码和响应结构// 示例完整的错误处理链funcprocess()error{data,err:getData()iferr!nil{returnfmt.Errorf(获取数据失败: %w,err)}result,err:parseData(data)iferr!nil{returnfmt.Errorf(解析数据失败: %w,err)}returnsaveResult(result)}七、性能优化建议连接复用http.Client默认启用连接池缓冲读取使用bufio.Reader提高读取效率合理超时为长时间请求设置超时通道缓冲根据数据量设置合适的通道缓冲区大小// 优化后的客户端配置funcNewOptimizedClient(apiKeystring)*DeepSeekClient{returnDeepSeekClient{APIKey:apiKey,APIURL:defaultDeepSeekAPIURL,Client:http.Client{Timeout:60*time.Second,// 设置超时Transport:http.Transport{MaxIdleConns:100,// 最大空闲连接IdleConnTimeout:90*time.Second,// 空闲连接超时TLSHandshakeTimeout:10*time.Second,// TLS握手超时},},}}总结通过本章的学习我们完成了DeepSeek API客户端的完整封装结构设计定义了清晰的数据结构和客户端接口同步模式实现了一次性获取完整响应的Generate方法流式模式实现了实时推送的GenerateStream方法错误处理建立了完善的错误处理机制实战应用提供了两种使用场景的示例代码这个客户端封装不仅解决了基本的API调用问题还提供了灵活性支持同步和流式两种模式可靠性完善的错误处理和资源管理可扩展性清晰的结构便于后续功能扩展核心价值通过封装我们将复杂的HTTP通信、JSON解析、流处理等细节隐藏起来让业务代码可以专注于核心逻辑大大提高了开发效率和代码质量。下期预告文档解析器支持 PDF、DOCX、Markdown在下一篇文章中我们将探讨如何构建一个多功能文档解析器。你将学习到如何解析不同格式的文档PDF、DOCX、Markdown提取文本内容的策略和技巧处理复杂文档结构如表格、图片、样式构建统一的文档处理接口无论你是处理用户上传的文档还是需要从多种来源收集内容一个强大的文档解析器都是不可或缺的。敬请期待