feat(chat): add multi-agent mode routing scaffold and telemetry.

This commit is contained in:
CREDO23 2026-04-28 15:35:14 +02:00
parent 78f71c7e3a
commit 7b9a218d62
13 changed files with 742 additions and 58 deletions

View file

@ -22,6 +22,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.future import select
from sqlalchemy.orm import selectinload
from app.agents.new_chat.architecture_mode import resolve_architecture_mode
from app.agents.new_chat.filesystem_selection import (
ClientPlatform,
FilesystemMode,
@ -61,7 +62,10 @@ from app.schemas.new_chat import (
TokenUsageSummary,
)
from app.services.token_tracking_service import record_token_usage
from app.tasks.chat.stream_new_chat import stream_new_chat, stream_resume_chat
from app.tasks.chat.stream_dispatch import (
dispatch_new_chat_stream,
dispatch_resume_chat_stream,
)
from app.users import current_active_user
from app.utils.rbac import check_permission
from app.utils.user_message_multimodal import (
@ -1244,23 +1248,28 @@ async def handle_new_chat(
image_urls = (
[p.as_data_url() for p in request.user_images] if request.user_images else None
)
architecture_mode = resolve_architecture_mode(request.architecture_mode)
return StreamingResponse(
stream_new_chat(
user_query=request.user_query,
search_space_id=request.search_space_id,
chat_id=request.chat_id,
user_id=str(user.id),
llm_config_id=llm_config_id,
mentioned_document_ids=request.mentioned_document_ids,
mentioned_surfsense_doc_ids=request.mentioned_surfsense_doc_ids,
needs_history_bootstrap=thread.needs_history_bootstrap,
thread_visibility=thread.visibility,
current_user_display_name=user.display_name or "A team member",
disabled_tools=request.disabled_tools,
filesystem_selection=filesystem_selection,
request_id=getattr(http_request.state, "request_id", "unknown"),
user_image_data_urls=image_urls,
dispatch_new_chat_stream(
architecture_mode=architecture_mode.value,
stream_kwargs={
"user_query": request.user_query,
"search_space_id": request.search_space_id,
"chat_id": request.chat_id,
"user_id": str(user.id),
"llm_config_id": llm_config_id,
"mentioned_document_ids": request.mentioned_document_ids,
"mentioned_surfsense_doc_ids": request.mentioned_surfsense_doc_ids,
"needs_history_bootstrap": thread.needs_history_bootstrap,
"thread_visibility": thread.visibility,
"current_user_display_name": user.display_name or "A team member",
"disabled_tools": request.disabled_tools,
"filesystem_selection": filesystem_selection,
"request_id": getattr(http_request.state, "request_id", "unknown"),
"user_image_data_urls": image_urls,
"architecture_mode": architecture_mode.value,
},
),
media_type="text/event-stream",
headers={
@ -1458,6 +1467,7 @@ async def regenerate_response(
if request.user_images is not None:
regenerate_image_urls = [p.as_data_url() for p in request.user_images]
architecture_mode = resolve_architecture_mode(request.architecture_mode)
if user_query_to_use is None:
raise HTTPException(
@ -1506,23 +1516,28 @@ async def regenerate_response(
async def stream_with_cleanup():
streaming_completed = False
try:
async for chunk in stream_new_chat(
user_query=str(user_query_to_use),
search_space_id=request.search_space_id,
chat_id=thread_id,
user_id=str(user.id),
llm_config_id=llm_config_id,
mentioned_document_ids=request.mentioned_document_ids,
mentioned_surfsense_doc_ids=request.mentioned_surfsense_doc_ids,
checkpoint_id=target_checkpoint_id,
needs_history_bootstrap=thread.needs_history_bootstrap,
thread_visibility=thread.visibility,
current_user_display_name=user.display_name or "A team member",
disabled_tools=request.disabled_tools,
filesystem_selection=filesystem_selection,
request_id=getattr(http_request.state, "request_id", "unknown"),
user_image_data_urls=regenerate_image_urls or None,
):
stream = dispatch_new_chat_stream(
architecture_mode=architecture_mode.value,
stream_kwargs={
"user_query": str(user_query_to_use),
"search_space_id": request.search_space_id,
"chat_id": thread_id,
"user_id": str(user.id),
"llm_config_id": llm_config_id,
"mentioned_document_ids": request.mentioned_document_ids,
"mentioned_surfsense_doc_ids": request.mentioned_surfsense_doc_ids,
"checkpoint_id": target_checkpoint_id,
"needs_history_bootstrap": thread.needs_history_bootstrap,
"thread_visibility": thread.visibility,
"current_user_display_name": user.display_name or "A team member",
"disabled_tools": request.disabled_tools,
"filesystem_selection": filesystem_selection,
"request_id": getattr(http_request.state, "request_id", "unknown"),
"user_image_data_urls": regenerate_image_urls or None,
"architecture_mode": architecture_mode.value,
},
)
async for chunk in stream:
yield chunk
streaming_completed = True
finally:
@ -1628,6 +1643,7 @@ async def resume_chat(
)
decisions = [d.model_dump() for d in request.decisions]
architecture_mode = resolve_architecture_mode(request.architecture_mode)
# Release the read-transaction so we don't hold ACCESS SHARE locks
# on searchspaces/documents for the entire duration of the stream.
@ -1635,15 +1651,19 @@ async def resume_chat(
await session.close()
return StreamingResponse(
stream_resume_chat(
chat_id=thread_id,
search_space_id=request.search_space_id,
decisions=decisions,
user_id=str(user.id),
llm_config_id=llm_config_id,
thread_visibility=thread.visibility,
filesystem_selection=filesystem_selection,
request_id=getattr(http_request.state, "request_id", "unknown"),
dispatch_resume_chat_stream(
architecture_mode=architecture_mode.value,
stream_kwargs={
"chat_id": thread_id,
"search_space_id": request.search_space_id,
"decisions": decisions,
"user_id": str(user.id),
"llm_config_id": llm_config_id,
"thread_visibility": thread.visibility,
"filesystem_selection": filesystem_selection,
"request_id": getattr(http_request.state, "request_id", "unknown"),
"architecture_mode": architecture_mode.value,
},
),
media_type="text/event-stream",
headers={