feat(observability): Langfuse 전송 — 턴 마무리(사용자 trace)·게이트웨이(FabriX generation) 두 훅, SDK 없이 HTTP. deploy/langfuse 에 compose+env+반입 절차

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
lee-hyeon-cheol
2026-09-22 13:44:12 +09:00
co-authored by Claude Fable 5.1
parent 95b169a7b9
commit 0321050053
13 changed files with 617 additions and 0 deletions
+16
View File
@@ -126,6 +126,22 @@ async def test_401_retries_other_auth_shape_and_remembers(upstream):
assert len(calls) == n + 1 and calls[-1].headers["x-openapi-token"] == "Bearer tok"
@override_settings(FABRIX_ENV=FULL_ENV, LANGFUSE={"host": "http://lf.test", "public_key": "pk", "secret_key": "sk"})
async def test_stream_reports_generation_to_langfuse(upstream, monkeypatch):
import asyncio
from apps.gateway import langfuse
got = []
monkeypatch.setattr(langfuse, "TRANSPORT", httpx.MockTransport(lambda r: (got.append(json.loads(r.content)), httpx.Response(207, json={}))[1]))
upstream(lambda r: _sse(b'data: {"choices":[{"delta":{"content":"hi"}}],"usage":{"prompt_tokens":2,"completion_tokens":1}}\n\ndata: [DONE]\n\n'))
resp = await _post({"model": "581", "stream": True, "messages": [{"role": "user", "content": "q"}]})
b"".join([c async for c in resp.streaming_content])
await asyncio.gather(*list(langfuse._pending))
gen = got[0]["batch"][1]["body"]
assert gen["model"] == "581" and gen["output"] == "hi" and gen["usage"]["total"] == 3 and gen["input"][0]["content"] == "q"
@override_settings(FABRIX_ENV=FULL_ENV)
async def test_flaky_401_on_right_auth_is_retried_once(upstream, monkeypatch):
"""FabriX 가 맞는 조합에도 가끔 401 — 4개 다 돈 뒤 맨 앞 조합 한 번 더."""