fix(agent): prevent trailing assistant messages for proxy provider compat

Reorder team task reminders to inject before the user message instead of
after, ensuring the user's message is always the last in the messages
array. Proxy providers (LiteLLM, OpenRouter) reject requests ending with
an assistant-role message (unsupported prefill).

Also add a safety net in the OpenAI provider to strip any trailing
assistant message that might slip through in the future.
This commit is contained in:
viettranx committed 2026-03-20 18:46:27 +07:00
1 parent 0415052872
commit 11673dabf1
2 files changed
+22 -1

No files matched your search

+11 -1
View File
@@ -396,6 +396,8 @@ func (l *Loop) runLoop(ctx context.Context, req RunRequest) (*RunResult, error)
// 2g. Cross-session task reminder: notify team leads about pending and in-progress tasks.
// Stale recovery (expired lock → pending) is handled by the background TaskTicker.
// Reminders are injected BEFORE the user message so the user's actual message is always
// the last message — prevents trailing assistant messages that proxy providers reject.
if l.teamStore != nil && l.agentUUID != uuid.Nil {
if team, _ := l.teamStore.GetTeamForAgent(ctx, l.agentUUID); team != nil && team.LeadAgentID == l.agentUUID {
if tasks, err := l.teamStore.ListTasks(ctx, team.ID, "newest", "active", req.UserID, "", "", 0, 0); err == nil {
@@ -434,16 +436,20 @@ func (l *Loop) runLoop(ctx context.Context, req RunRequest) (*RunResult, error)
}
if len(parts) > 0 {
reminder := "[System] " + strings.Join(parts, "\n\n")
// Pop user message, inject reminder, push user message back
userMsg := messages[len(messages)-1]
messages = messages[:len(messages)-1]
messages = append(messages,
providers.Message{Role: "user", Content: reminder},
providers.Message{Role: "assistant", Content: "I see the task status. Let me handle accordingly."},
userMsg,
)
}
}
}
}
// 2g. Member task reminder: inject task context for members working on dispatched tasks.
// 2h. Member task reminder: inject task context for members working on dispatched tasks.
// Caches task subject/number for mid-loop progress nudge (avoids extra DB query).
var memberTaskSubject string
var memberTaskNumber int
@@ -457,9 +463,13 @@ func (l *Loop) runLoop(ctx context.Context, req RunRequest) (*RunResult, error)
"Stay focused on this task. Your final response becomes the task result — make it clear and complete. "+
"For long tasks, report progress: team_tasks(action=\"progress\", percent=50, text=\"status\").",
task.TaskNumber, task.Subject)
// Pop user message, inject reminder, push user message back
userMsg := messages[len(messages)-1]
messages = messages[:len(messages)-1]
messages = append(messages,
providers.Message{Role: "user", Content: reminder},
providers.Message{Role: "assistant", Content: "Understood. I'll focus on this task and report progress."},
userMsg,
)
}
}
+11
View File
@@ -340,6 +340,17 @@ func (p *OpenAIProvider) buildRequestBody(model string, req ChatRequest, stream
msgs = append(msgs, msg)
}
// Safety net: strip trailing assistant message to prevent HTTP 400 from
// proxy providers (LiteLLM, OpenRouter) that don't support assistant prefill.
// This should rarely trigger — the agent loop ensures user message is last.
if len(msgs) > 0 {
if role, _ := msgs[len(msgs)-1]["role"].(string); role == "assistant" {
slog.Warn("openai: stripped trailing assistant message (unsupported prefill)",
"provider", p.name, "model", model)
msgs = msgs[:len(msgs)-1]
}
}
body := map[string]any{
"model": model,
"messages": msgs,