简打卡
51.76M · 2026-03-28
Score 是 Langfuse 中用于存储评估结果的核心数据对象。你可以把它理解为:给一次 LLM 交互打分。它可以挂载到 Trace(一次完整请求)、Observation(某个具体 Span/Generation)、Session(多轮对话)上。
| 类型 | 值域 | 典型场景 |
|---|---|---|
| NUMERIC | 浮点数flaot(如0.92) | 相关性得分、SQL 质量 0-1 |
| CATEGORICAL | 预定义字符串 string(如 "correct", "partial", "wrong") | 分级标签:correct / partial / wrong |
| BOOLEAN | 0 或 1 | sql验证通过/不通过、用户点赞/踩 |
| 来源 | 说明 | 典型场景 |
|---|---|---|
| User Feedback | 用户显式反馈(点赞/踩、评论) | 前端 thumbs up/down |
| Model-Based Evals | 用另一个 LLM 当评委打分 | LLM-as-a-Judge 自动评估 |
| Manual Annotation | 人工在 Langfuse UI 中标注 | 人工审核队列 |
| Custom / Programmatic | 代码计算后通过 SDK 提交 | 验证通过率、检索精确度 |
| 字段 | 类型 | 说明 |
|---|---|---|
| name | string | 分数名称标识(如 "sql_validation") |
| value | float/int/string | 分数值 |
| data_type | enum | NUMERIC、CATEGORICAL、BOOLEAN |
| trace_id | string | 关联到哪条 trace |
| session_id | string | 关联到哪个 session |
| comment | string | 附加说明 |
Session(会话)
└── Trace(单次请求链路) ← trace-level score
└── Observation(单步操作) ← observation-level score
from langfuse import Langfuse
langfuse = Langfuse(public_key="...", secret_key="...", host="...")
# 数值型 score
langfuse.score(
trace_id="trace-xxx",
name="retrieval_precision",
value=0.85,
data_type="NUMERIC",
comment="retrieved=10 used=8"
)
# 布尔型 score
langfuse.score(
trace_id="trace-xxx",
name="sql_validation",
value=1,
data_type="BOOLEAN"
)
手动指定 trace_id 和 observation_id 来打分。最灵活,但需要你自己管理这些 ID。
# Method 2: Score current span/generation (within context)
with langfuse.start_as_current_observation(as_type="span", name="my-operation") as span:
# Score the current span
span.score(
name="correctness",
value=0.9,
data_type="NUMERIC",
comment="Factually correct"
)
# Score the trace
span.score_trace(
name="overall_quality",
value=0.95,
data_type="NUMERIC"
)
通过上下文管理器中的 span 对象打分
with ... as span 做了两件事:
context(上下文) 的含义:Langfuse内部维护了一个线程级别的上下文栈。start_as_current_observation 会把新建的 span 压入栈顶,with块结束时自动弹出。这样在 with 块内部,Langfuse 就知道"当前正在执行的操作是哪个 span"。
# Method 3: Score via the current context
with langfuse.start_as_current_observation(as_type="span", name="my-operation"):
# Score the current span
langfuse.score_current_span(
name="correctness",
value=0.9,
data_type="NUMERIC",
comment="Factually correct"
)
# Score the trace
langfuse.score_current_trace(
name="overall_quality",
value=0.95,
data_type="NUMERIC"
)
langfuse.score_current_span给当前的上下文的span打分langfuse.score_current_trace给当前上下文的trace打分和 Method 2 的区别:不需要持有 span 变量的引用。Langfuse 从内部上下文栈中自动找到当前的 span和trace。 这在深层嵌套调用中很有用——你在某个被调用的函数里,拿不到外层的 span 变量,但仍然可以通过langfuse.score_current_span() 给它打分。
| 打分对象 | 适用场景 | 示例 |
|---|---|---|
| Span | 评价某个具体步骤的质量 | "检索步骤"的相关性得分、"LLM 调用"的准确性 |
| Trace | 评价整条链路的整体质量 | 最终回答的用户满意度、端到端的正确性 |
简单类比:trace 像一次考试的总分,span 像每道题的得分。(那么session就是一次月考的所有科目的总分和)
配置定义在 easysql/config.py文件中
class LangfuseConfig(BaseSettings):
enabled: bool = Field(default=False, alias="langfuse_enabled")
public_key: str | None = Field(default=None, alias="langfuse_public_key")
secret_key: str | None = Field(default=None, alias="langfuse_secret_key")
host: str = Field(default="https://cloud.langfuse.com", ...)
def is_configured(self) -> bool:
return bool(self.enabled and self.public_key and self.secret_key)
在 .env 中设置:
LANGFUSE_ENABLED=true
LANGFUSE_PUBLIC_KEY=pk-lf-xxx
LANGFUSE_SECRET_KEY=sk-lf-xxx
LANGFUSE_HOST=
在每次请求都会创建独立的 CallbackHandler
def create_langfuse_handler(*, session_id=None, tags=None):
from langfuse.langchain import CallbackHandler
kwargs = {}
if session_id:
kwargs["session_id"] = session_id
if tags:
kwargs["tags"] = tags
return CallbackHandler(**kwargs)
将 handler 注入 LangGraph 配置:
def _make_config(self, session_id, thread_id=None):
config = {"configurable": {"thread_id": effective_thread_id}}
if self.langfuse_enabled:
handler = create_langfuse_handler(
session_id=session_id,
tags=["text2sql"],
)
config["callbacks"] = [handler]
config["configurable"]["_langfuse_handler"] = handler
return config
这样 LangGraph 执行过程中所有 LLM 调用、工具调用都会被自动记录为 Langfuse trace 下的observations。
获取traceid
@staticmethod
def _extract_trace_id(config: RunnableConfig) -> str | None:
"""Extract the Langfuse trace ID from the per-request callback handler."""
handler = config.get("configurable", {}).get("_langfuse_handler")
if handler is None:
return None
try:
trace_id: str | None = handler.get_trace_id() # type: ignore[union-attr]
return trace_id
except Exception: # noqa: BLE001
return None
获取到trace后,将traceid传给前端(这是为了方便用户进行生成质量的评分),同时会自动对生成的内容进行评分并提交到langfuse
# Submit automatic scores to Langfuse
trace_id = self._extract_trace_id(config)
if trace_id:
response["langfuse_trace_id"] = trace_id
get_scoring_service().submit_query_scores(trace_id, result)
trace_id 同时被返回给前端(通过 SSE 的 complete 事件或 JSON 响应),以便前端后续提交用户反馈。
Score 名称: sql_validation
数据类型: BOOLEAN
值说明: SQL 是否通过语法/语义验证(1=通过, 0=失败)
# 1. SQL validation (boolean)
self.score_trace(
trace_id,
"sql_validation",
1 if validation_passed else 0,
data_type="BOOLEAN",
)
作用:衡量 LLM 生成的 SQL 是否在语法和语义上正确。这是 Text2SQL 系统最基本的质量指标。在Langfuse dashboard 中,你可以看到 sql_validation 的通过率趋势——如果通过率下降,说明 LLM或prompt 出了问题。
Score 名称: retry_count
数据类型: NUMERIC
值说明: SQL 生成的重试次数(0 表示一次成功)
self.score_trace(
trace_id,
"retry_count",
float(retry_count),
data_type="NUMERIC",
)
作用:记录 SQL 生成了几轮才最终通过验证。retry_count=0 表示一次性生成正确 SQL。这个值直接反映
Score 名称: first_attempt_success
数据类型: BOOLEAN
值说明: 是否首次尝试就成功(retry_count<=1 且 validation_passed)
# 3. First-attempt success (boolean)
self.score_trace(
trace_id,
"first_attempt_success",
1 if retry_count <= 1 and validation_passed else 0,
data_type="BOOLEAN",
)
作用:这是最核心的"黄金指标"。它衡量系统是否能一次性给出正确 SQL。在 Langfuse 中可以直接看到first_attempt_success 的百分比趋势——这是向业务方汇报的最直观指标。
Score 名称: retrieval_precision
数据类型: NUMERIC
值说明: 检索精确度 = 实际被 SQL 使用的表数 / 检索召回的表数
# 4. Retrieval precision – overlap of retrieved vs actually-used tables
retrieval_result = result.get("retrieval_result") or {}
retrieved_tables = set(retrieval_result.get("tables", []))
sql = result.get("generated_sql") or ""
if retrieved_tables and sql:
used_tables = _extract_table_names(sql)
if used_tables:
precision = len(retrieved_tables & used_tables) / len(retrieved_tables)
self.score_trace(
trace_id,
"retrieval_precision",
round(precision, 4),
data_type="NUMERIC",
comment=f"retrieved={len(retrieved_tables)} used={len(used_tables)}",
)
作用:衡量 Milvus 语义检索 + Neo4j FK 扩展后,召回的表有多少真正被 SQL 使用了。例如:
这个指标直接反映 retrieval pipeline 的质量:
数据类型: BOOLEAN
值说明: 本次生成是否使用了 few-shot 示例
few_shot_examples = result.get("few_shot_examples") or []
self.score_trace(
trace_id,
"few_shot_used",
1 if few_shot_examples else 0,
data_type="BOOLEAN",
)
作用:记录本次生成是否利用了 few-shot 示例。结合 first_attempt_success 可以分析:
Score 名称: user_feedback
数据类型: BOOLEAN
触发方式: 用户点击 (1) 或 (0)
@router.post("/feedback/message", response_model=FeedbackResponse)
async def submit_message_feedback(
request: MessageFeedbackRequest,
) -> FeedbackResponse:
"""Submit user feedback for a single generated SQL (trace-level score)."""
scoring = get_scoring_service()
scoring.score_trace(
trace_id=request.trace_id,
name="user_feedback",
value=request.score,
data_type="BOOLEAN",
comment=request.comment,
)
logger.info(
"User feedback submitted: trace_id=%s score=%d",
request.trace_id,
request.score,
)
return FeedbackResponse()
作用:这是最真实的质量信号。即使 SQL 语法正确(validation_passed=true),用户仍可能不满意(SQL逻辑不对、返回的不是想要的数据)。user_feedback 捕获了 validation 无法检测到的语义正确性。
Score 名称: session_satisfaction
数据类型: BOOLEAN
触发方式: 用户对整个 session 打分
@router.post("/feedback/session", response_model=FeedbackResponse)
async def submit_session_feedback(
request: SessionFeedbackRequest,
) -> FeedbackResponse:
"""Submit user feedback for an entire session (session-level score)."""
scoring = get_scoring_service()
scoring.score_session(
session_id=request.session_id,
name="session_satisfaction",
value=request.score,
data_type="BOOLEAN",
comment=request.comment,
)
logger.info(
"Session feedback submitted: session_id=%s score=%d",
request.session_id,
request.score,
)
return FeedbackResponse()
作用:评估多轮对话整体质量。一个 session 可能包含多次 SQL 生成,session_satisfaction反映用户对整个交互流程的满意度。
本项目的 Langfuse Score 集成覆盖了 Text2SQL 系统的完整质量评估链路:
以上所有代码示例均来自我的开源项目 EasySQL —— 一个 Text-to-SQL 智能体分析应用,项目地址:github.com/zaizaizhao/…。项目主要技术栈包括:
项目示例
欢迎 Star ⭐ 和交流、共建!