on main help sign in
fix(title): fallback title generation
46c1799Kieran Klukas
internal/agent/agent.go+44−36
@@ -1637,15 +1637,23 @@ func (a *sessionAgent) generateTitle(ctx context.Context, sessionID string, user
1637 return
1637 return
1638 }
1638 }
1639
1639
1640+ // Ensure the session always gets a title even if every path below
1641+ // fails or the context is cancelled before we finish.
1642+ var titleSaved bool
1643+ defer func() {
1644+ if !titleSaved {
1645+ fallbackCtx, cancel := context.WithTimeout(context.WithoutCancel(ctx), 5*time.Second)
1646+ defer cancel()
1647+ if err := a.sessions.Rename(fallbackCtx, sessionID, DefaultSessionName); err != nil {
1648+ slog.Error("Failed to save fallback session title", "error", err)
1649+ }
1650+ }
1651+ }()
1652+
1640 smallModel := a.smallModel.Get()
1653 smallModel := a.smallModel.Get()
1641 largeModel := a.largeModel.Get()
1654 largeModel := a.largeModel.Get()
1642 systemPromptPrefix := a.systemPromptPrefix.Get()
1655 systemPromptPrefix := a.systemPromptPrefix.Get()
1643
1656
1644− var maxOutputTokens int64 = 40
1645− if smallModel.CatwalkCfg.CanReason {
1646− maxOutputTokens = smallModel.CatwalkCfg.DefaultMaxTokens
1647− }
1648−
1649 newAgent := func(m fantasy.LanguageModel, p []byte, tok int64) fantasy.Agent {
1657 newAgent := func(m fantasy.LanguageModel, p []byte, tok int64) fantasy.Agent {
1650 return fantasy.NewAgent(
1658 return fantasy.NewAgent(
1651 m,
1659 m,
@@ -1668,41 +1676,40 @@ func (a *sessionAgent) generateTitle(ctx context.Context, sessionID string, user
1668 },
1676 },
1669 }
1677 }
1670
1678
1671− // Use the small model to generate the title.
1679+ type modelAttempt struct {
1680+ name string
1681+ model Model
1682+ }
1683+ attempts := []modelAttempt{
1672− model := smallModel
1684+ {"small", smallModel},
1673− agent := newAgent(model.Model, titlePrompt, maxOutputTokens)
1685+ {"large", largeModel},
1674− resp, err := agent.Stream(ctx, streamCall)
1686+ }
1675− if err == nil {
1687+
1676− // We successfully generated a title with the small model.
1688+ var resp *fantasy.AgentResult
1677− slog.Debug("Generated title with small model")
1689+ var err error
1678− } else {
1690+ var model Model
1679− // It didn't work. Let's try with the big model.
1691+ var success bool
1680− slog.Error("Error generating title with small model; trying big model", "err", err)
1692+ for _, attempt := range attempts {
1681− model = largeModel
1693+ tok := int64(40)
1694+ if attempt.model.CatwalkCfg.CanReason {
1695+ tok = attempt.model.CatwalkCfg.DefaultMaxTokens
1696+ }
1682− agent = newAgent(model.Model, titlePrompt, maxOutputTokens)
1697+ agent := newAgent(attempt.model.Model, titlePrompt, tok)
1683 resp, err = agent.Stream(ctx, streamCall)
1698 resp, err = agent.Stream(ctx, streamCall)
1684− if err == nil {
1699+ if err == nil && resp.Response.FinishReason != fantasy.FinishReasonLength {
1700+ model = attempt.model
1685− slog.Debug("Generated title with large model")
1701+ slog.Debug("Generated title with " + attempt.name + " model")
1702+ success = true
1703+ break
1704+ }
1705+ if err != nil {
1706+ slog.Error("Error generating title with "+attempt.name+" model; trying next", "err", err)
1686 } else {
1707 } else {
1687− // Welp, the large model didn't work either. Use the default
1708+ slog.Error("Title generation hit token limit with " + attempt.name + " model; trying next")
1688− // session name and return.
1689− slog.Error("Error generating title with large model", "err", err)
1690− saveErr := a.sessions.Rename(ctx, sessionID, DefaultSessionName)
1691− if saveErr != nil {
1692− slog.Error("Failed to save session title", "error", saveErr)
1693− }
1694− return
1695 }
1709 }
1696 }
1710 }
1697−
1711+ if !success {
1698− if resp == nil {
1712+ // The deferred fallback will save the default session name.
1699− // Actually, we didn't get a response so we can't. Use the default
1700− // session name and return.
1701− slog.Error("Response is nil; can't generate title")
1702− saveErr := a.sessions.Rename(ctx, sessionID, DefaultSessionName)
1703− if saveErr != nil {
1704− slog.Error("Failed to save session title", "error", saveErr)
1705− }
1706 return
1713 return
1707 }
1714 }
1708
1715
@@ -1756,6 +1763,7 @@ func (a *sessionAgent) generateTitle(ctx context.Context, sessionID string, user
1756 slog.Error("Failed to save session title and usage", "error", saveErr)
1763 slog.Error("Failed to save session title and usage", "error", saveErr)
1757 return
1764 return
1758 }
1765 }
1766+ titleSaved = true
1759}
1767}
1760
1768
1761func (a *sessionAgent) openrouterCost(metadata fantasy.ProviderMetadata) *float64 {
1769func (a *sessionAgent) openrouterCost(metadata fantasy.ProviderMetadata) *float64 {