feat(briefing): multi-article topic discussion endpoint for deep thematic analysis

This commit is contained in:
2026-04-08 22:53:23 -04:00
parent d56b57ee40
commit 058bd76719
+103
View File
@@ -544,3 +544,106 @@ async def discuss_article(item_id: int):
))
return jsonify({"assistant_message_id": assistant_msg.id, "status": "generating"}), 202
@briefing_bp.route("/topics/<topic>/discuss", methods=["POST"])
@_REQUIRE
async def discuss_topic(topic: str):
"""Discuss all recent articles in a topic cluster — multi-article deep analysis."""
data = await request.get_json() or {}
conv_id = data.get("conv_id")
if not conv_id:
return jsonify({"error": "conv_id required"}), 400
uid = g.user.id
# Verify conversation belongs to user
conv = await get_conversation(uid, conv_id)
if conv is None:
return jsonify({"error": "Conversation not found"}), 404
if get_buffer(conv_id) is not None:
return jsonify({"error": "Generation already in progress"}), 409
# Find recent articles with this topic (last 2 days)
async with async_session() as session:
result = await session.execute(
select(RssItem)
.join(RssFeed, RssFeed.id == RssItem.feed_id)
.where(RssFeed.user_id == uid)
.where(RssItem.topics.contains([topic]))
.order_by(RssItem.published_at.desc().nullslast())
.limit(5)
)
items = list(result.scalars().all())
if not items:
return jsonify({"error": f"No articles found for topic '{topic}'"}), 404
# Fetch full content for each article
from fabledassistant.services.rss import _fetch_full_article
synthetic_tool_calls = []
for item in items:
content = await _fetch_full_article(item.url) if item.url else None
content = content or item.content or ""
synthetic_tool_calls.append({
"function": "read_article",
"arguments": {"url": item.url or ""},
"result": {
"success": True,
"type": "article_content",
"url": item.url or "",
"title": item.title or "",
"source": "",
"content": content[:8000], # cap per article to stay within context
"truncated": len(content) > 8000,
},
})
await add_message(conv_id, "assistant", "", status="complete", tool_calls=synthetic_tool_calls)
user_prompt = (
f"I'd like to discuss the {len(items)} articles about '{topic}'. "
"Don't just summarize each one — draw connections between the sources, "
"highlight where they agree or disagree, and share your analysis of what "
"this means. Let's have a real discussion about this topic."
)
await add_message(conv_id, "user", user_prompt)
conv = await get_conversation(uid, conv_id)
assert conv is not None
history = []
for msg in conv.messages:
if msg.role == "system":
continue
msg_dict = {"role": msg.role, "content": msg.content or ""}
if msg.tool_calls:
msg_dict["tool_calls"] = [
{"function": {"name": tc["function"], "arguments": tc["arguments"]}}
for tc in msg.tool_calls
]
history.append(msg_dict)
for tc in msg.tool_calls:
history.append({"role": "tool", "content": json.dumps(tc.get("result", {}))})
else:
history.append(msg_dict)
model = await get_setting(uid, "default_model", "") or ""
assistant_msg = await add_message(conv_id, "assistant", "", status="generating")
buf = create_buffer(conv_id, assistant_msg.id)
asyncio.create_task(run_generation(
buf, history, model,
uid, conv_id, conv.title or "",
user_prompt,
think=True,
))
return jsonify({
"assistant_message_id": assistant_msg.id,
"status": "generating",
"article_count": len(items),
}), 202