Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 16 additions & 42 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,13 +102,14 @@ flowchart TB

subgraph RETRIEVE["⑤ Retrieval (shared)"]
Query["GET /v1/retrieval/query"] --> Pipeline["run_retrieval_query"]
Pipeline --> Channels["3-Channel BM25 (path/content/term)"]
Pipeline --> Agentic["WorkflowOrchestrator (Planner + DAG)"]
Channels --> RRF["RRF Fusion"]
Agentic --> Hydrate["hydrate_paths_to_rows"]
RRF --> Rank["_rank_candidates_by_path"]
Hydrate --> Rank
Pipeline --> Classic["classic_topk / small_corpus (use_agentic=False)"]
Pipeline --> MapNav["mapnav checklist (default / use_agentic≠False)"]
Classic --> Channels["3-Channel BM25 (path/content/term)"]
Channels --> Rank["rank_retrieval_candidates"]
MapNav --> NavSnap["nav_snapshot + run_nav_episode"]
NavSnap --> Bridge["nav_bridge referenced_chunks"]
Rank --> Assemble["assemble_retrieval_results"]
Bridge --> Assemble
Assemble --> Results["Cited Evidence Results"]
end
```
Expand Down Expand Up @@ -544,22 +545,19 @@ debug CSVs (`preds_*.csv`) are saved alongside for troubleshooting.

Core retrieval internals are grouped by ownership:

- `execution/`: request shaping, route selection, legacy route execution, and public response projection.
- `search/`: lexical channels, scoring, section filters, and candidate ranking.
- `execution/`: request shaping, route selection (classic / mapnav / small_corpus), and public response projection.
- `search/`: lexical channels, scoring, section filters, candidate ranking, and classic `bottom_discovery`.
- `hydration/`: row/path/reference hydration, inline assets, and result assembly.
- `nav/` + `nav_*.py`: map-nav checklist episode (PLANNER / HARVEST / CONTROL).
- `trace/`: `DecisionTraceStep` mapping and `TraceRecorder`.
- `graph/`: document graph publication/query support.
- `stats/`: retrieval hit recording.
- `workflow/`: query planning, retrieve step execution, and wallet state.
- `agentic/core/`: agentic run types, token budgets, runtime config, and traces.
- `agentic/discovery/`: bottom discovery and document selection.
- `agentic/navigation/`: section-tree navigation, selection hydration, and asset tools.
- `agentic/evidence/`: evidence tree rendering and budget trimming.

### Two Retrieval Modes

The system supports two modes, controlled globally by `RETRIEVAL_AGENTIC_ENABLED` and locally via the per-request `use_agentic` toggle.
Per-request `use_agentic`: `False` → classic 3-channel top-K; `None`/`True` → map-nav (default).

#### Legacy Mode (3-Channel RRF)
#### Classic Mode (3-Channel RRF)

```mermaid
flowchart LR
Expand All @@ -569,40 +567,16 @@ flowchart LR
P --> RRF["RRF Fusion (k=60)"]
C --> RRF
T --> RRF
RRF --> Graph[Legacy Graph Routing]
Graph --> Rank[Dual-priority ranking]
RRF --> Rank[rank_retrieval_candidates]
Rank --> Assemble[hydration.result_assembly]
```

**Channel weights** (default): path=1.0, content=2.0, term=1.5
**RRF formula**: `score = weight / (k + rank + 1)` per channel, summed across channels.

#### Agentic Mode (Workflow Orchestrator)
#### Map-nav Mode (default)

The agentic pipeline uses `WorkflowOrchestrator` to handle complex queries via a DAG-based planning and budget-constrained execution engine:

1. **Planning (`PlannerAgent`)**: The query is analyzed and decomposed into a DAG of retrieval steps.
- Simple queries generate a single `retrieve` step.
- Complex queries are broken into multiple `retrieve` steps. KNOWHERE does not plan answer synthesis steps.
2. **Budget Ledger (`BudgetLedger`)**: A strict token budget mechanism is enforced across the entire DAG execution. If the budget is exhausted, the pipeline halts safely and returns the best-effort evidence collected so far.
3. **Execution (`RetrievalAgent`)**: For each `retrieve` step, a multi-phase navigation engine runs:
- **Phase 1 (Discovery)**: 3-channel RRF keyword search and KG document selection.
- **Phase 2 (Navigation)**: Constrained Breadth-First Search (BFS) over the document's section tree. Discovered orphan leaves are merged into the tree to prevent data loss.
- **Phase 3 (Evidence Rendering)**: The hydrated document tree is rendered as `evidence_text`.
4. **Evidence-Only Contract**: Retrieval responses always expose `evidence_text` as the primary output. `answer_text` is retained only as a deprecated empty string. Downstream agents decide whether the evidence is sufficient and synthesize answers outside KNOWHERE.

### Tree Rendering & Hydration

Unlike legacy retrieval which relied on static `hydrate_mode` tags, hydration is now determined dynamically by the `DocTreeNode` structure:
- **Structural Context (Outlines)**: Sections not drilled into are simply rendered as structural outlines (`title` + `summary`) to guide the LLM.
- **Leaf Content (Hydration)**: Sections that the LLM explicitly selects for drill-down have their raw chunks (`text`, `image`, `table`) fully hydrated into the `leaf_content` of the tree.
- **Multi-Modal Inline Embedding**: During hydration, connected inline assets (images/tables) are natively resolved and embedded directly into the text chunk content, supporting multi-modal LLM processing without brittle string-replacement placeholders.

**`_rank_candidates_by_path()`** — Dual-priority ranking:

- When agent results exist: agent_score is primary, discovery_score is tiebreaker
- Rows with agent_score=0 are demoted to fallback pool
- Sort key: `(agent_score, discovery_score, dual_hit_flag, importance_norm_score)`
Default agentic path is checklist map-nav (`nav/`): PLANNER (`plan_query`) → HARVEST (`execute_plan` / `harvest`, recursive DISPATCH) → CONTROL (`plan_control`). Episode config lives in `nav_config.py`. Exit bridge expands kept chunks to `referenced_chunks`; `decision_trace` is mapped in `trace/mapnav.py`. Token hard-stop uses `NavConfig.token_limit`.

### Result Assembly

Expand Down
8 changes: 4 additions & 4 deletions apps/api/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -99,10 +99,10 @@ ARK_API_KEY=
# IMAGE_MODEL_MAX=qwen3.6-flash

# Optional retrieval overrides have code defaults. Retrieval is evidence-only:
# evidence_text is the primary output and answer_text is always empty. Set
# RETRIEVAL_AGENTIC_ENABLED=false only when you need to fall back to legacy
# 3-channel RRF mode.
# RETRIEVAL_WORKFLOW_PLANNER_TIMEOUT_SECONDS=10.0
# evidence_text is the primary output and answer_text is always empty.
# Default path is map-nav (PLANNER+HARVEST+CONTROL); set use_agentic=false for
# classic 3-channel RRF. Classic BM25 may use Postgres FTS prefilter:
# RETRIEVAL_POSTGRES_FTS_CANDIDATE_LIMIT=2000

# File handling defaults
SUPPORTED_EXTENSIONS=.doc,.docx,.pdf,.txt,.xls,.xlsx,.csv,.pptx,.jpg,.jpeg,.png,.md,.html,.htm
Expand Down
5 changes: 4 additions & 1 deletion apps/api/app/api/v1/routes/retrieval.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,10 @@ class RetrievalQueryRequest(BaseModel):
)
use_agentic: bool | None = Field(
None,
description="Set to true to enable agentic retrieval (LLM doc-select + navigation). Default (None/false) uses classic 3-channel top-K.",
description=(
"Map-nav (PLANNER+HARVEST+CONTROL) is the default when unset/true. "
"Set false to force classic 3-channel top-K retrieval."
),
)

@field_validator("channels")
Expand Down
2 changes: 1 addition & 1 deletion apps/api/app/mcp/retrieval_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ async def query_documents(
top_k=top_k,
exclude_document_ids=exclude_document_ids,
exclude_sections=[item for item in exclude_sections],
use_agentic=True,
use_agentic=None,
)
return to_mcp_query_response(response)

Expand Down
2 changes: 1 addition & 1 deletion apps/api/app/services/document_ingestion/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
_PUBLIC_MODE_SELECTOR_FIELDS = {"mode", "processing"}
_PARSE_TRACK_FIELD = "parse_track"
_PAGE_MEMORY_FIELD_PREFIX = "page_memory"
_PUBLIC_COMPATIBILITY_EXTRA_FIELDS = frozenset({_PARSE_TRACK_FIELD})
_PUBLIC_COMPATIBILITY_EXTRA_FIELDS = frozenset({_PARSE_TRACK_FIELD, "result_mode"})
IngestionCommandFactory = Callable[[str], DocumentIngestionCommand]


Expand Down
Loading
Loading