feat(observability): 트레이스에 model(metadata) + 사용 태그 — skill:<이름> / wiki / mcp:<함수>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
823292cbb6
commit
727486e7cb
@@ -228,6 +228,7 @@ async def _finalize(session: ChatSession, state: TurnState, started: float, *, f
|
||||
usage: dict | None = None
|
||||
title = state.title
|
||||
tool_parts: list[dict] = []
|
||||
model = ""
|
||||
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"]
|
||||
@@ -236,6 +237,7 @@ async def _finalize(session: ChatSession, state: TurnState, started: float, *, f
|
||||
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"]
|
||||
model = info.get("modelID") or ""
|
||||
full = "".join(p.get("text", "") for p in parts if p.get("type") == "text" and not p.get("synthetic"))
|
||||
if full:
|
||||
content = full
|
||||
@@ -276,9 +278,9 @@ async def _finalize(session: ChatSession, state: TurnState, started: float, *, f
|
||||
t0 = datetime.fromtimestamp(started, timezone.utc).isoformat()
|
||||
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,
|
||||
tags=["codeassist", *langfuse.usage_tags(tool_parts)], start=t0, end=langfuse.now_iso(),
|
||||
usage=langfuse.usage_of(usage["input"], usage["output"]) if usage else None, model=model,
|
||||
metadata={"model": model, **({"elapsed_ms": usage["elapsed_ms"]} if usage else {})}, error=failed,
|
||||
)
|
||||
langfuse.send_later([root, *_tool_spans(tid, root, tool_parts)])
|
||||
return usage, title
|
||||
|
||||
@@ -150,6 +150,35 @@ def tool_span(trace_id: str, name: str, *, parent: dict, input=None, output=None
|
||||
return _span(trace_id, name, attrs, start=start, end=end, parent=parent["spanId"], error=error)
|
||||
|
||||
|
||||
def usage_tags(tool_parts: list[dict]) -> list[str]:
|
||||
"""이 턴에 뭘 써서 답했는지 태그로 — Phoenix 에서 metadata.tags 로 필터. 순서 유지·중복 제거.
|
||||
skill:<이름> = OpenCode skill 도구, 또는 read 로 skills/<이름>/SKILL.md 를 연 것(ABAP_OPENCODE 방식)
|
||||
wiki = wiki/ 아래 파일을 읽음(CodeAssist 위키)
|
||||
mcp:<함수> = MCP 도구 호출. OpenCode 는 "<서버>_<함수>" 로 이름 붙임 → 서버 접두어 떼고 함수만"""
|
||||
import re
|
||||
|
||||
out: list[str] = []
|
||||
for p in tool_parts:
|
||||
tool = p.get("tool") or ""
|
||||
inp = (p.get("state") or {}).get("input") or {}
|
||||
path = str(inp.get("filePath") or inp.get("path") or "").replace("\\", "/")
|
||||
name = inp.get("name") if tool == "skill" else None
|
||||
if not name and tool == "read":
|
||||
m = re.search(r"skills/([^/]+)/SKILL\.md$", path)
|
||||
name = m.group(1) if m else None
|
||||
if name:
|
||||
out.append(f"skill:{name}")
|
||||
if "wiki/" in path:
|
||||
out.append("wiki")
|
||||
for server in MCP_SERVERS:
|
||||
if tool.startswith(server + "_"):
|
||||
out.append("mcp:" + tool[len(server) + 1:])
|
||||
return list(dict.fromkeys(out))
|
||||
|
||||
|
||||
MCP_SERVERS = ("sap-icf",) # opencode.json 의 mcp 이름들. 새 MCP 붙이면 여기 추가
|
||||
|
||||
|
||||
def ms_iso(ms: int | float | None) -> str | None:
|
||||
"""OpenCode 의 epoch ms → ISO. 없으면 None(=지금)."""
|
||||
return datetime.fromtimestamp(ms / 1000, timezone.utc).isoformat() if ms else None
|
||||
|
||||
Reference in New Issue
Block a user