From b5cc6f9aece6cdcdb757d41845c9545a2cf4a6be Mon Sep 17 00:00:00 2001 From: Marcus <1922576605@qq.com> Date: Sun, 18 Oct 2026 04:36:11 -0700 Subject: [PATCH] =?UTF-8?q?fix=EF=BC=9AJev=20=E8=A7=82=E6=B5=8B=E6=94=B9?= =?UTF-8?q?=E4=B8=BA=20evaluator=EF=BC=8C=E4=BC=9A=E8=AF=9D=E8=A7=86?= =?UTF-8?q?=E5=9B=BE=E4=B8=8D=E5=86=8D=E8=A2=AB=20JSON=20=E5=88=86?= =?UTF-8?q?=E7=B1=BB=E7=BB=93=E6=9E=9C=E5=8D=A0=E4=BD=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/clients/decision.py | 6 +++--- app/observability/tracing.py | 40 +++++++++++++++++------------------- 2 files changed, 22 insertions(+), 24 deletions(-) diff --git a/app/clients/decision.py b/app/clients/decision.py index d9256f1..bc57dcd 100644 --- a/app/clients/decision.py +++ b/app/clients/decision.py @@ -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 diff --git a/app/observability/tracing.py b/app/observability/tracing.py index 00118d4..8e5fe33 100644 --- a/app/observability/tracing.py +++ b/app/observability/tracing.py @@ -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, ) -- 2.54.0