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
4 changes: 0 additions & 4 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,6 @@ RA_LLM__MODEL=auto
RA_LLM__BASE_URL=http://localhost:11434/v1
RA_LLM__API_KEY=ollama

# Schema-enforced LLM output (native structured outputs). Disable only for
# backends that cannot honor response schemas; the legacy JSON-repair path
# is then used instead.
# RA_LLM__STRUCTURED_OUTPUTS=false
# OLLAMA_API_KEY=ollama

# Synthesis — see config/ollama_models.yaml for per-model hints
Expand Down
3 changes: 0 additions & 3 deletions config/default.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ llm:
base_url: http://localhost:11434
temperature: 0.2
timeout_seconds: 120
structured_outputs: true

embedding:
model: BAAI/bge-small-en-v1.5
Expand Down Expand Up @@ -145,7 +144,5 @@ memory:
synthesis:
llm_mode: auto
max_llm_papers: 3
extraction_max_retries: 0
collective_max_retries: 0
concurrency: 2
circuit_breaker_failures: 2
2 changes: 0 additions & 2 deletions docs/_analysis/config-inventory.md
Original file line number Diff line number Diff line change
Expand Up @@ -199,8 +199,6 @@ Source: `src/config/settings.py`, `config/*.yaml`, `.env.example`, `src/config/r
| `llm_mode` | `"auto"` | |
| `llm_enabled` | `False` | Resolved at pipeline start |
| `max_llm_papers` | `3` | May be overridden by Ollama catalog hints |
| `extraction_max_retries` | `0` | |
| `collective_max_retries` | `0` | |
| `concurrency` | `2` | |
| `circuit_breaker_failures` | `2` | |

Expand Down
8 changes: 4 additions & 4 deletions docs/_analysis/llm-resolution-tree.md
Original file line number Diff line number Diff line change
Expand Up @@ -176,16 +176,16 @@ flowchart TD
RESOLVED[ctx.config after resolve_effective_settings]

RESOLVED --> QE{query_expansion.llm_enabled?}
QE -->|yes| QE_AGENT[AgentFactory EXPANSION stream_agent_text]
QE -->|yes| QE_AGENT[run_structured EXPANSION]
QE -->|no| QE_SKIP[heuristics only]

RESOLVED --> SY{synthesis.llm_enabled?}
SY -->|yes| SY_A[AgentFactory EXTRACTION up to max_llm_papers]
SY_A --> SY_B[AgentFactory SYNTHESIS collective]
SY -->|yes| SY_A[try_run_structured EXTRACTION up to max_llm_papers]
SY_A --> SY_B[try_run_structured SYNTHESIS collective]
SY -->|no| SY_H[heuristic extraction + synthesis]

RESOLVED --> GA{synthesis.llm_enabled?}
GA -->|yes| GA_AGENT[AgentFactory GAP_ANALYSIS structured response]
GA -->|yes| GA_AGENT[try_run_structured GAP_ANALYSIS]
GA -->|no| GA_H[heuristic from synthesis fields]
```

Expand Down
29 changes: 3 additions & 26 deletions docs/_analysis/test-behavior-index.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Test Behavior Index

Source: all 28 files matching `tests/test_*.py`. Internal reference for `docs/development/testing.md`.
Source: all files matching `tests/test_*.py`. Internal reference for `docs/development/testing.md`.

## Summary by domain

Expand All @@ -12,7 +12,7 @@ Source: all 28 files matching `tests/test_*.py`. Internal reference for `docs/de
| Retrieval providers | `test_providers.py` |
| Embeddings | `test_embeddings.py` |
| Reporting / export | `test_reporting.py`, `test_export.py` |
| LLM layer | `test_llm_providers.py`, `test_graceful_response_handling.py` |
| LLM layer | `test_llm_providers.py`, `test_structured_outputs.py` |
| Orchestrator / degradation | `test_json_parsing_bug_exploration.py`, `test_json_parsing_preservation.py` |
| CLI / interactive | `test_main_mode_detection.py`, `test_interactive_mode.py`, `test_complete_workflow.py`, `test_input_handler.py`, `test_message_formatting.py`, `test_signal_handling.py`, `test_interactive_filters.py` |
| Memory | `test_memory.py` |
Expand Down Expand Up @@ -216,7 +216,7 @@ Source: all 28 files matching `tests/test_*.py`. Internal reference for `docs/de
| | |
|---|---|
| **Modules** | `src.analysis.synthesis`, `src.analysis.gap_analysis`, `src.core.stage_recovery` |
| **Mocks** | `MagicMock(EnhancedResponseHandler)`; `patch create_llm_agent` |
| **Mocks** | `patch src.models.structured.try_run_structured` (AsyncMock) |

| Class | Behavior |
|-------|----------|
Expand Down Expand Up @@ -305,28 +305,6 @@ Source: all 28 files matching `tests/test_*.py`. Internal reference for `docs/de

---

### `test_graceful_response_handling.py`

| | |
|---|---|
| **Modules** | `src.utils.{response_models,retry_manager,quality_monitor,enhanced_validation,content_quality,model_adaptation,fallback_processing}` |

| Class | Behavior |
|-------|----------|
| `TestRetryManager` | Retry rules, prompt enhancement |
| `TestQualityMonitor` | Success/failure recording |
| `TestEnhancedValidation` | Retry strategy mapping |
| `TestContentQuality` | Empty/insufficient/incomplete analysis detection |
| `TestQueryAnalyzer` | Query broadening suggestions |
| `TestRelevanceScorer` | Paper relevance ordering |
| `TestJSONProcessing` | Extract, parse errors, validation |
| `TestModelAdaptation` | GPT/Claude detection, markdown stripping |
| `TestFallbackProcessing` | Unstructured text → structured fallback |

**Note:** Does not exercise `EnhancedResponseHandler` end-to-end.

---

### `test_json_parsing_bug_exploration.py`

| | |
Expand Down Expand Up @@ -490,7 +468,6 @@ Source: all 28 files matching `tests/test_*.py`. Internal reference for `docs/de
|-----|--------|
| Query understanding | No dedicated unit test file |
| API routes | Only scaffold test in `test_phase3_extensibility.py` |
| `EnhancedResponseHandler` | Subcomponents tested, not end-to-end |
| Live LLM integration | All LLM tests mock Pydantic AI |
| Subprocess tests | `@pytest.mark.slow`; may skip in CI |

Expand Down
1 change: 0 additions & 1 deletion docs/configuration/environment-variables.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ Boolean env values accept standard truthy strings (`true`, `1`, `yes`).
| Variable | Default | Description |
|----------|---------|-------------|
| `RA_LLM__PROVIDER` | `ollama` | `ollama`, `openai`, or `anthropic` |
| `RA_LLM__STRUCTURED_OUTPUTS` | `true` | Schema-enforced LLM output; disable to use the legacy JSON-repair path |
| `RA_LLM__MODEL` | `auto` | Model name; `auto` selects from `config/ollama_models.yaml` (Ollama only) |
| `RA_LLM__BASE_URL` | `http://localhost:11434` | API base URL (Ollama OpenAI-compatible endpoint) |

Expand Down
2 changes: 0 additions & 2 deletions docs/configuration/yaml-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -183,8 +183,6 @@ Set `cache_enabled: true` to reuse cached retrieval results keyed by query + ena
synthesis:
llm_mode: auto
max_llm_papers: 3
extraction_max_retries: 0
collective_max_retries: 0
concurrency: 2
circuit_breaker_failures: 2
```
Expand Down
5 changes: 2 additions & 3 deletions docs/development/testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,9 @@ All LLM integration tests **mock pydantic-ai** — no Ollama or cloud API requir

| Pattern | Example location |
|---------|------------------|
| `patch create_llm_agent` | `test_synthesis.py` |
| `patch src.models.structured.try_run_structured` | `test_synthesis.py`, `test_structured_outputs.py` |
| `patch` OpenAI/Pydantic AI constructors | `test_llm_providers.py` |
| `MagicMock(EnhancedResponseHandler)` | synthesis workflow tests |
| `TestModel` / `FunctionModel` from pydantic-ai | `test_structured_outputs.py` |

This keeps CI fast and deterministic. Manual LLM verification uses the CLI with real providers.

Expand Down Expand Up @@ -128,7 +128,6 @@ Document these when adding tests:
|-----|--------|
| Query understanding | No dedicated unit test file |
| API routes | Scaffold tests only — no HTTP integration tests |
| `EnhancedResponseHandler` | Subcomponents tested; not end-to-end |
| Live LLM / API | All mocked in unit tests |
| Subprocess tests | Marked `@pytest.mark.slow`; may skip in tight CI |

Expand Down
16 changes: 7 additions & 9 deletions docs/llm/heuristic-vs-llm.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,15 +126,13 @@ YAML equivalents under `synthesis:` and `query_expansion:` in `config/default.ya

## Structured outputs

When the LLM path runs, output schemas are enforced natively by default
(`RA_LLM__STRUCTURED_OUTPUTS=true`): the pydantic model is attached to the
agent (`output_type`), pydantic-ai validates the response and retries with
validation feedback on schema violations, and the caller receives a typed
object or falls back to heuristics. Disable the flag to route through the
legacy prose-JSON path with the repair/retry machinery in `src/utils/` —
useful only for backends that cannot honor response schemas. Identity
fields (paper ID, title) are always taken from retrieval metadata, never
from model output.
When the LLM path runs, output schemas are enforced natively: the pydantic
model is attached to the agent (`output_type`), pydantic-ai validates the
response and retries with validation feedback on schema violations, and the
caller receives a typed object or falls back to heuristics. This is the only
LLM call path — the legacy prose-JSON repair machinery has been retired.
Identity fields (paper ID, title) are always taken from retrieval metadata,
never from model output.

## Verify LLM is active

Expand Down
4 changes: 0 additions & 4 deletions docs/operations/logging-and-debug.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,10 +104,6 @@ Use these dumps to inspect why a stage was partial or what papers were retrieved

6. **Correlate with combined log** using timestamps and session ID in log lines.

## Quality metrics (optional)

`src/utils/quality_monitor.py` can write additional JSON to `logs/quality_metrics_<timestamp>.json` when quality monitoring is active during LLM stages.

## What debug does *not* include

- Raw HTTP response bodies from retrieval providers (only normalized papers in artifacts)
Expand Down
2 changes: 1 addition & 1 deletion docs/operations/progress-streaming.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ sequenceDiagram
Bus->>Rep: _on_stage_complete
```

Stages call `set_activity()` / `set_llm_preview()` during long operations (especially synthesis). LLM calls use `stream_agent_text()` which delegates to `stream_agent_response()` when a reporter is active.
Stages call `set_activity()` / `set_llm_preview()` during long operations (especially synthesis). Structured LLM calls report per-paper activity through `set_activity()`; `stream_agent_text()` remains available for streaming free-text agent runs and delegates to `stream_agent_response()` when a reporter is active.

## Context variable

Expand Down
45 changes: 7 additions & 38 deletions src/analysis/gap_analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,8 @@
from typing import TYPE_CHECKING

from ..core.context import PipelineContext, StageResult
from ..models import AgentFactory, AgentRole
from ..models import AgentRole
from ..retrieval.models import GapAnalysisResult, PaperCluster, SynthesisResult
from ..utils.enhanced_response_handler import EnhancedResponseHandler
from ..utils.response_models import RequestContext, ResponseHandlerConfig

if TYPE_CHECKING:
from ..config.settings import LLMConfig
Expand Down Expand Up @@ -98,8 +96,6 @@ async def analyze_gaps(
clusters: list[PaperCluster] | None = None,
*,
llm_config: LLMConfig | None = None,
handler: EnhancedResponseHandler | None = None,
session_id: str = "",
) -> GapAnalysisResult:
"""Refine synthesis gaps into prioritized research opportunities."""
clusters = clusters or []
Expand All @@ -117,38 +113,16 @@ async def analyze_gaps(
if reporter is not None:
reporter.set_activity("Analyzing research gaps with AI…")

if llm_config.structured_outputs:
from ..models.structured import try_run_structured
from ..models.structured import try_run_structured

gap_result = await try_run_structured(
AgentRole.GAP_ANALYSIS,
prompt,
GapAnalysisResult,
llm_config,
)
if gap_result is not None:
return gap_result
return _heuristic_gap_analysis(synthesis, query, clusters)

agent = AgentFactory(llm_config).create_agent(AgentRole.GAP_ANALYSIS, config=llm_config)
response_handler = handler or EnhancedResponseHandler(ResponseHandlerConfig())
context = RequestContext(
user_query=query,
model_name=llm_config.model,
session_id=session_id,
)

result = await response_handler.process_structured_response(
agent,
gap_result = await try_run_structured(
AgentRole.GAP_ANALYSIS,
prompt,
context,
GapAnalysisResult,
schema_description=(
'{"gaps": ["..."], "opportunities": ["..."], "underexplored_areas": ["..."]}'
),
llm_config,
)
if result.success and isinstance(result.data, GapAnalysisResult):
return result.data
if gap_result is not None:
return gap_result
return _heuristic_gap_analysis(synthesis, query, clusters)


Expand All @@ -157,9 +131,6 @@ class GapAnalysisStage:

name = "gap_analysis"

def __init__(self, handler: EnhancedResponseHandler | None = None) -> None:
self.handler = handler

async def run(
self,
ctx: PipelineContext,
Expand Down Expand Up @@ -196,8 +167,6 @@ async def run(
synthesis,
clusters,
llm_config=ctx.config.llm,
handler=self.handler,
session_id=ctx.session.id,
)
except Exception as exc:
warnings.append(f"Gap analysis failed, using heuristic fallback: {exc}")
Expand Down
Loading
Loading