feat(agent): 增加 LLM 路由与诊断日志
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import logging
|
||||
|
||||
from django.db.models import Q, QuerySet
|
||||
from django.conf import settings
|
||||
@@ -8,12 +9,12 @@ from django.utils import timezone
|
||||
|
||||
from .file_summary.skills.attachment_reader import AttachmentReaderSkill
|
||||
from .file_summary.workflow import create_file_summary_batch, start_file_summary_workflow
|
||||
from .file_summary.workflow_trigger import (
|
||||
evaluate_attachment_reader_trigger,
|
||||
evaluate_file_summary_trigger,
|
||||
)
|
||||
from .llm import LLMConfigurationError, LLMRequestError, generate_reply, stream_reply
|
||||
from .models import Conversation, FileAttachment, Message
|
||||
from .skill_router import route_message_intent
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def list_conversations(user, search: str = "") -> QuerySet[Conversation]:
|
||||
@@ -54,6 +55,14 @@ def append_user_message(conversation: Conversation, content: str) -> Message:
|
||||
role=Message.Role.USER,
|
||||
content=content.strip(),
|
||||
)
|
||||
logger.info(
|
||||
"User message appended",
|
||||
extra={
|
||||
"conversation_id": conversation.pk,
|
||||
"message_id": message.pk,
|
||||
"content_length": len(message.content),
|
||||
},
|
||||
)
|
||||
|
||||
if conversation.messages.filter(role=Message.Role.USER).count() == 1:
|
||||
conversation.title = build_conversation_title(content)
|
||||
@@ -65,11 +74,20 @@ def append_user_message(conversation: Conversation, content: str) -> Message:
|
||||
def append_assistant_message(conversation: Conversation, content: str) -> Message:
|
||||
"""Appends the deterministic assistant reply."""
|
||||
|
||||
return Message.objects.create(
|
||||
message = Message.objects.create(
|
||||
conversation=conversation,
|
||||
role=Message.Role.ASSISTANT,
|
||||
content=content,
|
||||
)
|
||||
logger.info(
|
||||
"Assistant message appended",
|
||||
extra={
|
||||
"conversation_id": conversation.pk,
|
||||
"message_id": message.pk,
|
||||
"content_length": len(content or ""),
|
||||
},
|
||||
)
|
||||
return message
|
||||
|
||||
|
||||
def send_message(conversation: Conversation, content: str) -> tuple[Message, Message]:
|
||||
@@ -95,8 +113,18 @@ def stream_message(conversation: Conversation, content: str):
|
||||
|
||||
user_message = append_user_message(conversation, content)
|
||||
assistant_parts: list[str] = []
|
||||
trigger = evaluate_file_summary_trigger(conversation, content)
|
||||
attachment_reader_trigger = evaluate_attachment_reader_trigger(conversation, content)
|
||||
route = route_message_intent(conversation, content)
|
||||
logger.info(
|
||||
"Stream message started",
|
||||
extra={
|
||||
"conversation_id": conversation.pk,
|
||||
"user_message_id": user_message.pk,
|
||||
"route_action": route.action,
|
||||
"route_source": route.source,
|
||||
"route_confidence": route.confidence,
|
||||
"route_reason": route.reason,
|
||||
},
|
||||
)
|
||||
|
||||
yield sse_event(
|
||||
"meta",
|
||||
@@ -108,7 +136,7 @@ def stream_message(conversation: Conversation, content: str):
|
||||
},
|
||||
)
|
||||
|
||||
if trigger.reason == "missing_attachment":
|
||||
if route.starts_file_summary and not _has_active_attachments(conversation):
|
||||
reply_content = "请先在当前对话右侧上传需要汇总的文件或压缩包,然后再发送自动汇总指令。"
|
||||
assistant_message = append_assistant_message(conversation, reply_content)
|
||||
yield sse_event("chunk", {"delta": reply_content})
|
||||
@@ -122,7 +150,7 @@ def stream_message(conversation: Conversation, content: str):
|
||||
)
|
||||
return
|
||||
|
||||
if attachment_reader_trigger.reason == "missing_attachment":
|
||||
if route.uses_attachment_reader and not _has_active_attachments(conversation):
|
||||
reply_content = "请先在当前对话右侧上传需要阅读的附件,然后再发送解析或阅读附件指令。"
|
||||
assistant_message = append_assistant_message(conversation, reply_content)
|
||||
yield sse_event("chunk", {"delta": reply_content})
|
||||
@@ -136,8 +164,16 @@ def stream_message(conversation: Conversation, content: str):
|
||||
)
|
||||
return
|
||||
|
||||
if attachment_reader_trigger.should_start:
|
||||
if route.uses_attachment_reader:
|
||||
attachments = _select_attachments_for_reader(conversation, content)
|
||||
logger.info(
|
||||
"Attachment reader path selected",
|
||||
extra={
|
||||
"conversation_id": conversation.pk,
|
||||
"attachment_count": len(attachments),
|
||||
"attachment_ids": [attachment.pk for attachment in attachments],
|
||||
},
|
||||
)
|
||||
result = AttachmentReaderSkill().run_for_attachments(attachments)
|
||||
reply_content = _format_attachment_reader_reply(result.data.get("attachments", []), result.message)
|
||||
assistant_message = append_assistant_message(conversation, reply_content)
|
||||
@@ -152,7 +188,7 @@ def stream_message(conversation: Conversation, content: str):
|
||||
)
|
||||
return
|
||||
|
||||
if trigger.should_start:
|
||||
if route.starts_file_summary:
|
||||
batch = create_file_summary_batch(
|
||||
conversation=conversation,
|
||||
user=conversation.user,
|
||||
@@ -190,6 +226,18 @@ def stream_message(conversation: Conversation, content: str):
|
||||
except (LLMConfigurationError, LLMRequestError) as exc:
|
||||
fallback = f"模型调用失败:{exc}"
|
||||
assistant_parts = [fallback]
|
||||
logger.warning(
|
||||
"LLM stream failed",
|
||||
extra={"conversation_id": conversation.pk, "error": str(exc)},
|
||||
)
|
||||
yield sse_event("error", {"message": fallback})
|
||||
except Exception as exc:
|
||||
fallback = f"回复生成中断:{exc}"
|
||||
assistant_parts.append("\n\n" + fallback)
|
||||
logger.exception(
|
||||
"Unexpected stream failure",
|
||||
extra={"conversation_id": conversation.pk, "error": str(exc)},
|
||||
)
|
||||
yield sse_event("error", {"message": fallback})
|
||||
|
||||
assistant_message = append_assistant_message(conversation, "".join(assistant_parts).strip())
|
||||
@@ -230,6 +278,14 @@ def _select_attachments_for_reader(conversation: Conversation, content: str):
|
||||
return matched or attachments
|
||||
|
||||
|
||||
def _has_active_attachments(conversation: Conversation) -> bool:
|
||||
return (
|
||||
FileAttachment.objects.filter(conversation=conversation, is_active=True)
|
||||
.exclude(upload_status=FileAttachment.UploadStatus.DELETED)
|
||||
.exists()
|
||||
)
|
||||
|
||||
|
||||
def _format_attachment_reader_reply(attachments: list[dict[str, object]], message: str) -> str:
|
||||
if not attachments:
|
||||
return message or "当前对话没有可读取的附件。"
|
||||
|
||||
Reference in New Issue
Block a user