mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat(chatd): use lightweight model candidates for title generation (#22605)
## Problem Title generation uses the same model the user selected for chat. This breaks when: 1. **Thinking/extended thinking models** — `ToolChoice: None` conflicts with extended thinking on Anthropic. The bare call has no thinking config, so provider-level defaults can conflict. 2. **Expensive models** — User picks `o3` or `claude-opus-4`, and a trivial 8-word title generation burns through tokens/cost unnecessarily. 3. **Provider quirks** — Different providers have different constraints around thinking mode + tool choice combinations. ## Solution Modeled after how `coder/mux` handles this with `NAME_GEN_PREFERRED_MODELS` + ordered candidate fallback: ### Phase 1: Candidate model list with fallback - New `TitleModelFunc` type returns an ordered list of candidate models - Tries `claude-haiku-4-5` → `gpt-4o-mini` → user's model - Gracefully skips unavailable candidates (missing API key, provider not configured) - Falls back to the user's chat model as last resort ### Phase 2: Provider-safe call options - Removed `ToolChoice: None` which conflicts with extended thinking on some providers - Added `MaxOutputTokens: 256` to cap token usage - Improved title prompt with verb-noun format guidance (`Fix sidebar layout`, `Add user authentication`) and explicit no-markdown/no-code-fences instructions ### Files changed - `coderd/chatd/title.go` — Candidate loop, improved prompt, safe call options - `coderd/chatd/chatd.go` — Build `TitleModelFunc` closure with lightweight candidates
This commit is contained in:
@@ -1904,7 +1904,7 @@ func (p *Server) runChat(
|
||||
chat database.Chat,
|
||||
logger slog.Logger,
|
||||
) error {
|
||||
model, modelConfig, err := p.resolveChatModel(ctx, chat)
|
||||
model, modelConfig, providerKeys, err := p.resolveChatModel(ctx, chat)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -1926,7 +1926,7 @@ func (p *Server) runChat(
|
||||
p.inflight.Add(1)
|
||||
go func() {
|
||||
defer p.inflight.Done()
|
||||
p.maybeGenerateChatTitle(context.WithoutCancel(ctx), chat, messages, model, logger)
|
||||
p.maybeGenerateChatTitle(context.WithoutCancel(ctx), chat, messages, model, providerKeys, logger)
|
||||
}()
|
||||
|
||||
prompt, err := chatprompt.ConvertMessages(messages)
|
||||
@@ -2406,17 +2406,17 @@ func (p *Server) persistChatContextSummary(
|
||||
func (p *Server) resolveChatModel(
|
||||
ctx context.Context,
|
||||
chat database.Chat,
|
||||
) (fantasy.LanguageModel, database.ChatModelConfig, error) {
|
||||
) (fantasy.LanguageModel, database.ChatModelConfig, chatprovider.ProviderAPIKeys, error) {
|
||||
dbConfig, err := p.resolveModelConfig(ctx, chat)
|
||||
if err != nil {
|
||||
return nil, database.ChatModelConfig{}, xerrors.Errorf(
|
||||
return nil, database.ChatModelConfig{}, chatprovider.ProviderAPIKeys{}, xerrors.Errorf(
|
||||
"resolve model config: %w", err,
|
||||
)
|
||||
}
|
||||
|
||||
providers, err := p.db.GetEnabledChatProviders(ctx)
|
||||
if err != nil {
|
||||
return nil, database.ChatModelConfig{}, xerrors.Errorf(
|
||||
return nil, database.ChatModelConfig{}, chatprovider.ProviderAPIKeys{}, xerrors.Errorf(
|
||||
"get enabled chat providers: %w", err,
|
||||
)
|
||||
}
|
||||
@@ -2438,11 +2438,11 @@ func (p *Server) resolveChatModel(
|
||||
dbConfig.Provider, dbConfig.Model, keys,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, database.ChatModelConfig{}, xerrors.Errorf(
|
||||
return nil, database.ChatModelConfig{}, chatprovider.ProviderAPIKeys{}, xerrors.Errorf(
|
||||
"create model: %w", err,
|
||||
)
|
||||
}
|
||||
return model, dbConfig, nil
|
||||
return model, dbConfig, keys, nil
|
||||
}
|
||||
|
||||
// resolveModelConfig looks up the chat's model config by its
|
||||
|
||||
Reference in New Issue
Block a user