package adapters import ( "context" "fmt" "strings" "git.threesix.ai/jordan/persona-community-5/pkg/laozhang" "git.threesix.ai/jordan/persona-community-5/pkg/textgen" ) const ( // IMPORTANT: Always use gemini-3-flash-preview for text generation. // DO NOT use gemini-2.5-flash or any 2.x models. defaultLaoZhangTextModel = "gemini-3-flash-preview" ) // LaoZhangTextProvider implements textgen.TextGenerator using LaoZhang API. type LaoZhangTextProvider struct { client *laozhang.Client model string } // NewLaoZhangTextProvider creates a new LaoZhang text generation provider. func NewLaoZhangTextProvider(client *laozhang.Client, model string) *LaoZhangTextProvider { if model == "" { model = defaultLaoZhangTextModel } return &LaoZhangTextProvider{ client: client, model: model, } } // Name implements textgen.Provider. func (p *LaoZhangTextProvider) Name() string { return "laozhang" } // Health implements textgen.Provider. func (p *LaoZhangTextProvider) Health(ctx context.Context) error { return p.client.Health(ctx) } // GenerateText implements textgen.TextGenerator. func (p *LaoZhangTextProvider) GenerateText(ctx context.Context, req textgen.TextRequest) (*textgen.TextResponse, error) { model := req.Model if model == "" { model = p.model } // Build messages from request var messages []laozhang.ChatMessage // Add system prompt if provided if req.SystemPrompt != "" { messages = append(messages, laozhang.ChatMessage{ Role: "system", Content: req.SystemPrompt, }) } // Add messages if provided (multi-turn conversation) if len(req.Messages) > 0 { for _, msg := range req.Messages { messages = append(messages, laozhang.ChatMessage{ Role: msg.Role, Content: msg.Content, }) } } else if req.Prompt != "" { // Single prompt messages = append(messages, laozhang.ChatMessage{ Role: "user", Content: req.Prompt, }) } // Build chat request chatReq := laozhang.ChatCompletionRequest{ Model: model, Messages: messages, } if req.MaxTokens > 0 { chatReq.MaxTokens = req.MaxTokens } if req.Temperature > 0 { chatReq.Temperature = req.Temperature } // Call LaoZhang API resp, err := p.client.ChatCompletion(ctx, chatReq) if err != nil { return nil, classifyLaoZhangError(err) } // Extract text from response if len(resp.Choices) == 0 { return nil, fmt.Errorf("empty response from LaoZhang") } responseText := resp.Choices[0].Message.Content if responseText == "" { return nil, fmt.Errorf("empty content in LaoZhang response") } // Build response result := &textgen.TextResponse{ Text: responseText, Usage: &textgen.Usage{ PromptTokens: resp.Usage.PromptTokens, CompletionTokens: resp.Usage.CompletionTokens, TotalTokens: resp.Usage.TotalTokens, }, } return result, nil } // classifyLaoZhangError converts LaoZhang errors to textgen sentinel errors. func classifyLaoZhangError(err error) error { if err == nil { return nil } errStr := strings.ToLower(err.Error()) // Check for common error patterns switch { case strings.Contains(errStr, "quota"): return fmt.Errorf("%w: %v", textgen.ErrQuotaExceeded, err) case strings.Contains(errStr, "rate") || strings.Contains(errStr, "429"): return fmt.Errorf("%w: %v", textgen.ErrRateLimited, err) case strings.Contains(errStr, "safety") || strings.Contains(errStr, "blocked") || strings.Contains(errStr, "content"): return fmt.Errorf("%w: %v", textgen.ErrContentBlocked, err) case strings.Contains(errStr, "timeout") || strings.Contains(errStr, "deadline"): return fmt.Errorf("%w: %v", textgen.ErrTimeout, err) default: return err } } // GenerateStream implements textgen.TextStreamer using LaoZhang streaming API. func (p *LaoZhangTextProvider) GenerateStream(ctx context.Context, req textgen.TextRequest, onChunk func(textgen.StreamChunk)) error { model := req.Model if model == "" { model = p.model } // Build messages from request var messages []laozhang.ChatMessage if req.SystemPrompt != "" { messages = append(messages, laozhang.ChatMessage{ Role: "system", Content: req.SystemPrompt, }) } if len(req.Messages) > 0 { for _, msg := range req.Messages { messages = append(messages, laozhang.ChatMessage{ Role: msg.Role, Content: msg.Content, }) } } else if req.Prompt != "" { messages = append(messages, laozhang.ChatMessage{ Role: "user", Content: req.Prompt, }) } chatReq := laozhang.ChatCompletionRequest{ Model: model, Messages: messages, } if req.MaxTokens > 0 { chatReq.MaxTokens = req.MaxTokens } if req.Temperature > 0 { chatReq.Temperature = req.Temperature } return p.client.ChatCompletionStream(ctx, chatReq, func(chunk laozhang.StreamChunk) { onChunk(textgen.StreamChunk{ Text: chunk.Text, Done: chunk.Done, Provider: func() string { if chunk.Done { return p.Name() }; return "" }(), }) }) } // Compile-time interface checks var _ textgen.TextGenerator = (*LaoZhangTextProvider)(nil) var _ textgen.TextStreamer = (*LaoZhangTextProvider)(nil)