Skip to content

Migrate observability to Langfuse v4 SDK - #45

Open
kapitmax07 wants to merge 5 commits into
ed-donner:mainfrom
kapitmax07:langfuse-v4-obs
Open

Migrate observability to Langfuse v4 SDK#45
kapitmax07 wants to merge 5 commits into
ed-donner:mainfrom
kapitmax07:langfuse-v4-obs

Conversation

@kapitmax07

Copy link
Copy Markdown

Summary

  • Replace the logfire-based tracing shim with the official Langfuse v4 SDK and OpenInference OpenAI Agents instrumentation, applied consistently across planner, tagger, reporter, charter, retirement, and the researcher App Runner service.
  • Each lambda_handler now tags traces with user_id/session_id so a full portfolio-analysis job groups into one Langfuse session, and adds request/response PII masking (email/phone/card).
  • Reporter's judge/evaluator step uses start_evaluator() with a proper NUMERIC score instead of a raw span.
  • Replace the old time.sleep(15) post-flush workaround with a real blocking client.flush() call before the Lambda execution environment freezes.
  • Add backend/researcher/observability.py plus a Langfuse smoke-test script, and wire LANGFUSE_* env vars into the 4_researcher and 6_agents Terraform modules.
  • Fix terraform/5_database: restore the aws_secretsmanager_secret resource (a local edit had dropped it, which would break terraform apply on a fresh deploy), and widen the tfstate gitignore pattern to catch timestamped backup files.
  • Fix a trace/user_id inconsistency in charter, reporter, and retirement: the job used to set observe()'s user_id was being re-fetched and reassigned later, which could diverge if the first lookup failed but the second succeeded.

Test plan

  • uv run test_langfuse_smoke.py in backend/researcher against a configured Langfuse project
  • Deploy 6_agents and 4_researcher with LANGFUSE_* vars set, run a portfolio analysis job, confirm a session trace appears in Langfuse
  • terraform plan in 5_database shows no changes on a fresh checkout

…tate gitignore

The aws_secretsmanager_secret resource had been dropped locally in favor of
a hardcoded secret_id on aws_secretsmanager_secret_version, which only
creates a version inside an already-existing secret. A fresh terraform
apply (as Guide 5 walks students through) would fail with
ResourceNotFoundException since nothing actually creates the secret.
Reverts the working tree back to the already-correct version committed
on this branch.

Also widen the tfstate gitignore pattern from *.tfstate.backup to
*.tfstate.* so timestamped backup files (which contain DB credentials
in plaintext) are excluded too.
Replace the old logfire-based tracing shim with the official Langfuse v4
Python SDK and OpenInference OpenAI Agents instrumentation, applied
consistently to planner, tagger, reporter, charter, retirement, and the
researcher App Runner service.

- observability.py: idempotent setup_instrumentation(), observe()/
  observation() context managers, PII masking (email/phone/card) via
  Langfuse's mask and mask_otel_spans hooks, and environment tagging
  (production/development).
- Each lambda_handler.py now loads the job owner up front and tags
  traces with user_id/session_id/tags so agent runs group under one
  Langfuse session per job.
- Reporter's judge/evaluator step now uses start_evaluator() with a
  proper NUMERIC score instead of a raw span.
- Replace the old time.sleep(15) post-flush workaround with a real
  blocking client.flush() call before the Lambda execution environment
  freezes.
- Add researcher/observability.py and a smoke-test script for App
  Runner, and wire LANGFUSE_* env vars into the 4_researcher and
  6_agents Terraform modules.
- Bump langfuse to >=4 and add openinference-instrumentation-openai-agents
  across all agent uv projects.
charter, reporter, and retirement each fetched the job once to set
observe()'s user_id, then unconditionally re-fetched it again inside the
handler and reassigned user_id from that second result. If the first
lookup failed but the second succeeded, the Langfuse trace would be
recorded with user_id=None while the agent actually ran for a known
user. Reuse the already-fetched job instead of re-querying, which also
drops a redundant Data API round-trip per invocation.
Fixes several issues found while debugging a failed local frontend
start on Windows: a UTF-8 encoding crash when stdout isn't a real
console, too-short a startup timeout for cold Next.js compiles, orphaned
backend/frontend processes left behind because proc.terminate() only
kills the immediate uv/cmd.exe wrapper (not its child server) on Windows,
and false "process died" shutdowns triggered by that same wrapper exiting
on its own while the real server keeps running fine.
…lock

backend/api/main.py logs via logging.basicConfig(), which defaults to
stderr. run_local.py captured that stderr with its own subprocess.PIPE
but never read it, so once enough log lines accumulated the OS pipe
buffer filled up and the backend's next logging call blocked forever -
freezing its single-threaded event loop (and every request, including
/health) with no error ever logged. Fix: merge backend stderr into
stdout and continuously drain it via a background thread, matching the
pattern already used for the frontend.

Found and verified with py-spy: before the fix, a live hang showed the
backend's only thread stuck inside logging.emit(); after the fix, 800
rapid requests left it idle in the normal asyncio event loop.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant