fix: stop citing web search results as sources (#29631)

A web search is not something that can be cited. The search engine
returns a title, a link and a one line snippet for each hit, and the
model never opens any of those pages. Emitting them as citation sources
produced one <source> tag per result, all named search_web with empty
bodies, and the citation template then instructed the model to cite them
by id. Models either hesitated visibly or attached an id to content from
a different result, which the citations panel then resolved to a title
that looked plausible, so the misattribution read as correct.

Web search results now stay in the tool output the model reads, and stop
being offered as things to cite. Where the model needs to cite a page it
calls fetch_url, whose citation names the URL and already works.

Web search results no longer appear in the citations panel. That is the
point of the change: the panel was offering pages that nothing had read.

Scoped to the native tool-calling path. The legacy handler cites every
tool result as one opaque source and does not single out web search, so
it is left alone rather than special-cased.
This commit is contained in:
Classic298 2026-09-04 17:28:18 +02:00 committed by GitHub
parent 0b804c5316
commit c7cce962d2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -400,9 +400,6 @@ def get_citation_source_from_tool_result(
Returns a list of sources (usually one, but query_knowledge_files/query_chat_files may return multiple).
"""
_EXPECTS_LIST = {'search_web', 'query_knowledge_files', 'query_chat_files'}
_EXPECTS_DICT = {'view_knowledge_file', 'view_file'}
try:
try:
tool_result = JSONCodec.loads(tool_result)
@ -411,41 +408,10 @@ def get_citation_source_from_tool_result(
if isinstance(tool_result, dict) and 'error' in tool_result:
return []
# Validate tool_result type based on what the branch expects
if tool_name in _EXPECTS_LIST and not isinstance(tool_result, list):
return []
elif tool_name in _EXPECTS_DICT and not isinstance(tool_result, dict):
return []
if tool_name in ('view_knowledge_file', 'view_file'):
if not isinstance(tool_result, dict):
return []
if tool_name == 'search_web':
# Parse JSON array: [{"title": "...", "link": "...", "snippet": "..."}]
results = tool_result
documents = []
metadata = []
for result in results:
title = result.get('title', '')
link = result.get('link', '')
snippet = result.get('snippet', '')
documents.append(f'{title}\n{snippet}')
metadata.append(
{
'source': link,
'name': title,
'url': link,
}
)
return [
{
'source': {'name': 'search_web', 'id': 'search_web'},
'document': documents,
'metadata': metadata,
}
]
elif tool_name in ('view_knowledge_file', 'view_file'):
file_data = tool_result
filename = file_data.get('filename', 'Unknown File')
file_id = file_data.get('id', '')
@ -490,6 +456,9 @@ def get_citation_source_from_tool_result(
]
elif tool_name in ('query_knowledge_files', 'query_chat_files'):
if not isinstance(tool_result, list):
return []
chunks = tool_result
# Group chunks by source for better citation display
@ -5772,7 +5741,6 @@ async def streaming_chat_response_handler(response, ctx):
citations_enabled
and tool_function_name
in [
'search_web',
'fetch_url',
'view_file',
'view_knowledge_file',