Skip to content
Merged
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
59 changes: 24 additions & 35 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,19 +44,20 @@ knowhereapi-main/
│ ├── web/ # Frontend (separate repo: knowhere-dashboard)
│ └── docs/ # Internal documentation
├── packages/
│ ├── shared-python/shared/ # Shared library (pip: knowhere-shared)
│ │ ├── models/database/ # SQLAlchemy ORM models
│ │ ├── models/schemas/ # Pydantic request/response schemas
│ │ ├── services/retrieval/ # Core retrieval engine
│ │ ├── services/chunks/ # DataFrame → ChunkPayload conversion
│ │ ├── services/ai/ # LLM prompt service & AI client
│ │ └── utils/ # Text, file, and chunk utilities
│ ├── sdk-python/ # Public Python SDK
│ ├── sdk-typescript/ # Public Node.js SDK
│ └── openapi-specs/ # OpenAPI spec definitions
│ └── shared-python/shared/ # Shared library (pip: knowhere-shared)
│ ├── models/database/ # SQLAlchemy ORM models
│ ├── models/schemas/ # Pydantic request/response schemas
│ ├── services/retrieval/ # Core retrieval engine
│ ├── services/chunks/ # DataFrame → ChunkPayload conversion
│ ├── services/ai/ # LLM prompt service & AI client
│ └── utils/ # Text, file, and chunk utilities
└── deploy/ # Docker Compose & deployment scripts
```

> **SDKs live in standalone repos:**
> - Python SDK → [`Ontos-AI/knowhere-python-sdk`](https://github.com/Ontos-AI/knowhere-python-sdk)
> - Node SDK → [`Ontos-AI/knowhere-node-sdk`](https://github.com/Ontos-AI/knowhere-node-sdk)

---

## End-to-End Pipeline Overview
Expand Down Expand Up @@ -99,7 +100,7 @@ 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["RetrievalAgent.run (LLM-driven)"]
Pipeline --> Agentic["WorkflowOrchestrator (Planner + DAG)"]
Channels --> RRF["RRF Fusion"]
Agentic --> Hydrate["hydrate_paths_to_rows"]
RRF --> Rank["_rank_candidates_by_path"]
Expand Down Expand Up @@ -528,7 +529,7 @@ debug CSVs (`preds_*.csv`) are saved alongside for troubleshooting.

### Two Retrieval Modes

The system supports two modes controlled by `RETRIEVAL_AGENTIC_ENABLED`:
The system supports two modes, controlled globally by `RETRIEVAL_AGENTIC_ENABLED` and locally via the per-request `use_agentic` toggle.

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

Expand All @@ -546,33 +547,21 @@ flowchart LR
```

**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 (LLM-driven Navigation)

#### Agentic Mode (LLM-driven Navigation)

The agentic pipeline uses a deterministic multi-phase orchestration engine:

**Phase 1: Discovery + Document Selection**
- **Bottom Discovery**: Always runs first. Executes a 3-channel RRF keyword search across the entire Knowledge Base, returning top high-relevance chunks and their parent documents (`discovery_auto`).
- **KG Document Select**: The LLM analyzes the KB-wide overview (from `knowledge_graph.json`) and selects highly relevant documents.
- *Merge Strategy*: Documents found by Bottom Discovery but omitted by the LLM are automatically appended to the selected documents list to ensure no blind spots.
#### Agentic Mode (Workflow Orchestrator)

**Phase 2: Per-Document Navigation & Discovery Merging**
For each selected document, the agent performs a constrained Breadth-First Search (BFS):
1. **Scope Navigation**: The document's section tree is dynamically rendered to the LLM.
- *Path-Based Hierarchy*: Child nodes are strictly filtered using structural path prefixes (e.g., `child_path.startswith(parent_path + ' / ')`) to maintain structural integrity and eliminate L2 duplicate rendering.
- *Visual Constraints*: Actionable drill-down paths are explicitly prefixed with `[SELECT]` tags. The LLM system prompt tightly constrains the model to only pick paths with this tag, preventing redundant re-selection of the current scope.
2. **Discovery Select**: The LLM reviews the specific paths flagged by Phase 1's Bottom Discovery for the current document. Selected discovery paths are hydrated into leaf chunks (with `job_result_id` dynamically extracted from the chunks) and merged directly into the BFS document tree.
- *Reparenting*: The `DocTreeNode.merge()` process reparents these discovered leaf chunks into the closest matching navigated child node.
- *Orphan Leaves*: Discovered chunks whose paths are not explicitly covered by the BFS `outline_items` are rendered cleanly as `[Leaf]` items (orphans) beneath their appropriate parent, ensuring no relevant data is lost even if the BFS did not explicitly drill into that path.
The agentic pipeline uses `WorkflowOrchestrator` to handle complex queries via a DAG-based planning and budget-constrained execution engine:

**Phase 3: Verdict & Revision**
The combined document tree (BFS Navigation + Discovery) is rendered as unified evidence. The tree naturally displays structural context (outlines) alongside hydrated chunk rows (for selected leaf paths). The LLM attempts to answer the user's query:
- `DONE`: Evidence is sufficient (or partially covers the query), exit and return final results.
- `NOT_FOUND`: Evidence lacks sufficient information. Discard current evidence and trigger another revision round with a generated hint (max 2 rounds).
1. **Planning (`PlannerAgent`)**: The query is analyzed and decomposed into a DAG of steps.
- Simple queries generate a single `retrieve` step.
- Complex queries are broken into multiple `retrieve` steps followed by a final `synthesize` step.
2. **Budget Ledger (`BudgetLedger`)**: A strict token budget mechanism is enforced across the entire DAG execution (e.g., `AGENTIC_MAX_BUDGET=30000`). 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 (Verdict)**: The LLM evaluates the collected structural outlines + hydrated chunks. Triggers a revision round (max 2) if `NOT_FOUND`.
4. **Synthesis**: The LLM synthesizes a final `answer_text` and precise citations (`referenced_chunks`) using the unified evidence tree.

### Tree Rendering & Hydration

Expand Down
14 changes: 13 additions & 1 deletion apps/api/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,18 @@ NORMOL_MODEL=deepseek-chat
HIERARCHY_LLM_MODEL=qwen3.6-flash
IMAGE_MODEL=qwen3.5-flash
IMAGE_MODEL_MAX=qwen3.5-flash
RETRIEVAL_DECOMPOSITION_ENABLED=false
RETRIEVAL_PLANNER_MODEL=
RETRIEVAL_PLANNER_THINKING_BUDGET=4000
RETRIEVAL_DECOMPOSITION_MAX_STEPS=5
RETRIEVAL_WALLET_TOTAL_BUDGET=200000
RETRIEVAL_WALLET_PER_RETRIEVE_STEP_BUDGET=40000
RETRIEVAL_WALLET_PER_SYNTHESIZE_STEP_BUDGET=6000
RETRIEVAL_WORKFLOW_PARALLEL_MAX=3

# Agentic retrieval (LLM-driven hierarchical navigation).
# Set to false to fall back to legacy 3-channel RRF mode.
RETRIEVAL_AGENTIC_ENABLED=true

# File handling defaults
SUPPORTED_EXTENSIONS=.doc,.docx,.pdf,.txt,.xls,.xlsx,.csv,.pptx,.jpg,.jpeg,.png,.md
Expand Down Expand Up @@ -120,4 +132,4 @@ ILOVEAPI_TIMEOUT=120

# Legacy parser compatibility fields.
ALL_DF_COLS=content,path,type,length,keywords,summary,know_id,tokens,connectto,addtime,page_nums
SPLIT_CHAR=-->
SPLIT_CHAR=/
16 changes: 15 additions & 1 deletion apps/api/app/api/v1/routes/retrieval.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ class RetrievalQueryRequest(BaseModel):
internal_recall_k: int | None = Field(
None, ge=1, description="Override per-channel recall count"
)
use_agentic: bool | None = Field(
None,
description="Per-request agentic mode toggle. true=force agentic, false=force legacy, null=use server default.",
)

@field_validator("channels")
@classmethod
Expand All @@ -63,7 +67,16 @@ def validate_channels(cls, v: list[str]) -> list[str]:
return v


@router.post("/query")
class RetrievalQueryResponse(BaseModel):
namespace: str
query: str
router_used: str
answer_text: str | None = None
referenced_chunks: list[dict] = Field(default_factory=list)
results: list[dict] = Field(default_factory=list)


@router.post("/query", response_model=RetrievalQueryResponse)
async def query_retrieval(
payload: RetrievalQueryRequest,
current_user: CurrentUser = Depends(with_current_user),
Expand All @@ -85,4 +98,5 @@ async def query_retrieval(
rerank=payload.rerank,
threshold=payload.threshold,
internal_recall_k=payload.internal_recall_k,
use_agentic=payload.use_agentic,
)
1 change: 1 addition & 0 deletions apps/api/tests/contract/test_demo_documents_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ async def test_should_materialize_demo_source_without_parse_or_credit_charge(
"shared.services.storage.result_storage.get_result_storage",
lambda: fake_result_storage,
)
monkeypatch.setenv("RETRIEVAL_AGENTIC_ENABLED", "false")

async with developer_api_client_factory() as api_client:
empty_cached_response = await api_client.post(
Expand Down
2 changes: 2 additions & 0 deletions apps/api/tests/contract/test_retrieval_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,8 @@ async def test_should_return_empty_results_for_an_empty_query(
"query": "",
"router_used": "empty_query_filtered",
"results": [],
"answer_text": None,
"referenced_chunks": [],
}


Expand Down
14 changes: 13 additions & 1 deletion apps/worker/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,18 @@ NORMOL_MODEL=deepseek-chat
HIERARCHY_LLM_MODEL=deepseek-chat
IMAGE_MODEL=qwen3.5-flash
IMAGE_MODEL_MAX=qwen3.5-flash
RETRIEVAL_DECOMPOSITION_ENABLED=false
RETRIEVAL_PLANNER_MODEL=
RETRIEVAL_PLANNER_THINKING_BUDGET=4000
RETRIEVAL_DECOMPOSITION_MAX_STEPS=5
RETRIEVAL_WALLET_TOTAL_BUDGET=200000
RETRIEVAL_WALLET_PER_RETRIEVE_STEP_BUDGET=40000
RETRIEVAL_WALLET_PER_SYNTHESIZE_STEP_BUDGET=6000
RETRIEVAL_WORKFLOW_PARALLEL_MAX=3

# Agentic retrieval (LLM-driven hierarchical navigation).
# Set to false to fall back to legacy 3-channel RRF mode.
RETRIEVAL_AGENTIC_ENABLED=true

# Required for specific features: billing and analytics
BILLING_ENABLED=false
Expand All @@ -110,5 +122,5 @@ MAX_FILE_SIZE=104857600

# Legacy parser compatibility fields.
ALL_DF_COLS=content,path,type,length,keywords,summary,know_id,tokens,connectto,addtime,page_nums
SPLIT_CHAR=-->
SPLIT_CHAR=/

Loading
Loading