[Bug]: res:// orphan resource aliases leak into streaming responses, causing broken images in chat
Affected Component
Backend (Go), Frontend
Bug Description
In knowledge-base Q&A, the streaming response (SSE) sometimes contains undecoded res://XXXX resource aliases (e.g. res://0007), causing broken images in the chat UI. The browser cannot resolve the res:// protocol, so affected images render as broken.
Some images load correctly (those referenced as images/imageFileXXXX.png or properly expanded /files/xxx paths), while others fail (referenced as raw res://XXXX aliases that survived decoding).
Root Cause
WeKnora uses a two-layer registry system for resource references:
llmresource.Registry- compactsresource://handles into short aliasesres://XXXX(e.g.res://0001) before sending to the LLM, and restores them viaStreamDecoderin the response.llmreference.Registry- handles<ref/>citation tags and expandsresource://handles to accessible/files/xxxURLs.
The pipeline in chat_completion_stream.go:
chatMessages, sourceRefs := prepareMessagesWithReferences(ctx, chatManage)
resourceRefs := llmresource.NewRegistry()
chatMessages = sourceRefs.EncodeMessages(chatMessages)
chatMessages = resourceRefs.EncodeMessages(chatMessages)
// ...
answerDecoder := llmresource.NewStreamDecoder(resourceRefs)
answerRefExpander := llmreference.NewStreamExpander(sourceRefs)
response.Content = answerRefExpander.Feed(answerDecoder.Feed(response.Content))The bug: The LLM sometimes emits res://XXXX aliases that do not exist in the request-local registry (hallucination). StreamDecoder.DecodeText() cannot map them back to resource:// handles, so these orphan aliases pass through as literal text into the SSE stream. The browser cannot resolve res:// protocol -> broken images.
Key Code Locations
Non-streaming path (chat_completion.go:74):
if orphans := resourceRefs.OrphanAliases(chatResponse.Content); len(orphans) > 0 {
pipelineWarn(ctx, "Completion", "orphan_resource_aliases", ...)
}Only logs a warning - does not strip orphan aliases from the content.
Streaming path (chat_completion_stream.go:114-222):
answerDecoder := llmresource.NewStreamDecoder(resourceRefs)
answerRefExpander := llmreference.NewStreamExpander(sourceRefs)No OrphanAliases check at all - orphan aliases flow directly into SSE events.
Agent path (agent/think.go:119):
if orphans := e.resourceRefs.OrphanAliases(result.Content); len(orphans) > 0 {
logger.Warnf(ctx, "[Agent][Stream] Model emitted %d unresolvable resource alias(es): %v", ...)
}Also only logs a warning, does not strip.
Reproduction
- Deploy WeKnora v0.7.0 with local storage (
STORAGE_TYPE=local) - Upload a PDF document containing images to a knowledge base
- Ask a question that causes the model to reference document images in the answer
- Observe that some images in the response are broken
- Inspect the DOM - broken images have
src="res://0007"(or similar) instead of a proper URL
Reproduction Rate
Approximately 30-50% of image-containing responses. Longer context and more resource references increase the likelihood.
Expected Behavior
Orphan res://XXXX aliases should be stripped from the response content before sending to the frontend, or at minimum replaced with a placeholder. The streaming path should have the same OrphanAliases guard as the non-streaming path.
Suggested Fixes
- Add
OrphanAliasescheck to the streaming path: AfterflushDecodersand eachResponseTypeAnswerchunk, detect and strip orphan aliases. - Strip in
DecodeText/StreamDecoder.Feed: Replace unresolvableres://XXXXtokens with empty string or placeholder text instead of passing them through. - Frontend fallback: Markdown renderer should gracefully degrade
res://URLs (hide image or show "image load failed").
Environment
- WeKnora v0.7.0
- Docker deployment (5 containers: frontend/app/postgres/redis/docreader)
- Storage type: local
- LLM: doubao-seed-2.0-lite (Volcengine Ark)
Related
- PR #2075 introduced the resource registry and alias system
- PR #2278 (
fix(agent): resolve request-local source handles) addresses a related but different alias resolution issue in the Agent path - Issue #2179 (
think tags leak into chat responses) is a similar pattern - LLM output not being sanitized in the streaming pipeline
Source: Tencent/WeKnora