mirror of
https://github.com/coder/coder.git
synced 2026-09-22 05:05:20 +08:00
feat(coderd/x/chatd): parallelize ConvertMessagesWithFiles with g2 errgroup (#24034)
## Summary Move `ConvertMessagesWithFiles` into the `g2` errgroup so prompt conversion runs concurrently with instruction persistence, user prompt resolution, MCP server connections, and workspace MCP tool discovery. ## Problem In `runChat`, the setup before the first LLM `Stream()` call is sequential across two errgroups: ``` g.Wait() // model + messages + MCP configs ConvertMessagesWithFiles() // sequential — blocked on g2 starting g2.Wait() // instructions + user prompt + MCP connect + workspace MCP ``` `ConvertMessagesWithFiles` can take non-trivial time on conversations with file attachments (batch DB resolution), and it was blocking g2 from starting. ## Fix `ConvertMessagesWithFiles` only reads the `messages` slice (available after `g.Wait()`) and resolves file references via the database. No g2 task reads or writes the `prompt` variable. This makes it safe to overlap with g2: ``` g.Wait() g2.Wait() // now includes ConvertMessagesWithFiles in parallel ``` The `InsertSystem` call for parent chats and the `promptErr` check are deferred to after `g2.Wait()`, preserving correctness. <details><summary>Decision log</summary> - `ConvertMessagesWithFiles` is read-only on `messages` — no mutation, safe for concurrent access - `prompt` and `promptErr` are written only by the conversion goroutine, read only after `g2.Wait()` — no data race - Error from prompt conversion is checked immediately after `g2.Wait()`, before any code that uses `prompt` - `chatloop.Run` now uses `:=` instead of `=` since the prior `err` declaration from `prompt, err :=` was removed </details> > Generated by Coder Agents
This commit is contained in:
+20
-11
@@ -3934,14 +3934,6 @@ func (p *Server) runChat(
|
||||
)
|
||||
}()
|
||||
|
||||
prompt, err := chatprompt.ConvertMessagesWithFiles(ctx, messages, p.chatFileResolver(), logger)
|
||||
if err != nil {
|
||||
return result, xerrors.Errorf("build chat prompt: %w", err)
|
||||
}
|
||||
if chat.ParentChatID.Valid {
|
||||
prompt = chatprompt.InsertSystem(prompt, defaultSubagentInstruction)
|
||||
}
|
||||
|
||||
// Detect computer-use subagent via the mode column.
|
||||
isComputerUse := chat.Mode.Valid && chat.Mode.ChatMode == database.ChatModeComputerUse
|
||||
|
||||
@@ -3998,7 +3990,20 @@ func (p *Server) runChat(
|
||||
needsInstructionPersist = true
|
||||
}
|
||||
}
|
||||
// Convert messages to prompt format in parallel with g2 work.
|
||||
// ConvertMessagesWithFiles only reads `messages` (available
|
||||
// after g.Wait()) and resolves file references via the DB.
|
||||
// No g2 task reads or writes `prompt`, so this is safe.
|
||||
var prompt []fantasy.Message
|
||||
var g2 errgroup.Group
|
||||
g2.Go(func() error {
|
||||
var err error
|
||||
prompt, err = chatprompt.ConvertMessagesWithFiles(ctx, messages, p.chatFileResolver(), logger)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("build chat prompt: %w", err)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if needsInstructionPersist {
|
||||
g2.Go(func() error {
|
||||
var persistErr error
|
||||
@@ -4112,8 +4117,12 @@ func (p *Server) runChat(
|
||||
return nil
|
||||
})
|
||||
}
|
||||
// All g2 goroutines return nil; error is discarded.
|
||||
_ = g2.Wait()
|
||||
if err := g2.Wait(); err != nil {
|
||||
return result, err
|
||||
}
|
||||
if chat.ParentChatID.Valid {
|
||||
prompt = chatprompt.InsertSystem(prompt, defaultSubagentInstruction)
|
||||
}
|
||||
if mcpCleanup != nil {
|
||||
defer mcpCleanup()
|
||||
}
|
||||
@@ -4571,7 +4580,7 @@ func (p *Server) runChat(
|
||||
prompt = filterPromptForChainMode(prompt, chainInfo.trailingUserCount)
|
||||
}
|
||||
|
||||
err = chatloop.Run(ctx, chatloop.RunOptions{
|
||||
err := chatloop.Run(ctx, chatloop.RunOptions{
|
||||
Model: model,
|
||||
Messages: prompt,
|
||||
Tools: tools, MaxSteps: maxChatSteps,
|
||||
|
||||
Reference in New Issue
Block a user