feat: persist photos and enrich conversation history
This commit is contained in:
@@ -7,6 +7,7 @@ from collections import deque
|
||||
from collections.abc import Awaitable, Callable
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Literal, Protocol
|
||||
from uuid import uuid4
|
||||
|
||||
from loguru import logger
|
||||
from pipecat.frames.frames import (
|
||||
@@ -17,6 +18,7 @@ from pipecat.frames.frames import (
|
||||
StopFrame,
|
||||
)
|
||||
from pipecat.processors.frame_processor import FrameDirection, FrameProcessor
|
||||
from pipecat.utils.time import time_now_iso8601
|
||||
|
||||
|
||||
class ClientToolError(RuntimeError):
|
||||
@@ -29,6 +31,7 @@ ClientToolResponseWaitMode = Literal["timeout", "session"]
|
||||
@dataclass(frozen=True)
|
||||
class _PendingClientToolCall:
|
||||
future: asyncio.Future[dict[str, Any]]
|
||||
function_name: str
|
||||
interrupt_on_result: bool = False
|
||||
|
||||
|
||||
@@ -80,8 +83,6 @@ class ClientToolBroker(FrameProcessor):
|
||||
response_wait_mode: ClientToolResponseWaitMode = "timeout",
|
||||
interrupt_on_result: bool = False,
|
||||
) -> dict[str, Any]:
|
||||
from uuid import uuid4
|
||||
|
||||
if self._closed_message is not None:
|
||||
raise ClientToolError(self._closed_message)
|
||||
|
||||
@@ -99,9 +100,21 @@ class ClientToolBroker(FrameProcessor):
|
||||
OutputTransportMessageUrgentFrame(message=message)
|
||||
)
|
||||
except Exception as exc:
|
||||
await self._emit_trace(
|
||||
"client_tool_failed",
|
||||
toolCallId=tool_call_id,
|
||||
functionName=function_name,
|
||||
error="客户端工具调用发送失败",
|
||||
)
|
||||
raise ClientToolError(
|
||||
f"客户端工具调用发送失败: {function_name}"
|
||||
) from exc
|
||||
await self._emit_trace(
|
||||
"client_tool_completed",
|
||||
toolCallId=tool_call_id,
|
||||
functionName=function_name,
|
||||
status="dispatched",
|
||||
)
|
||||
return {
|
||||
"status": "ok",
|
||||
"data": {"dispatched": True},
|
||||
@@ -111,6 +124,7 @@ class ClientToolBroker(FrameProcessor):
|
||||
future: asyncio.Future[dict[str, Any]] = loop.create_future()
|
||||
self._pending[tool_call_id] = _PendingClientToolCall(
|
||||
future=future,
|
||||
function_name=function_name,
|
||||
interrupt_on_result=interrupt_on_result,
|
||||
)
|
||||
try:
|
||||
@@ -121,6 +135,12 @@ class ClientToolBroker(FrameProcessor):
|
||||
return await future
|
||||
return await asyncio.wait_for(future, timeout=timeout_seconds)
|
||||
except TimeoutError as exc:
|
||||
await self._emit_trace(
|
||||
"client_tool_failed",
|
||||
toolCallId=tool_call_id,
|
||||
functionName=function_name,
|
||||
error="客户端工具调用超时",
|
||||
)
|
||||
raise ClientToolError(f"客户端工具调用超时: {function_name}") from exc
|
||||
except ClientToolError:
|
||||
raise
|
||||
@@ -163,10 +183,28 @@ class ClientToolBroker(FrameProcessor):
|
||||
|
||||
future = pending.future
|
||||
status = str(message.get("status") or "error")
|
||||
result_data = message.get("data")
|
||||
user_action = (
|
||||
str(result_data.get("action") or "")
|
||||
if isinstance(result_data, dict)
|
||||
else ""
|
||||
)
|
||||
await self._emit_trace(
|
||||
"client_tool_completed" if status == "ok" else "client_tool_failed",
|
||||
toolCallId=tool_call_id,
|
||||
functionName=pending.function_name,
|
||||
status=status,
|
||||
**({"userAction": user_action} if user_action else {}),
|
||||
**(
|
||||
{"error": str(message.get("message") or "客户端工具执行失败")}
|
||||
if status != "ok"
|
||||
else {}
|
||||
),
|
||||
)
|
||||
if status == "ok":
|
||||
response = {
|
||||
"status": "ok",
|
||||
"data": message.get("data"),
|
||||
"data": result_data,
|
||||
}
|
||||
if pending.interrupt_on_result:
|
||||
# Match text input exactly: broadcasting only starts the
|
||||
@@ -206,6 +244,23 @@ class ClientToolBroker(FrameProcessor):
|
||||
}
|
||||
)
|
||||
|
||||
async def _emit_trace(self, event: str, **details: Any) -> None:
|
||||
"""Publish sanitized client interaction metadata for conversation history."""
|
||||
try:
|
||||
await self.push_frame(
|
||||
OutputTransportMessageUrgentFrame(
|
||||
message={
|
||||
"type": "workflow-event",
|
||||
"eventId": f"wfe_{uuid4().hex[:20]}",
|
||||
"event": event,
|
||||
"timestamp": time_now_iso8601(),
|
||||
**details,
|
||||
}
|
||||
)
|
||||
)
|
||||
except Exception as exc: # noqa: BLE001 - tracing must not alter the tool call
|
||||
logger.warning(f"发送客户端工具轨迹失败,不影响工具调用: {exc}")
|
||||
|
||||
def on_interruption_processed(self) -> None:
|
||||
"""Release one result after the aggregator acknowledges interruption."""
|
||||
while self._deferred_results:
|
||||
|
||||
Reference in New Issue
Block a user