Merge pull request 'fix:Jev 观测改为 evaluator,会话视图不再被 JSON 分类结果占位' (#14) from feat/0.3.0 into main
my-v0-server / deploy (push) Successful in 45s

Reviewed-on: #14
This commit was merged in pull request #14.
This commit is contained in:
2026-10-18 19:39:45 +08:00
2 changed files with 22 additions and 24 deletions
+3 -3
View File
@@ -63,8 +63,8 @@ class DecisionEngine:
from app.observability.tracing import observe_decision, record_decision
# 观测包在请求外面:成功才记结果,失败仍抛原来的 DecisionError
# generation 为 None 时 record_decision 什么都不做
with observe_decision(request) as generation:
# observation 为 None 时 record_decision 什么都不做
with observe_decision(request) as observation:
try:
response = await client.system_one(
state=request.state,
@@ -93,7 +93,7 @@ class DecisionEngine:
except TypeSafeError as error:
raise DecisionError("Jev SDK 调用失败", kind="sdk") from error
parsed = self._parse_response(request, response)
record_decision(generation, parsed)
record_decision(observation, parsed)
return parsed
@staticmethod
+19 -21
View File
@@ -1,6 +1,6 @@
"""一次大屏图调用的 Langfuse 追踪
不属于处理管道的任一步。把图调用收成可过滤的 trace,并把 Jev 记成当前节点下的 generation
不属于处理管道的任一步。把图调用收成可过滤的 trace,并把 Jev 记成当前节点下的 evaluator
每轮调用单独记录,首次请求与澄清恢复通过 session 关联,不修改业务状态
"""
@@ -100,7 +100,7 @@ def _screen_trace(payload, thread_id, session_id, user_id=None):
# 明确回调所属项目,避免进程中存在多个客户端时跳过追踪
handler = CallbackHandler(public_key=os.getenv("LANGFUSE_PUBLIC_KEY"))
# 在当前异步上下文执行回调,手动记录的 Jev generation 才会挂到所属节点。
# 在当前异步上下文执行回调,手动记录的 Jev 观测才会挂到所属节点。
handler.run_inline = True
resume = isinstance(payload, Command) and payload.resume is not None
trace_input = (
@@ -162,9 +162,10 @@ def _run_output(state: dict[str, Any]) -> dict[str, Any]:
@contextmanager
def observe_decision(request: Any) -> Iterator[Any]:
"""在当前节点下面记一次 Jev 调用,yield 的是 generation,未追踪时是 None
"""在当前节点下面记一次 Jev 调用,yield 的是观测对象,未追踪时是 None
TypeSafe 不走 LangChain callback,所以要手记。类型必须是 generation,面板才能算模型和 token
TypeSafe 不走 LangChain callback,所以要手记。类型不能是 generation:会话视图只用 generation
拼对话,且只显示最浅的一条对话线,Jev 比 agent 的模型调用浅,用 generation 会把真正的对话挤掉
只有图回调已经挂上当前 span 时才记。没有父 span 就跳过,避免 Jev 单独变成一条 trace
这里不吞异常:失败时标 ERROR 后原样抛出,调用方的 DecisionError 类型不变
"""
@@ -177,7 +178,7 @@ def observe_decision(request: Any) -> Iterator[Any]:
name = "classify-intent" if set(request.decisions) == {"intent"} else "run-decision"
# 复用本应用已经选定的客户端
with client.start_as_current_observation(
as_type="generation",
as_type="evaluator",
name=name,
model=model,
input={
@@ -187,11 +188,11 @@ def observe_decision(request: Any) -> Iterator[Any]:
},
"state": request.state,
},
) as generation:
) as observation:
try:
yield generation
yield observation
except Exception as error:
generation.update(
observation.update(
level="ERROR",
status_message=str(error),
metadata=_error_metadata(error),
@@ -199,29 +200,26 @@ def observe_decision(request: Any) -> Iterator[Any]:
raise
def record_decision(generation: Any, response: Any) -> None:
"""把成功的 Jev 结果写回刚才那条 generation
def record_decision(observation: Any, response: Any) -> None:
"""把成功的 Jev 结果写回刚才那条观测
generation 为 None 表示这次没追踪,直接返回,不影响判断结果
observation 为 None 表示这次没追踪,直接返回,不影响判断结果
只写选项、置信度和 token。不写原始 HTTP 响应
"""
if generation is None:
if observation is None:
return
usage: dict[str, int] = {}
# evaluator 没有 model/usage 字段,模型和 token 只能放 metadata
metadata = {"model": response.model, "request_id": response.request_id or ""}
if response.usage.input_tokens is not None:
usage["input_tokens"] = response.usage.input_tokens
metadata["input_tokens"] = str(response.usage.input_tokens)
if response.usage.output_tokens is not None:
usage["output_tokens"] = response.usage.output_tokens
metadata["output_tokens"] = str(response.usage.output_tokens)
generation.update(
model=response.model,
observation.update(
output={
name: _result_summary(result) for name, result in response.results.items()
},
usage_details=usage or None,
metadata={
"request_id": response.request_id or "",
},
metadata=metadata,
)