feat: record call observability columns and terminal failure rows
Grow the telemetry contract from 26 to 36 fields and give every logical call a failure terminal row, so SQL can finally answer "how many calls failed" and "why did the whole pool die". Schema and port move together with the emitter writes in one commit: splitting them would ship columns that nothing populates. - schema: append 10 nullable columns (scope, operation, logical_call_id, event_kind, http_status_code, error_type, cause_type, error_body, attempts, total_latency_ms) to all five definition sites in one order - ports: 10 keyword-only parameters without defaults; the protocol signature is now the single source the assembly gate derives from - emitter: take domain exception objects instead of pre-flattened text and pin down the diagnostics in one helper; a relabelled 503 stays 503 and success rows leave all five columns NULL - emitter: reject recorders whose record_llm_call cannot accept the current field shape at assembly time, since _record would otherwise swallow the TypeError and drop every row while calls keep succeeding - clients: write at most one terminal row per logical call through a single shared exit, deduplicated by the call context; TelemetryMW stops writing terminals so the two sites cannot double count - clients: cancellation stays best effort and propagates, non-domain exceptions get no terminal row and keep their classification - transports: give _status_to_error an explicit operation and fix the historically mislabelled embedding HTTP failures - structured: promote the bounded error formatter so the reask feedback and the terminal explanation share one set of limits Terminal rows carry no cost and no tokens, so cost aggregation is unchanged; failure counts must now filter on event_kind.
This commit is contained in:
@@ -14,6 +14,8 @@ from typing import TYPE_CHECKING
|
||||
from polygateway.errors import ResultInvalidError
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Sequence
|
||||
|
||||
from polygateway.ports import CallNext, StructuredOutputStrategy
|
||||
from polygateway.types import ChatRequest, LLMResponse
|
||||
|
||||
@@ -22,12 +24,18 @@ _FEEDBACK_TEMPLATE = (
|
||||
"Your previous reply was not valid JSON matching the required schema. "
|
||||
"Errors: {errors}. Reply with ONLY the corrected JSON object."
|
||||
)
|
||||
_MAX_FEEDBACK_ERRORS = 3
|
||||
_MAX_ERROR_CHARS = 200
|
||||
MAX_FEEDBACK_ERRORS = 3
|
||||
MAX_ERROR_CHARS = 200
|
||||
|
||||
|
||||
def _format_errors(errors: list[str]) -> str:
|
||||
clipped = [e[:_MAX_ERROR_CHARS] for e in errors[:_MAX_FEEDBACK_ERRORS]]
|
||||
def format_bounded_errors(errors: Sequence[str]) -> str:
|
||||
"""校验错误的有界拼装: 至多 3 条 × 每条 200 字符。
|
||||
|
||||
**本模块是这条规则的所有者**: 重问反馈文案与 1.3.5 终态行的结构化说明
|
||||
两个消费者共用同一份实现与同一组数值——数值复制成两份必然漂移,而漂移后
|
||||
"模型看到的错误"与"台账里记的错误"就不再是同一件事。行为与重命名前逐字相同。
|
||||
"""
|
||||
clipped = [e[:MAX_ERROR_CHARS] for e in list(errors)[:MAX_FEEDBACK_ERRORS]]
|
||||
return "; ".join(clipped) if clipped else "output could not be parsed"
|
||||
|
||||
|
||||
@@ -104,7 +112,10 @@ class StructuredMW:
|
||||
messages = [
|
||||
*current.messages,
|
||||
{"role": "assistant", "content": bad_content},
|
||||
{"role": "user", "content": _FEEDBACK_TEMPLATE.format(errors=_format_errors(errors))},
|
||||
{
|
||||
"role": "user",
|
||||
"content": _FEEDBACK_TEMPLATE.format(errors=format_bounded_errors(errors)),
|
||||
},
|
||||
]
|
||||
reask = dataclasses.replace(current, messages=messages)
|
||||
if self._escalation is not None:
|
||||
|
||||
Reference in New Issue
Block a user