271 lines
12 KiB
Python
271 lines
12 KiB
Python
"""Feishu signal pipeline mixin."""
|
||
from __future__ import annotations
|
||
|
||
from .shared import *
|
||
|
||
|
||
class FeishuPipelineGateDispatchMixin:
|
||
async def _handle_external_connect_gate(
|
||
self,
|
||
*,
|
||
message: str,
|
||
sender: str,
|
||
source_chat_id: str,
|
||
source_sender_open_id: Optional[str],
|
||
feishu_message_id: Optional[str],
|
||
captured_at: Optional[datetime],
|
||
intent_gate: dict[str, Any],
|
||
process_started_at: float,
|
||
) -> dict[str, Any]:
|
||
occurred_at = captured_at or _utc_now()
|
||
if occurred_at.tzinfo is None:
|
||
occurred_at = occurred_at.replace(tzinfo=CN_TZ)
|
||
fields = intent_gate.get("fields") if isinstance(intent_gate.get("fields"), dict) else {}
|
||
blocking_reason = str(intent_gate.get("clarification_template") or "").strip() or "外部系统接入默认进入待决策队列。"
|
||
queued = await self._queue_command_intent_item(
|
||
message=message,
|
||
sender=sender,
|
||
source_chat_id=source_chat_id,
|
||
source_sender_open_id=source_sender_open_id,
|
||
feishu_message_id=feishu_message_id,
|
||
occurred_at=occurred_at,
|
||
intent_gate=intent_gate,
|
||
operation_code="source.connect",
|
||
operation_payload={"project_id": _safe_int(fields.get("project_id"))},
|
||
blocking_reason=blocking_reason,
|
||
decision_reason="external_connect_requires_manual_decision",
|
||
matched_project_id=_safe_int(fields.get("project_id")),
|
||
)
|
||
await self._write_processing_log(
|
||
feishu_message_id=feishu_message_id,
|
||
feishu_item_id=_safe_int(queued.get("item_id")),
|
||
raw_message=message,
|
||
ai_raw_output=json.dumps({"intent_gate": intent_gate}, ensure_ascii=False),
|
||
project_score=1.0 if fields.get("project_id") is not None else None,
|
||
signal_confidence=0.0 if fields.get("project_id") is not None else None,
|
||
level="command",
|
||
routing_decision=ROUTING_INTENT_GATE_EXTERNAL_CONNECT,
|
||
routing_reason="外部系统接入被收口到待决策队列,不允许自由执行。",
|
||
reasoning_bullets=[
|
||
"intent gate 命中 external_connect。",
|
||
"当前仅允许受控工具链执行。",
|
||
"系统已转待决策,不做任意 HTTP/shell。",
|
||
],
|
||
processing_status="success",
|
||
error_message=None,
|
||
duration_ms=int((time.perf_counter() - process_started_at) * 1000),
|
||
normalized_text=message,
|
||
route=ROUTING_INTENT_GATE_EXTERNAL_CONNECT,
|
||
intent=INTENT_EXTERNAL_CONNECT,
|
||
executor="router",
|
||
operation_code="source.connect",
|
||
reply_source="deterministic_reply",
|
||
)
|
||
return {
|
||
"status": "captured",
|
||
"classification": "command",
|
||
"project_scope": "command",
|
||
"linked_project_ids": [int(fields["project_id"])] if fields.get("project_id") is not None else [],
|
||
"signal_id": None,
|
||
"created_project_id": None,
|
||
"auto_tasks": [],
|
||
"ai_trace_id": f"feishu-external-{queued.get('item_id')}",
|
||
"results": [queued],
|
||
"intent": INTENT_EXTERNAL_CONNECT,
|
||
"clarification_message": queued.get("clarification_message"),
|
||
"direct_reply_required": True,
|
||
"direct_reply_text": str(queued.get("clarification_message") or blocking_reason),
|
||
}
|
||
|
||
async def _maybe_handle_intent_gate_capture(
|
||
self,
|
||
*,
|
||
intent_gate: dict[str, Any],
|
||
message: str,
|
||
sender: str,
|
||
source_chat_id: str,
|
||
source_sender_open_id: Optional[str],
|
||
feishu_message_id: Optional[str],
|
||
captured_at: Optional[datetime],
|
||
inferred_project_id: Optional[int],
|
||
projects: list[dict[str, Any]],
|
||
process_started_at: float,
|
||
normalized_chat_type: str,
|
||
) -> Optional[dict[str, Any]]:
|
||
gate_intent = str(intent_gate.get("intent") or INTENT_SIGNAL_INTAKE)
|
||
if gate_intent == INTENT_SMALLTALK:
|
||
duration_ms = int((time.perf_counter() - process_started_at) * 1000)
|
||
await self._write_processing_log(
|
||
feishu_message_id=feishu_message_id,
|
||
feishu_item_id=None,
|
||
raw_message=message,
|
||
ai_raw_output=json.dumps({"intent_gate": intent_gate}, ensure_ascii=False),
|
||
project_score=None,
|
||
signal_confidence=None,
|
||
level="smalltalk",
|
||
routing_decision=ROUTING_NOISE_FILTERED,
|
||
routing_reason="intent gate 识别为 smalltalk/短确认,不进入 Inbox。",
|
||
reasoning_bullets=[
|
||
"消息不包含可执行主数据动作。",
|
||
"命中 short confirmation / smalltalk。",
|
||
"系统直接忽略,不写通知。",
|
||
],
|
||
processing_status="success",
|
||
error_message=None,
|
||
duration_ms=duration_ms,
|
||
)
|
||
await self.db.commit()
|
||
return self._decorate_capture_result(
|
||
{
|
||
"status": "ignored",
|
||
"reason": ROUTING_NOISE_FILTERED,
|
||
"classification": "smalltalk",
|
||
"project_scope": "smalltalk",
|
||
"linked_project_ids": [],
|
||
"signal_id": None,
|
||
"created_project_id": None,
|
||
"auto_tasks": [],
|
||
"ai_trace_id": f"feishu-smalltalk-{(feishu_message_id or 'noid')[:32]}",
|
||
"results": [],
|
||
},
|
||
chat_type=normalized_chat_type,
|
||
normalized_text=message,
|
||
route=f"{normalized_chat_type}.intent_gate.smalltalk",
|
||
db_mutations=[],
|
||
)
|
||
|
||
if gate_intent == INTENT_HEALTHCHECK:
|
||
healthcheck_capture = await self._build_healthcheck_capture(
|
||
message=message,
|
||
feishu_message_id=feishu_message_id,
|
||
process_started_at=process_started_at,
|
||
)
|
||
await self.db.commit()
|
||
return self._decorate_capture_result(
|
||
healthcheck_capture,
|
||
chat_type=normalized_chat_type,
|
||
normalized_text=message,
|
||
route=f"{normalized_chat_type}.intent_gate.healthcheck",
|
||
db_mutations=[],
|
||
)
|
||
|
||
if gate_intent == INTENT_QUERY:
|
||
rag_service = AdaptiveRAGService(self.db, tenant_id=self.tenant_id)
|
||
conversation_key = self._build_rag_conversation_key(
|
||
source_chat_id=source_chat_id,
|
||
source_sender_open_id=source_sender_open_id,
|
||
sender=sender,
|
||
)
|
||
rag_result = await rag_service.answer_question(
|
||
question=message,
|
||
asked_by=None,
|
||
asked_via="feishu",
|
||
project_id=inferred_project_id,
|
||
source_chat_id=source_chat_id,
|
||
conversation_key=conversation_key,
|
||
)
|
||
qa_capture = await self._build_direct_qa_capture(
|
||
message=message,
|
||
rag_result=rag_result,
|
||
inferred_project_id=inferred_project_id,
|
||
feishu_message_id=feishu_message_id,
|
||
process_started_at=process_started_at,
|
||
route_reason="intent gate 判定为 query,直接走 RAG 直答通道,不进入决策队列。",
|
||
intent_label=INTENT_QUERY,
|
||
)
|
||
await self.db.commit()
|
||
return self._decorate_capture_result(
|
||
qa_capture,
|
||
chat_type=normalized_chat_type,
|
||
normalized_text=message,
|
||
route=f"{normalized_chat_type}.intent_gate.query",
|
||
db_mutations=[],
|
||
)
|
||
|
||
if gate_intent == INTENT_CREATE_PROJECT or (
|
||
gate_intent == INTENT_CLARIFICATION_REQUIRED and str(intent_gate.get("canonical_action") or "") == "project.create"
|
||
):
|
||
project_capture = await self._handle_create_project_gate(
|
||
message=message,
|
||
sender=sender,
|
||
source_chat_id=source_chat_id,
|
||
source_sender_open_id=source_sender_open_id,
|
||
feishu_message_id=feishu_message_id,
|
||
captured_at=captured_at,
|
||
intent_gate=intent_gate,
|
||
process_started_at=process_started_at,
|
||
)
|
||
await self.db.commit()
|
||
return self._decorate_capture_result(
|
||
project_capture,
|
||
chat_type=normalized_chat_type,
|
||
normalized_text=message,
|
||
route=f"{normalized_chat_type}.intent_gate.project_create",
|
||
db_mutations=self._summarize_db_mutations(project_capture),
|
||
)
|
||
|
||
if gate_intent == INTENT_CREATE_TASK or (
|
||
gate_intent == INTENT_CLARIFICATION_REQUIRED and str(intent_gate.get("canonical_action") or "") == "task.create"
|
||
):
|
||
task_capture = await self._handle_create_task_gate(
|
||
message=message,
|
||
sender=sender,
|
||
source_chat_id=source_chat_id,
|
||
source_sender_open_id=source_sender_open_id,
|
||
feishu_message_id=feishu_message_id,
|
||
captured_at=captured_at,
|
||
intent_gate=intent_gate,
|
||
process_started_at=process_started_at,
|
||
)
|
||
await self.db.commit()
|
||
return self._decorate_capture_result(
|
||
task_capture,
|
||
chat_type=normalized_chat_type,
|
||
normalized_text=message,
|
||
route=f"{normalized_chat_type}.intent_gate.task_create",
|
||
db_mutations=self._summarize_db_mutations(task_capture),
|
||
)
|
||
|
||
if gate_intent == INTENT_EXTERNAL_CONNECT:
|
||
external_capture = await self._handle_external_connect_gate(
|
||
message=message,
|
||
sender=sender,
|
||
source_chat_id=source_chat_id,
|
||
source_sender_open_id=source_sender_open_id,
|
||
feishu_message_id=feishu_message_id,
|
||
captured_at=captured_at,
|
||
intent_gate=intent_gate,
|
||
process_started_at=process_started_at,
|
||
)
|
||
await self.db.commit()
|
||
return self._decorate_capture_result(
|
||
external_capture,
|
||
chat_type=normalized_chat_type,
|
||
normalized_text=message,
|
||
route=f"{normalized_chat_type}.intent_gate.external_connect",
|
||
db_mutations=self._summarize_db_mutations(external_capture),
|
||
)
|
||
|
||
command_capture = await self._maybe_block_legacy_command_request(
|
||
message=message,
|
||
sender=sender,
|
||
source_chat_id=source_chat_id,
|
||
source_sender_open_id=source_sender_open_id,
|
||
feishu_message_id=feishu_message_id,
|
||
captured_at=captured_at,
|
||
inferred_project_id=inferred_project_id,
|
||
projects=projects,
|
||
process_started_at=process_started_at,
|
||
)
|
||
if command_capture is not None:
|
||
await self.db.commit()
|
||
return self._decorate_capture_result(
|
||
command_capture,
|
||
chat_type=normalized_chat_type,
|
||
normalized_text=message,
|
||
route=f"{normalized_chat_type}.command",
|
||
db_mutations=self._summarize_db_mutations(command_capture),
|
||
)
|
||
return None
|
||
|