generateFromAnthropic uses Claude (Anthropic) to generate semantic task and display names from a user prompt. It sends the prompt to Claude with a structured system prompt requesting JSON output containing both names. Returns an error if the API call fails, the response is invalid, or Claude returns
(ctx context.Context, prompt string, apiKey string, model anthropic.Model, opts ...anthropicoption.RequestOption)
| 209 | // It sends the prompt to Claude with a structured system prompt requesting JSON output containing both names. |
| 210 | // Returns an error if the API call fails, the response is invalid, or Claude returns an "unnamed" placeholder. |
| 211 | func generateFromAnthropic(ctx context.Context, prompt string, apiKey string, model anthropic.Model, opts ...anthropicoption.RequestOption) (TaskName, error) { |
| 212 | anthropicModel := model |
| 213 | if anthropicModel == "" { |
| 214 | anthropicModel = defaultModel |
| 215 | } |
| 216 | if apiKey == "" { |
| 217 | return TaskName{}, ErrNoAPIKey |
| 218 | } |
| 219 | |
| 220 | conversation := []aisdk.Message{ |
| 221 | { |
| 222 | Role: "system", |
| 223 | Parts: []aisdk.Part{{ |
| 224 | Type: aisdk.PartTypeText, |
| 225 | Text: systemPrompt, |
| 226 | }}, |
| 227 | }, |
| 228 | { |
| 229 | Role: "user", |
| 230 | Parts: []aisdk.Part{{ |
| 231 | Type: aisdk.PartTypeText, |
| 232 | Text: prompt, |
| 233 | }}, |
| 234 | }, |
| 235 | } |
| 236 | |
| 237 | anthropicOptions := anthropic.DefaultClientOptions() |
| 238 | anthropicOptions = append(anthropicOptions, anthropicoption.WithAPIKey(apiKey)) |
| 239 | anthropicOptions = append(anthropicOptions, opts...) |
| 240 | anthropicClient := anthropic.NewClient(anthropicOptions...) |
| 241 | |
| 242 | stream, err := anthropicDataStream(ctx, anthropicClient, anthropicModel, conversation) |
| 243 | if err != nil { |
| 244 | return TaskName{}, xerrors.Errorf("create anthropic data stream: %w", err) |
| 245 | } |
| 246 | |
| 247 | var acc aisdk.DataStreamAccumulator |
| 248 | stream = stream.WithAccumulator(&acc) |
| 249 | |
| 250 | if err := stream.Pipe(io.Discard); err != nil { |
| 251 | return TaskName{}, xerrors.Errorf("pipe data stream") |
| 252 | } |
| 253 | |
| 254 | if len(acc.Messages()) == 0 { |
| 255 | return TaskName{}, ErrNoNameGenerated |
| 256 | } |
| 257 | |
| 258 | // Parse the JSON response. LLMs sometimes wrap JSON in |
| 259 | // markdown code fences (```json ... ```), so we strip |
| 260 | // those before unmarshalling. |
| 261 | var taskNameResponse TaskName |
| 262 | if err := json.Unmarshal([]byte(extractJSON(acc.Messages()[0].Content)), &taskNameResponse); err != nil { |
| 263 | return TaskName{}, xerrors.Errorf("failed to parse anthropic response: %w", err) |
| 264 | } |
| 265 | |
| 266 | taskNameResponse.Name = strings.TrimSpace(taskNameResponse.Name) |
| 267 | taskNameResponse.DisplayName = strings.TrimSpace(taskNameResponse.DisplayName) |
| 268 |