| 458 | } |
| 459 | |
| 460 | func generateStructuredTitleWithUsage( |
| 461 | ctx context.Context, |
| 462 | model fantasy.LanguageModel, |
| 463 | systemPrompt string, |
| 464 | userInput string, |
| 465 | ) (string, fantasy.Usage, error) { |
| 466 | userInput = strings.TrimSpace(userInput) |
| 467 | if userInput == "" { |
| 468 | return "", fantasy.Usage{}, xerrors.New("title input was empty") |
| 469 | } |
| 470 | |
| 471 | prompt := fantasy.Prompt{ |
| 472 | { |
| 473 | Role: fantasy.MessageRoleSystem, |
| 474 | Content: []fantasy.MessagePart{ |
| 475 | fantasy.TextPart{Text: systemPrompt}, |
| 476 | }, |
| 477 | }, |
| 478 | { |
| 479 | Role: fantasy.MessageRoleUser, |
| 480 | Content: []fantasy.MessagePart{ |
| 481 | fantasy.TextPart{Text: userInput}, |
| 482 | }, |
| 483 | }, |
| 484 | } |
| 485 | |
| 486 | var maxOutputTokens int64 = 256 |
| 487 | var result *fantasy.ObjectResult[generatedTitle] |
| 488 | err := chatretry.Retry(ctx, func(retryCtx context.Context) error { |
| 489 | var genErr error |
| 490 | result, genErr = object.Generate[generatedTitle](retryCtx, model, fantasy.ObjectCall{ |
| 491 | Prompt: prompt, |
| 492 | SchemaName: "propose_title", |
| 493 | SchemaDescription: "Propose a short chat title.", |
| 494 | MaxOutputTokens: &maxOutputTokens, |
| 495 | }) |
| 496 | return genErr |
| 497 | }, nil) |
| 498 | if err != nil { |
| 499 | var usage fantasy.Usage |
| 500 | var noObjErr *fantasy.NoObjectGeneratedError |
| 501 | if errors.As(err, &noObjErr) { |
| 502 | usage = noObjErr.Usage |
| 503 | } |
| 504 | return "", usage, xerrors.Errorf("generate structured title: %w", err) |
| 505 | } |
| 506 | |
| 507 | title := normalizeTitleOutput(result.Object.Title) |
| 508 | if err := validateGeneratedTitle(title); err != nil { |
| 509 | return "", result.Usage, err |
| 510 | } |
| 511 | return title, result.Usage, nil |
| 512 | } |
| 513 | |
| 514 | func validateGeneratedTitle(title string) error { |
| 515 | if title == "" { |