Skip to content

fix(agent): suppress empty final content instead of sending literal "..."#1116

Open
nguyennguyenit wants to merge 1 commit intomainfrom
fix/agent-empty-final-content-no-ellipsis
Open

fix(agent): suppress empty final content instead of sending literal "..."#1116
nguyennguyenit wants to merge 1 commit intomainfrom
fix/agent-empty-final-content-no-ellipsis

Conversation

@nguyennguyenit
Copy link
Copy Markdown
Contributor

Summary

  • Bỏ hardcoded "..." fallback ở v2 (internal/agent/loop_finalize.go) và v3 (internal/pipeline/finalize_stage.go) finalize.
  • Coi empty FinalContent như silent reply (suppress delivery, không leak "..." xuống channel).
  • Thêm slog.Warn riêng cho case empty (kèm session/iteration/thinking_len/tool_calls/async_tool_calls) để root cause LLM trả rỗng không bị che bởi NO_REPLY.
  • NO_REPLY token semantics giữ nguyên.

Why

Trace evidence (019e01c2-b12d-7bf7-babc-29833b288f81, agent tra, model qwen3.6-plus qua bailian):

  • LLM trả 596 output tokens với câu trả lời tiếng Việt đầy đủ (~700 chars).
  • Nhưng traces.output_previewsessions.messages[].content đều lưu literal "...".
  • User thấy bot reply "..." 2 lần liên tiếp trong group Zalo.

Root cause proximate là fallback ở finalize: if FinalContent == "" → "...". Bug ảnh hưởng tất cả channel (Telegram/Discord/Zalo/Feishu/WhatsApp/Slack), không Zalo-specific.

Downstream consumer (cmd/gateway_consumer_normal.go:468, cmd/gateway_announce_queue.go:137) đã có sẵn check Content == "" || IsSilentReply(...) để suppress + cleanup placeholder. Fallback "..." đang bypass logic này. Fix này feed đúng empty xuống path đó.

What changed

File Δ What
internal/pipeline/finalize_stage.go +14/-6 Tách isSilentByToken vs isEmptyOutput, gộp thành isSilent; bỏ branch fallback "..."; log warn cho empty
internal/agent/loop_finalize.go +18/-15 Same logic cho v2 path (predefined agents)
internal/pipeline/stages_test.go +70 3 regression tests: empty→suppress, NO_REPLY→suppress, real→passthrough

Test plan

  • go build ./... clean
  • go build -tags sqliteonly ./... clean (desktop edition)
  • go vet ./... clean
  • go test ./internal/pipeline/ ./internal/agent/ pass
  • 3 regression tests pass (TestFinalizeStage_EmptyContent_SuppressesInsteadOfEllipsis, TestFinalizeStage_NoReplyToken_StillSuppresses, TestFinalizeStage_RealContent_PassesThrough)
  • Reproduce trên staging: trigger run với input gần context_window, kiểm tra log agent produced empty final content; suppressing delivery xuất hiện và channel KHÔNG gửi "..."

Unresolved

  • Root cause gốc (vì sao FinalContent rỗng dù LLM trả 596 output tokens) chưa giải - fix này chặn symptom rò ra user, không sửa nguyên nhân Content rỗng. Cần điều tra parser bailian/qwen3.6-plus (có thể đẩy text vào reasoning_content field) qua log warn mới khi reproduce. Tracking riêng nếu confirm.

Behavior change cho consumer

Sau fix:

WARN agent produced empty final content; suppressing delivery
  session=agent:tra:tra-on-zalo:group:7937974080316500889
  iteration=1 thinking_len=1300 tool_calls=0 async_tool_calls=0

→ channel nhận Content="" → không send tin nhắn "..." nữa. Placeholder cleanup vẫn chạy bình thường nếu channel có streaming/placeholder.

…..."

When the agent's final content is empty after sanitization, both v2
loop_finalize.go and v3 finalize_stage.go used to substitute the literal
string "..." and ship it to channels. Users saw stray "..." messages in
group chats (Zalo/Telegram/Discord/etc.) when the LLM produced no usable
visible text (provider parsing quirk, reasoning-only output, etc.).

Treat empty FinalContent as silent (same delivery path as NO_REPLY) and
emit a structured slog.Warn with session/iteration/thinking_len/tool_calls
so the underlying cause stays visible for debugging without leaking
"..." to end users. NO_REPLY semantics unchanged.

Downstream suppression in cmd/gateway_consumer_normal.go and
gateway_announce_queue.go already handles empty Content correctly; the
"..." fallback was bypassing that check.

Adds regression tests for empty / NO_REPLY / real-content paths.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant