Skip to content

feat(project-graph): LLM-grounded extractor with enforced segment citations (Phase 2)#931

Open
seonghobae wants to merge 1 commit into
feat/project-graph-import-activationfrom
feat/project-graph-llm-extractor
Open

feat(project-graph): LLM-grounded extractor with enforced segment citations (Phase 2)#931
seonghobae wants to merge 1 commit into
feat/project-graph-import-activationfrom
feat/project-graph-llm-extractor

Conversation

@seonghobae

Copy link
Copy Markdown
Contributor

What (stacked on #927 — Phase 2 of #925)

Upgrades project-graph extraction from the deterministic keyword baseline to an LLM-grounded extractor that cannot fabricate:

  • Hard grounding rule: every extracted object must cite content_segment_uids present in the input segments — objects with unknown/missing/partially-unknown citations are dropped. The model cannot introduce uncited domain claims into the graph.
  • Unknown object_type dropped; confidence clamped to [0,1]; titles bounded to the DB column limit.
  • Prompt-injection hardening: segment text is passed strictly as JSON data (mirrors api/llm.py); base_url goes through the SSRF-validated provider client; request bounded to 40 segments × 2000 chars.
  • Selection knob: PROJECT_GRAPH_EXTRACTOR=keyword|llm (default keyword → zero behavior change). The llm path reuses the import's OpenAI-compatible provider credentials and falls back to keyword on any failure — the projection is never lost to a provider outage.

Verification (local — 7 new tests + 18 regressions green)

grounded mapping + evidence edges · hallucinated/empty citations dropped · unknown type dropped + confidence clamped · empty segments short-circuit (no LLM call) · selection: llm-when-configured / keyword-fallback-on-failure / keyword-default.

Follow-up (Phase 3, per #925)

Corrections → per-tenant eval set + calibrated confidence.

🤖 Generated with Claude Code

…ations

Phase 2 of the project-graph activation plan (stacked on the Phase 1 import
wiring): upgrades extraction from the deterministic keyword baseline to an
LLM-backed extractor that cannot fabricate.

- services/project_graph/llm_extractor.py: structured-output extraction over
  the imported email's content segments. Hard grounding rule: every object
  must cite content_segment_uids present in the input; objects with unknown,
  missing, or partial-unknown citations are DROPPED. Unknown object types
  dropped; confidence clamped to [0,1]; segment text passed as JSON data
  (prompt-injection hardening, mirrors api/llm.py); base_url goes through the
  SSRF-validated provider client; segments capped (40 x 2000 chars) to bound
  tokens.
- Selection: PROJECT_GRAPH_EXTRACTOR=keyword|llm (default keyword -> zero
  behavior change). The llm path reuses the import's OpenAI-compatible
  provider credentials and falls back to the keyword extractor on any
  failure, so the projection is never lost to a provider outage.

Tests (7): grounded mapping + evidence edges; hallucinated/empty citations
dropped; unknown type dropped + confidence clamped; empty segments short-
circuit without an LLM call; selection uses llm when configured, falls back
to keyword on llm failure, defaults to keyword. Regressions green (18).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017RkKdtHRLG4wSLh6PVsp8J
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