-
Notifications
You must be signed in to change notification settings - Fork 1
feat: research public sources for post units #714
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
900f1a4
4188373
aebfd93
601545d
24a852c
5f294c7
aa93318
bfe9816
f92bc98
53a4b8b
9814972
76a602c
c720978
4b00f41
2b3bfab
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| # 2.19.0 — Post-scoped source-reference research | ||
|
|
||
| ## Added | ||
|
|
||
| - Public posts can research a highlighted passage or image detail against a | ||
| cited public page (ADR 0248, remaining ADR 0133 / issue #611). The workflow | ||
| reuses self-hosted SearXNG, retrieves one public HTTP(S) target with | ||
| redirects disabled and non-global addresses rejected, and judges through | ||
| contextual-orchestrator `mode=verify`. Private posts fail closed without | ||
| egress. Deployments must set both source-research resource budgets explicitly; | ||
| otherwise the channel remains unavailable. Citations persist in 3NF | ||
| `source_research_citation`. | ||
| - Reader next action: open the cited public resource, then compare it with | ||
| the highlighted passage or image detail. Supported or refuted judgments | ||
| without a cited URL downgrade to not enough information. Missing search, | ||
| retrieval, or adjudication is `research_unavailable`, never a fabricated | ||
| score. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -152,6 +152,10 @@ | |
| ) | ||
| from backend.app.ranking_ingestion import load_visible_ranking_posts | ||
| from backend.app.relation_verification_ingestion import verify_post_relations_from_pool | ||
| from backend.app.source_research_ingestion import ( | ||
| list_source_research_citations, | ||
| research_post_sources_from_pool, | ||
| ) | ||
| from backend.app.report_ingestion import ( | ||
| GROUPING_KINDS, | ||
| fetch_period_comparison, | ||
|
|
@@ -239,6 +243,12 @@ | |
| NullRelationVerificationClient, | ||
| SearxngRelationVerificationClient, | ||
| ) | ||
| from lineageweave.source_reference_research import ( | ||
| PRIVATE_POST_UNAVAILABLE, | ||
| VISIBILITY_PUBLIC, | ||
| NullSourceResearchClient, | ||
| SearxngOrchestratedSourceResearchClient, | ||
| ) | ||
| from lineageweave.semantic_hints import customer_hint_trust, format_semantic_hints | ||
| from lineageweave.semantic_query import ( | ||
| ContextualOrchestratorSemanticQueryClient, | ||
|
|
@@ -349,6 +359,27 @@ def _claim_verification_client_factory(): | |
| return _claim_verification_client() | ||
|
|
||
|
|
||
| def _source_research_client(): | ||
| """Return the post-scoped public-research client, or its unavailable null.""" | ||
|
|
||
| settings = load_settings() | ||
| if not ( | ||
| settings.searxng_base_url | ||
| and settings.orchestrator_base_url | ||
| and settings.orchestrator_api_key | ||
| and settings.source_research_maximum_leads is not None | ||
| and settings.source_research_maximum_results is not None | ||
| ): | ||
| return NullSourceResearchClient() | ||
| return SearxngOrchestratedSourceResearchClient( | ||
| settings.searxng_base_url, | ||
| settings.orchestrator_base_url, | ||
| settings.orchestrator_api_key, | ||
| maximum_leads=settings.source_research_maximum_leads, | ||
| maximum_results=settings.source_research_maximum_results, | ||
| ) | ||
|
|
||
|
|
||
| def _organization_name_resolution_client(): | ||
| """Live orchestrator client when configured; otherwise the unavailable null.""" | ||
| settings = load_settings() | ||
|
|
@@ -2459,6 +2490,112 @@ async def verify_post_entity_relationships( | |
| } | ||
|
|
||
|
|
||
| @app.get("/api/posts/{post_id}/research-citations") | ||
| async def read_post_research_citations( | ||
| post_id: str, | ||
| account: CurrentAccount = Depends(get_current_account), | ||
| pool: asyncpg.Pool = Depends(get_pool), | ||
| ) -> dict[str, Any]: | ||
| """Return persisted public-research citations for this post's source leads.""" | ||
|
|
||
| post = await _load_visible_post(post_id, account, pool) | ||
| if str(post["visibility_code"]) != VISIBILITY_PUBLIC: | ||
| return { | ||
| "post_id": str(post["post_id"]), | ||
| "visibility_code": post["visibility_code"], | ||
| "unavailable_reason": PRIVATE_POST_UNAVAILABLE, | ||
| "citations": [], | ||
| } | ||
| async with pool.acquire() as conn: | ||
| citations = await list_source_research_citations(conn, post_id) | ||
| return { | ||
| "post_id": str(post["post_id"]), | ||
| "visibility_code": post["visibility_code"], | ||
| "unavailable_reason": None, | ||
| "citations": [ | ||
| { | ||
| "lead_kind_code": row["lead_kind_code"], | ||
| "lead_source_unit_id": row["lead_source_unit_id"], | ||
| "lead_image_region_id": row["lead_image_region_id"], | ||
| "lead_excerpt_text": row["lead_excerpt_text"], | ||
| "search_query_text": row["search_query_text"], | ||
| "evidence_url": row["evidence_url"], | ||
| "evidence_title_text": row["evidence_title_text"], | ||
| "evidence_excerpt_text": row["evidence_excerpt_text"], | ||
| "judgment_code": row["judgment_code"], | ||
| "rationale_text": row["rationale_text"], | ||
| "next_action_text": row["next_action_text"], | ||
| "checked_at": row["checked_at"], | ||
| } | ||
| for row in citations | ||
| ], | ||
| } | ||
|
seonghobae marked this conversation as resolved.
|
||
|
|
||
|
|
||
| @app.post("/api/posts/{post_id}/research-citations") | ||
| async def research_post_source_references( | ||
| post_id: str, | ||
| account: CurrentAccount = Depends(get_current_account), | ||
| pool: asyncpg.Pool = Depends(get_pool), | ||
| valkey: redis.Redis = Depends(get_valkey), | ||
| ) -> dict[str, Any]: | ||
| """Search and retrieve a public resource for this post's source leads. | ||
|
|
||
| Private posts fail closed without sending content. Gated by post_admin | ||
| because retrieval is a real external-search write action. | ||
| """ | ||
|
|
||
| _require_post_admin(account) | ||
| post = await _load_visible_post(post_id, account, pool) | ||
| if str(post["visibility_code"]) != VISIBILITY_PUBLIC: | ||
| return { | ||
| "post_id": str(post["post_id"]), | ||
| "visibility_code": post["visibility_code"], | ||
| "unavailable_reason": PRIVATE_POST_UNAVAILABLE, | ||
| "citations": [], | ||
| } | ||
| client = _source_research_client() | ||
| if not client.available: | ||
| raise HTTPException( | ||
| status.HTTP_503_SERVICE_UNAVAILABLE, | ||
| "Public research is unavailable. Ask an administrator to enable it, " | ||
| "then try again.", | ||
| ) | ||
|
Comment on lines
+2557
to
+2563
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📝 Info: Client construction sits outside the fail-closed try block
Was this helpful? React with 👍 or 👎 to provide feedback. |
||
| try: | ||
| with use_llm_metadata(build_post_llm_metadata(post_id, post)): | ||
| run = await research_post_sources_from_pool( | ||
| pool, | ||
| client, | ||
| post_id, | ||
| visibility_code=str(post["visibility_code"]), | ||
| ) | ||
| except (HttpClientError, OSError, ValueError) as exc: | ||
| raise HTTPException( | ||
| status.HTTP_503_SERVICE_UNAVAILABLE, | ||
| "Public research could not be completed. Try again later or review " | ||
| "this post's existing evidence.", | ||
| ) from exc | ||
| except Exception as exc: # noqa: BLE001 - provider boundary is fail-closed. | ||
| raise HTTPException( | ||
| status.HTTP_503_SERVICE_UNAVAILABLE, | ||
| "Public research could not be completed. Try again later or review " | ||
| "this post's existing evidence.", | ||
| ) from exc | ||
| await publish_activity_event( | ||
| valkey, | ||
| post_id, | ||
| "source_research_checked", | ||
| account.user_account_id, | ||
| f"Public sources reviewed: {len(run.citations)} item(s)", | ||
| ) | ||
|
seonghobae marked this conversation as resolved.
seonghobae marked this conversation as resolved.
seonghobae marked this conversation as resolved.
|
||
| return { | ||
| "post_id": run.post_id, | ||
| "visibility_code": run.visibility_code, | ||
| "unavailable_reason": run.unavailable_reason, | ||
| "citations": [citation.to_payload() for citation in run.citations], | ||
| } | ||
|
|
||
|
|
||
| @app.post("/api/posts/{post_id}/extract-keymen") | ||
| async def extract_post_keymen( | ||
| post_id: str, | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.