FILE / ScuroNeko/go-deepseek
completion.go
Исходный файл и его история в репозитории.
160 lines
4.2 KiB
Go
160 lines
4.2 KiB
Go
package deepseek
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"io"
|
|
"net/http"
|
|
"strings"
|
|
)
|
|
|
|
// CompletionReq configures a chat completion request.
|
|
type CompletionReq struct {
|
|
ChatSessionID string `json:"chat_session_id"`
|
|
ParentMessageID *uint64 `json:"parent_message_id"`
|
|
ModelType ModelType `json:"model_type"`
|
|
promptBuilder strings.Builder
|
|
Prompt string `json:"prompt"`
|
|
RefFileIDs []string `json:"ref_file_ids"`
|
|
ThinkingEnabled bool `json:"thinking_enabled"`
|
|
SearchEnabled bool `json:"search_enabled"`
|
|
}
|
|
|
|
// NewCompletionReq creates a completion request for chatID using the default
|
|
// model type.
|
|
func NewCompletionReq(chatID string) *CompletionReq {
|
|
return &CompletionReq{
|
|
ChatSessionID: chatID, ModelType: ModelTypeDefault,
|
|
}
|
|
}
|
|
|
|
// SetParentMessageID sets the parent message for a continued conversation.
|
|
func (req *CompletionReq) SetParentMessageID(id uint64) *CompletionReq {
|
|
req.ParentMessageID = new(id)
|
|
return req
|
|
}
|
|
|
|
// SetModelType selects the model used for the completion.
|
|
func (req *CompletionReq) SetModelType(t ModelType) *CompletionReq {
|
|
req.ModelType = t
|
|
return req
|
|
}
|
|
|
|
// AddPrompt appends s to the prompt under construction.
|
|
func (req *CompletionReq) AddPrompt(s string) *CompletionReq {
|
|
_, _ = req.promptBuilder.WriteString(s)
|
|
return req
|
|
}
|
|
|
|
// BuildPrompt stores the accumulated prompt and resets the internal builder.
|
|
func (req *CompletionReq) BuildPrompt() *CompletionReq {
|
|
req.Prompt = req.promptBuilder.String()
|
|
req.promptBuilder = strings.Builder{}
|
|
return req
|
|
}
|
|
|
|
// SetPrompt replaces the request prompt.
|
|
func (req *CompletionReq) SetPrompt(prompt string) *CompletionReq {
|
|
req.Prompt = prompt
|
|
return req
|
|
}
|
|
|
|
// SetThinkingEnabled enables or disables model thinking output.
|
|
func (req *CompletionReq) SetThinkingEnabled(b bool) *CompletionReq {
|
|
req.ThinkingEnabled = b
|
|
return req
|
|
}
|
|
|
|
// SetSearchEnabled enables or disables web search for the completion.
|
|
func (req *CompletionReq) SetSearchEnabled(b bool) *CompletionReq {
|
|
req.SearchEnabled = b
|
|
return req
|
|
}
|
|
|
|
// ModelType identifies a DeepSeek chat model mode.
|
|
type ModelType string
|
|
|
|
const (
|
|
// ModelTypeDefault selects the default model mode.
|
|
ModelTypeDefault ModelType = "default"
|
|
// ModelTypeExpert selects the expert model mode.
|
|
ModelTypeExpert ModelType = "expert"
|
|
)
|
|
|
|
// Completion starts a streaming completion using a background context.
|
|
// The caller owns and must either close the response body or pass the response
|
|
// to ReadStream or ReadStreamAsString.
|
|
func (api *Client) Completion(body CompletionReq) (*http.Response, error) {
|
|
return api.CompletionWithContext(context.Background(), body)
|
|
}
|
|
|
|
// CompletionWithContext starts a streaming completion and honors ctx
|
|
// cancellation. The caller owns and must either close the response body or
|
|
// pass the response to ReadStream or ReadStreamAsString.
|
|
func (api *Client) CompletionWithContext(ctx context.Context, body CompletionReq) (*http.Response, error) {
|
|
req := NewRequest[CreatePowChallengeRes]("POST", "chat/completion", body)
|
|
err := req.SolvePow(ctx, api)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
resp, err := req.DoWithContext(ctx, api)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return resp, nil
|
|
}
|
|
|
|
// ReadStream consumes and closes resp.Body, updates stream state, and calls
|
|
// onText for newly appended response text. A nil callback is allowed.
|
|
func ReadStream(ctx context.Context, resp *http.Response, onText func(string)) (*StreamState, error) {
|
|
defer resp.Body.Close()
|
|
|
|
reader := NewSSEReader(resp.Body)
|
|
state := &StreamState{}
|
|
|
|
var previousLength int
|
|
|
|
for {
|
|
event, err := reader.Next(ctx)
|
|
if errors.Is(err, io.EOF) {
|
|
break
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err := state.Apply(event); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
current := state.Content.String()
|
|
|
|
if len(current) > previousLength {
|
|
chunk := current[previousLength:]
|
|
|
|
if onText != nil {
|
|
onText(chunk)
|
|
}
|
|
|
|
previousLength = len(current)
|
|
}
|
|
|
|
if event.Event == "close" {
|
|
break
|
|
}
|
|
}
|
|
|
|
return state, nil
|
|
}
|
|
|
|
// ReadStreamAsString consumes and closes resp.Body and returns the final
|
|
// response content.
|
|
func ReadStreamAsString(ctx context.Context, resp *http.Response) (string, error) {
|
|
state, err := ReadStream(ctx, resp, nil)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return state.Content.String(), nil
|
|
}
|