Files
stonks/internal/analyze/api.go
T
2026-09-05 10:25:39 +08:00

78 lines
1.9 KiB
Go

package analyze
import (
"context"
"fmt"
"strings"
"github.com/anthropics/anthropic-sdk-go"
"gitea.konchin.com/service/stonks/internal/config"
)
func init() {
Register("api", newAPIAnalyzer)
}
const defaultAPIModel = anthropic.ModelClaudeOpus5
// apiAnalyzer calls the Claude Messages API directly, billed per token.
// Reads credentials the standard way (ANTHROPIC_API_KEY / ANTHROPIC_AUTH_TOKEN
// / an `ant auth login` profile) via the zero-arg client constructor.
type apiAnalyzer struct {
client anthropic.Client
model anthropic.Model
effort anthropic.OutputConfigEffort
}
func newAPIAnalyzer(cfg config.AnalyzerConfig) (Analyzer, error) {
model := anthropic.Model(cfg.Model)
if cfg.Model == "" {
model = defaultAPIModel
}
effort := anthropic.OutputConfigEffortMedium
if cfg.Effort != "" {
effort = anthropic.OutputConfigEffort(cfg.Effort)
}
return &apiAnalyzer{
client: anthropic.NewClient(),
model: model,
effort: effort,
}, nil
}
func (a *apiAnalyzer) Analyze(ctx context.Context, prompt string) (string, error) {
resp, err := a.client.Messages.New(ctx, anthropic.MessageNewParams{
Model: a.model,
MaxTokens: 4096,
System: []anthropic.TextBlockParam{{Text: SystemPrompt}},
OutputConfig: anthropic.OutputConfigParam{
Effort: a.effort,
},
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock(prompt)),
},
})
if err != nil {
return "", fmt.Errorf("analyze (api mode): %w", err)
}
if resp.StopReason == anthropic.StopReasonRefusal {
return "", fmt.Errorf("analyze (api mode): request refused by safety classifiers")
}
var out strings.Builder
for _, block := range resp.Content {
if text, ok := block.AsAny().(anthropic.TextBlock); ok {
out.WriteString(text.Text)
}
}
if out.Len() == 0 {
return "", fmt.Errorf("analyze (api mode): response contained no text content (stop_reason=%s)", resp.StopReason)
}
return out.String(), nil
}