feat(research): linked section notes + executive summary in index
Research pipeline now produces an index note with:
- Executive summary (2-3 paragraphs synthesized from sections)
- Clickable links to each section note (/notes/{id})
- Section notes have parent_id pointing to the index
Also improves outline resilience: lowered minimum sections from 3
to 2, retries once on failure before falling back to monolithic note.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -34,8 +34,8 @@ async def test_generate_outline_returns_empty_on_parse_failure():
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_generate_outline_returns_empty_when_fewer_than_3_sections():
|
||||
"""_generate_outline returns [] when model returns fewer than 3 valid sections."""
|
||||
async def test_generate_outline_returns_empty_when_fewer_than_2_sections():
|
||||
"""_generate_outline returns [] when model returns fewer than 2 valid sections."""
|
||||
from fabledassistant.services.research import _generate_outline
|
||||
|
||||
outline_json = json.dumps([{"title": "Only One", "focus": "Focus"}])
|
||||
@@ -106,7 +106,7 @@ async def test_synthesize_section_strips_whitespace():
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_pipeline_creates_section_notes_and_index():
|
||||
"""run_research_pipeline creates N section notes + 1 index note, returns index."""
|
||||
"""run_research_pipeline creates N section notes + 1 index note with links, returns index."""
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
outline = [
|
||||
@@ -117,23 +117,31 @@ async def test_pipeline_creates_section_notes_and_index():
|
||||
|
||||
note_id_counter = iter(range(10, 20))
|
||||
|
||||
def _make_note(user_id, title, body, tags, project_id=None):
|
||||
def _make_note(user_id, title, body, tags, project_id=None, parent_id=None):
|
||||
n = MagicMock()
|
||||
n.id = next(note_id_counter)
|
||||
n.title = title
|
||||
return n
|
||||
|
||||
mock_update = AsyncMock()
|
||||
|
||||
with patch("fabledassistant.services.research._generate_sub_queries", new_callable=AsyncMock, return_value=["q1"]), \
|
||||
patch("fabledassistant.services.research._search_searxng", new_callable=AsyncMock, return_value=[{"url": "http://x.com", "title": "X", "snippet": "s"}]), \
|
||||
patch("fabledassistant.services.research.fetch_url_content", new_callable=AsyncMock, return_value="content"), \
|
||||
patch("fabledassistant.services.research._generate_outline", new_callable=AsyncMock, return_value=outline), \
|
||||
patch("fabledassistant.services.research._synthesize_section", new_callable=AsyncMock, side_effect=lambda t, f, s, m: (t, f"Body for {t}")), \
|
||||
patch("fabledassistant.services.research.create_note", new_callable=AsyncMock, side_effect=_make_note):
|
||||
patch("fabledassistant.services.research._generate_executive_summary", new_callable=AsyncMock, return_value="Executive summary text."), \
|
||||
patch("fabledassistant.services.research.create_note", new_callable=AsyncMock, side_effect=_make_note), \
|
||||
patch("fabledassistant.services.research.update_note", new_callable=AsyncMock) as mock_update:
|
||||
|
||||
from fabledassistant.services.research import run_research_pipeline
|
||||
result = await run_research_pipeline("test topic", user_id=1, model="test-model")
|
||||
|
||||
assert result.title == "Research: test topic"
|
||||
# Index note body should be updated with section links
|
||||
mock_update.assert_called_once()
|
||||
updated_body = mock_update.call_args.kwargs.get("body", "")
|
||||
assert "/notes/" in updated_body
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
||||
Reference in New Issue
Block a user