feat: record stream end mode in telemetry

llm_calls gains a nullable stream_end TEXT column (auto backfill,
manual-mode trimming as in 1.3.8). TelemetryRecorder.record_llm_call
takes a new required keyword stream_end; the emitter reads it from the
response on attempt rows and writes NULL for failed, cache-hit and
terminal rows.

Red: 18 tests in tests/unit/test_telemetry.py and test_ports.py failed
against the pre-change source (missing column, 38-field signature,
stream_end absent from emitted rows).
This commit is contained in:
2026-10-14 23:54:11 -04:00
parent 48ba36dd94
commit 779a9b29a3
7 changed files with 152 additions and 25 deletions
+12 -4
View File
@@ -72,6 +72,8 @@ _EXPECTED_COLUMNS = [
# —— 1.3.8 沉默归因两列(issue #27)——
"stall_ms",
"stall_count",
# —— 1.3.10 结束方式(#31)——
"stream_end",
]
# 1.3.5 之前那张表的 27 个物理列(26 个 INSERT 字段 + created_at)。写成固定切片
@@ -173,6 +175,8 @@ def _minimal_fields(call_id: str | None = None, **overrides) -> dict[str, object
# —— 1.3.8 两列: 沉默归因只属流式 chat 成功 attempt 行(issue #27),普通行 NULL ——
"stall_ms": None,
"stall_count": None,
# —— 1.3.10: 结束方式只属流式成功 attempt 行,普通行 NULL ——
"stream_end": None,
}
fields.update(overrides)
return fields
@@ -775,7 +779,7 @@ class TestCallerDimensionsAcceptance:
"WHERE table_schema = $1 AND table_name = 'llm_calls' ORDER BY ordinal_position",
schema,
)
# 22 → 38 个 recorder 字段(加 created_at 共 39 个物理列),且新列追加在末尾
# 22 → 39 个 recorder 字段(加 created_at 共 40 个物理列),且新列追加在末尾
assert [r["column_name"] for r in cols] == _EXPECTED_COLUMNS
rows = await _fetch(
schema_dsn,
@@ -1106,7 +1110,7 @@ class TestCallObservabilityColumnsAcceptance:
"""
async def test_pre_135_table_gains_the_ten_columns_and_old_rows_stay_null(self, pre_135_schema):
"""27 列旧表 auto 补齐到 39 列(1.3.5 十列 + 1.3.8 两列),新行两类取值读得回,**历史行新增列一律 NULL**。
"""27 列旧表 auto 补齐到 40 列(1.3.5 十列 + 1.3.8 两列 + 1.3.10 一列),新行两类取值读得回,**历史行新增列一律 NULL**。
旧行不回填是本版的明示决策(设计 §5): NULL 表达的是"补列之前根本没记过
这件事",与任何哨兵值都不是一回事。若哪天有人给这十列加了 DEFAULT,历史行
@@ -1148,7 +1152,7 @@ class TestCallObservabilityColumnsAcceptance:
"WHERE table_schema = $1 AND table_name = 'llm_calls' ORDER BY ordinal_position",
schema,
)
# 26 → 38 个 recorder 字段(加 created_at 共 39 个物理列),新列追加在末尾:
# 26 → 39 个 recorder 字段(加 created_at 共 40 个物理列),新列追加在末尾:
# 列序与新建库一致才不会让两条升级路径分叉
assert [r["column_name"] for r in cols] == _EXPECTED_COLUMNS
@@ -1176,6 +1180,8 @@ class TestCallObservabilityColumnsAcceptance:
# 1.3.8 两列: 普通成功 attempt 行无沉默观测 → NULL
"stall_ms": None,
"stall_count": None,
# 1.3.10: 该行未传结束方式 → NULL
"stream_end": None,
}
assert dict(by_id["term"]) == {
"call_id": "term",
@@ -1192,6 +1198,8 @@ class TestCallObservabilityColumnsAcceptance:
# 1.3.8 两列: 终态行无 transport 观测 → NULL
"stall_ms": None,
"stall_count": None,
# 1.3.10: 终态行无流可言 → NULL
"stream_end": None,
}
# 历史行: 十列逐列 NULL(整体比对,漏掉任一列都红)
assert dict(by_id["pre135"]) == {
@@ -1349,7 +1357,7 @@ class TestPublishedSchemaScript:
await _execute_script(fresh_dsn, script)
actual = [r["column_name"] for r in await _fetch(fresh_dsn, _PHYSICAL_COLUMNS_SQL, schema)]
# 物理列 = 38 个 INSERT 字段 + 库从不显式写的 created_at;对着库常量比,不另抄一份
# 物理列 = 39 个 INSERT 字段 + 库从不显式写的 created_at;对着库常量比,不另抄一份
assert set(actual) == set(COLUMNS) | {"created_at"}
# 列序也不许漂: 新列必须排在 created_at 之后,否则新建库与 ALTER 升级的列序分叉
assert actual == _EXPECTED_COLUMNS