feat(langfuse): 목록 한 줄 = 채팅(질문/답/토큰), 트리 = OpenCode 도구 호출(SAP 조회·위키 read·grep) 순서대로
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
07d733ea30
commit
c9cc6b7eeb
@@ -192,6 +192,21 @@ class TurnState:
|
||||
return out
|
||||
|
||||
|
||||
def _tool_spans(tid: str, root: dict, tool_parts: list[dict]) -> list[dict]:
|
||||
"""OpenCode 도구 파트 → Langfuse 자식 span. 출력은 앞 4KB 만(원문은 OpenCode 에 있음)."""
|
||||
out = []
|
||||
for p in tool_parts:
|
||||
st = p.get("state") or {}
|
||||
t = st.get("time") or {}
|
||||
out.append(langfuse.tool_span(
|
||||
tid, p.get("tool") or "tool", parent=root, input=st.get("input"), title=st.get("title") or "",
|
||||
output=(st.get("output") or "")[:4000] or None,
|
||||
start=langfuse.ms_iso(t.get("start")), end=langfuse.ms_iso(t.get("end")),
|
||||
error=st.get("error") if st.get("status") == "error" else None,
|
||||
))
|
||||
return out
|
||||
|
||||
|
||||
def _real_title(raw) -> str | None:
|
||||
"""OpenCode 기본 제목("New session - 2026-…")은 제목이 아님 → None."""
|
||||
t = (raw or "").strip()
|
||||
@@ -212,12 +227,15 @@ async def _finalize(session: ChatSession, state: TurnState, started: float, *, f
|
||||
content = state.accumulated()
|
||||
usage: dict | None = None
|
||||
title = state.title
|
||||
tool_parts: list[dict] = []
|
||||
try:
|
||||
msgs = await opencode_service.list_messages_a(session.id) or []
|
||||
assistant = [m for m in msgs if (m.get("info") or {}).get("role") == "assistant"]
|
||||
if assistant:
|
||||
info = assistant[-1]["info"]
|
||||
parts = assistant[-1].get("parts") or []
|
||||
# 이 턴의 assistant 메시지가 여러 개(도구 왕복마다 하나)라 도구 파트는 전부 모음
|
||||
tool_parts = [p for m in assistant for p in (m.get("parts") or []) if p.get("type") == "tool"]
|
||||
full = "".join(p.get("text", "") for p in parts if p.get("type") == "text" and not p.get("synthetic"))
|
||||
if full:
|
||||
content = full
|
||||
@@ -251,25 +269,18 @@ async def _finalize(session: ChatSession, state: TurnState, started: float, *, f
|
||||
session.title_llm = title[:200]
|
||||
await session.asave(update_fields=["is_generating", "title_llm", "updated_at"])
|
||||
|
||||
# 관측: 사용자 단위 trace 하나 + 답변 generation 하나. 실패해도 여기까진 이미 저장됨.
|
||||
# 관측: 목록 한 줄 = 이 채팅(질문·답·토큰). 트리 = 답 만들며 부른 도구들(SAP 조회·위키 read…) 순서대로.
|
||||
if langfuse.enabled():
|
||||
tid = f"{session.id}-{int(started * 1000)}"
|
||||
user_email = await sync_to_async(lambda: session.user.email)()
|
||||
meta = {"failed": failed} if failed else {}
|
||||
t0 = datetime.fromtimestamp(started, timezone.utc).isoformat()
|
||||
root = langfuse.trace(tid, "chat", userId=user_email, sessionId=session.id, input=state.user_text,
|
||||
output=content, metadata=meta, tags=["codeassist"], start=t0, end=langfuse.now_iso())
|
||||
langfuse.send_later([
|
||||
root,
|
||||
langfuse.generation(
|
||||
tid, "opencode-turn", parent=root,
|
||||
startTime=t0, endTime=langfuse.now_iso(),
|
||||
input=state.user_text, output=content,
|
||||
usage=langfuse.usage_of(usage["input"], usage["output"]) if usage else None,
|
||||
level="ERROR" if failed else "DEFAULT", statusMessage=failed or "",
|
||||
metadata={"elapsed_ms": usage["elapsed_ms"]} if usage else {},
|
||||
),
|
||||
])
|
||||
root = langfuse.trace(
|
||||
tid, "chat", userId=user_email, sessionId=session.id, input=state.user_text, output=content,
|
||||
tags=["codeassist"], start=t0, end=langfuse.now_iso(),
|
||||
usage=langfuse.usage_of(usage["input"], usage["output"]) if usage else None,
|
||||
metadata={"elapsed_ms": usage["elapsed_ms"]} if usage else {}, error=failed,
|
||||
)
|
||||
langfuse.send_later([root, *_tool_spans(tid, root, tool_parts)])
|
||||
return usage, title
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user