Skip to content
Back to skills

Acm Observability

BSecurity

Agent-actionable reference for ACM-AI's 6-tool observability stack. Teaches how to query traces, inspect graph state, debug Pydantic failures, and analyze costs programmatically.

  • 666 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added September 2, 2026
ai-agentspythonbashnodefastapirailsapidocumentation

Works with

  • cli
  • api

Security analysis

B84/100
  • mediumUses curl or wget to download content
  • criticalDownloads and executes remote scripts — classic supply chain attack

Pro shows the line behind each finding and how to fix it

Scanned September 2, 2026

npx -y skills add majiayu000/claude-skill-registry --skill acm-observability --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Acm Observability?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Acm Observability
[![Security: B — Skills Directory](https://www.skillsdirectory.com/api/skills/majiayu000-acm-observability/badge)](https://www.skillsdirectory.com/skills/majiayu000-acm-observability)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: acm-observability
description: Agent-actionable reference for ACM-AI's 6-tool observability stack. Teaches how to query traces, inspect graph state, debug Pydantic failures, and analyze costs programmatically.
---

# ACM-AI Observability Skill

## Decision Tree — Which Tool for Which Problem?

| Problem | Tool | Action |
|---------|------|--------|
| Wrong extraction data | Langfuse | Query trace by `session_id=extraction-{source_id}`, examine LLM input/output spans |
| Prompt iteration | LangSmith | Open Playground, edit prompt, re-run side-by-side |
| Pipeline costing | Langfuse | Aggregate GENERATION observations by model, sum tokens/cost |
| Pydantic parse failure | Logfire (via Langfuse) | Search OTel spans for `pydantic.validate_*` with error status |
| Graph stuck / wrong state | LangGraph API | `GET /threads/{id}/state` at `:2024` |
| Model relationships | erdantic | Run `scripts/generate_model_diagrams.py` -> `docs/diagrams/*.svg` |
| Nested JSON exploration | JSON Crack | Paste JSON at `localhost:8888` |
| Pipeline healthy across runs? | Langfuse | Historical traces, score trends, session list |

## Langfuse Query Patterns

### Authentication

```bash
# All Langfuse API calls use HTTP Basic auth
curl -u "$LANGFUSE_PUBLIC_KEY:$LANGFUSE_SECRET_KEY" \
  "$LANGFUSE_BASE_URL/api/public/traces?sessionId=extraction-{source_id}"
```

Default `LANGFUSE_BASE_URL` is `http://localhost:3000` (self-hosted) or `https://cloud.langfuse.com`.

### Python SDK

```python
from langfuse import Langfuse

client = Langfuse(
    public_key=os.getenv("LANGFUSE_PUBLIC_KEY"),
    secret_key=os.getenv("LANGFUSE_SECRET_KEY"),
    host=os.getenv("LANGFUSE_BASE_URL", "http://localhost:3000"),
)

# List traces for an extraction
traces = client.fetch_traces(session_id=f"extraction-{source_id}")

# Get a specific trace with observations
trace = client.fetch_trace(trace_id)
```

### Common Filters

| Filter | API Parameter | Example |
|--------|--------------|---------|
| By source | `sessionId` | `extraction-source:abc123` |
| By tag | `tags` | `acm-extraction` |
| By date | `fromTimestamp` | `2026-03-01T00:00:00Z` |
| By name | `name` | `acm_extraction` |

### Session ID Convention

From `langfuse_config.py:135`:
```python
"langfuse_session_id": f"extraction-{clean_source_id}"
```

All extraction traces for a source are grouped under `session_id=extraction-{source_id}`.

## LangGraph API Patterns

The LangGraph dev server runs at `http://127.0.0.1:2024`.

### Start the Server

```bash
uv run langgraph dev --no-browser
```

Registered graphs (from `langgraph.json`):
- `acm_extraction` — ACM extraction pipeline
- `supervisor` — Multi-agent supervisor

### Common Endpoints

```bash
# List registered graphs
curl -s http://127.0.0.1:2024/assistants | python -m json.tool

# List threads
curl -s "http://127.0.0.1:2024/threads?limit=10" | python -m json.tool

# Get thread state
curl -s http://127.0.0.1:2024/threads/{thread_id}/state | python -m json.tool

# Get thread history (checkpoints)
curl -s http://127.0.0.1:2024/threads/{thread_id}/history | python -m json.tool

# Dump state to JSON file for JSON Crack
uv run python scripts/dump_state_json.py {thread_id}
```

### Swagger UI

Full API documentation at `http://127.0.0.1:2024/docs`.

## Logfire Safety Guardrails

### NEVER Call instrument_pydantic() Without include={}

Blanket `logfire.instrument_pydantic()` creates an OTel span for EVERY `model_validate()` call. Docling creates 1 `PdfTextCell` per PDF character, causing ~48K traces per extraction.

### Safe Instrumentation Set

```python
import logfire

logfire.instrument_pydantic(
    include={
        "ACMExtractionRecord",
        "BuildingRoomContext",
        "ACMItemRecord",
        "ACMExtractionResult",
        "ACMItemExtractionResult",
        "NormalizedExtractionResult",
    }
)
```

This instruments only ACM domain models (~10-50 traces per run).

### OTel Span Nesting

`langfuse_tracing()` pre-injects OTel trace context via `_try_inject_otel_trace_context()` so Logfire Pydantic validation spans nest under the Langfuse trace (not as orphan top-level traces).

## Wiring Patterns

### Pattern 1: Context Manager (Routers)

Used in FastAPI router endpoints:

```python
from open_notebook.observability.langfuse_config import (
    langfuse_tracing,
    merge_langfuse_into_config,
)

with langfuse_tracing("acm_extraction", source_id=source_id) as (cb, meta):
    config = merge_langfuse_into_config(base_config, cb, meta)
    result = await graph.ainvoke(input_state, config=config)
```

### Pattern 2: Manual Handler (Commands)

Used in background command handlers:

```python
from open_notebook.observability.langfuse_config import (
    get_langfuse_handler,
    build_langfuse_metadata,
    append_langfuse_callback,
    flush_langfuse_handler,
)

handler = get_langfuse_handler()
callbacks = append_langfuse_callback([], handler)
metadata = build_langfuse_metadata(source_id=source_id)
config = {"callbacks": callbacks, "metadata": metadata}
try:
    result = graph.invoke(input_state, config=config)
finally:
    flush_langfuse_handler(handler)
```

### Critical Rule: Callback Placement

Callbacks belong at the **invocation site** (routers, commands), NEVER inside graph node functions. Graph nodes receive callbacks automatically via LangGraph's config propagation.

### Do NOT Modify Pre-Existing Wiring

`acm_extraction.py` and `source_commands.py` have working Langfuse wiring. Do not alter it.

## Cross-Tool Workflow Recipes

### Recipe 1: Debug a Failed Extraction

1. Query Langfuse: `session_id=extraction-{source_id}`
2. Find the trace with errors (look for spans with `statusCode: ERROR`)
3. Examine the LLM input/output in the GENERATION span
4. Check Logfire Pydantic spans for validation failures
5. If graph state needed: `GET /threads/{thread_id}/state` at `:2024`
6. Cross-reference with SurrealDB: `SELECT * FROM acm_record WHERE source_id = "{source_id}"`

### Recipe 2: Compare Costs Across Models

1. Fetch GENERATION observations from Langfuse filtered by date range
2. Group by `model` field
3. Sum `totalCost`, `promptTokens`, `completionTokens`
4. Compare cost-per-record: `totalCost / records_extracted`

### Recipe 3: Debug Pydantic Validation

1. Verify Logfire is initialized: check `LOGFIRE_ENABLED=true` and Langfuse keys set
2. Search Langfuse for OTel spans with name pattern `pydantic.validate_*`
3. Filter for spans with error status
4. Examine the span attributes for validation error details
5. Cross-reference with the LLM output that produced the invalid data

### Recipe 4: Inspect Graph Execution Flow

1. Start LangGraph dev server: `uv run langgraph dev --no-browser`
2. Find thread: `GET /threads?limit=10`
3. Get current state: `GET /threads/{id}/state`
4. View checkpoint history: `GET /threads/{id}/history`
5. Dump to JSON Crack: `uv run python scripts/dump_state_json.py {thread_id}`
6. Open `localhost:8888`, paste JSON for visual exploration

## Environment Variables Reference

| Variable | Purpose | Default |
|----------|---------|---------|
| `LANGFUSE_ENABLED` | Enable Langfuse tracing | `false` |
| `LANGFUSE_PUBLIC_KEY` | Langfuse auth | (required if enabled) |
| `LANGFUSE_SECRET_KEY` | Langfuse auth | (required if enabled) |
| `LANGFUSE_BASE_URL` | Langfuse host | `https://cloud.langfuse.com` |
| `LOGFIRE_ENABLED` | Enable Logfire -> Langfuse bridge | `false` |
| `LANGCHAIN_TRACING_V2` | Enable LangSmith auto-tracing | `false` |
| `LANGCHAIN_API_KEY` | LangSmith auth | (required if tracing enabled) |

## Key Source Files

| File | Purpose |
|------|---------|
| `open_notebook/observability/langfuse_config.py` | `langfuse_tracing()`, `get_langfuse_handler()`, `merge_langfuse_into_config()` |
| `open_notebook/observability/logfire_config.py` | `init_logfire()`, instrument_pydantic guidance |
| `open_notebook/observability/langfuse_bridge.py` | `emit_pipeline_event()` for custom LangChain events |
| `scripts/dump_state_json.py` | Dump LangGraph thread state to JSON file |
| `scripts/generate_model_diagrams.py` | Generate erdantic ER diagrams |
| `scripts/observability/setup_langfuse_datasets.py` | Langfuse dataset/prompt setup |
| `docs/development/observability.md` | Comprehensive reference (1026 lines) |

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.