Spaces:
Runtime error
Runtime error
| from __future__ import annotations | |
| from dataclasses import dataclass | |
| from typing import Any, Callable | |
| import json | |
| from pathlib import Path | |
| import sys | |
| from .demo_packs import DemoPack, read_text_inputs | |
| from .embedding import extract_keywords | |
| from .logging_utils import setup_logging | |
| from .model_runtime import DEFAULT_MODEL_ID, generate_text_completion, load_llama, resolve_model_path | |
| from .storage import SQLiteStore | |
| LOGGER = setup_logging("p1_elder_paperwork", stream=sys.stderr) | |
| class ProjectSpec: | |
| key: str | |
| title: str | |
| description: str | |
| data_subdir: str | |
| search_enabled: bool | |
| inbox_label: str | |
| processor: Callable[[DemoPack, SQLiteStore, Any], dict[str, Any]] | |
| def run_pack(self, pack: DemoPack, store: SQLiteStore, config: Any) -> dict[str, Any]: | |
| return self.processor(pack, store, config) | |
| def _base_result( | |
| pack: DemoPack, | |
| store: SQLiteStore, | |
| project: str, | |
| title: str, | |
| primary_text: str, | |
| payload: dict[str, Any], | |
| search_text: str | None = None, | |
| ) -> dict[str, Any]: | |
| record_id = store.store_record(project, pack.pack_id, title, primary_text, payload, status='ready') | |
| store.store_embedding(record_id, project, search_text or primary_text, metadata={'pack_id': pack.pack_id}) | |
| return {'record_id': record_id, 'pack_id': pack.pack_id, 'project': project, **payload} | |
| def _first_input_kind(pack: DemoPack) -> str: | |
| inputs = pack.manifest.get('inputs', []) | |
| if isinstance(inputs, list) and inputs: | |
| first = inputs[0] | |
| if isinstance(first, dict): | |
| kind = first.get('kind') | |
| if isinstance(kind, str) and kind.strip(): | |
| return kind.strip() | |
| return 'text' | |
| def _build_user_prompt(text: str, excerpt: str, keywords: tuple[str, ...], pack: DemoPack) -> str: | |
| questions = [ | |
| 'What is this document about?', | |
| 'What action is requested?', | |
| 'Is there a deadline or date mentioned?', | |
| 'Is there an amount, phone number, or next step mentioned?', | |
| ] | |
| return ( | |
| "You are an elder paperwork triage assistant. Answer using only the document content below. " | |
| "Return strict JSON with these keys: triage, summary, qa, citations, ocr_preview, safety. " | |
| "triage must be one of urgent, important, FYI, informational. " | |
| "qa must be a list of four objects, each with question, answer, and citation fields. " | |
| "citations must be a list of four objects, each with question and snippet fields. " | |
| "safety must be an object with missing_info_policy and invented_values fields. " | |
| "Do not invent facts; quote short source snippets for citations when possible.\n\n" | |
| f"PACK_ID: {pack.pack_id}\n" | |
| f"EXPECTED_SIGNALS: {json.dumps(pack.expected_signals, ensure_ascii=False)}\n" | |
| f"DOCUMENT_KIND: {_first_input_kind(pack)}\n" | |
| f"KEYWORDS: {', '.join(keywords) or 'none'}\n" | |
| f"DOCUMENT_EXCERPT:\n{excerpt}\n\n" | |
| f"DOCUMENT_TEXT:\n{text[:4000]}\n\n" | |
| "QUESTIONS:\n" | |
| + "\n".join(f"{idx}. {question}" for idx, question in enumerate(questions, start=1)) | |
| ) | |
| def processor_p1(pack: DemoPack, store: SQLiteStore, config: Any) -> dict[str, Any]: | |
| text = read_text_inputs(pack).strip() | |
| if not text: | |
| raise RuntimeError("P1 requires document text for model inference; no readable text was found in the pack.") | |
| excerpt = text.splitlines()[0].strip() if text.splitlines() else text[:240].strip() | |
| keywords = tuple(extract_keywords(text)) | |
| model = resolve_model_path(model_id=DEFAULT_MODEL_ID) | |
| llm = load_llama(str(model.model_path)) | |
| def _final_text(raw: str) -> str: | |
| cleaned = raw.replace('</think>', '\n').replace('<think>', '\n') | |
| parts = [part.strip() for part in cleaned.splitlines() if part.strip()] | |
| return parts[-1] if parts else raw.strip() | |
| def _normalize_triage(raw: str) -> str: | |
| lowered = raw.lower() | |
| if 'urgent' in lowered: | |
| return 'urgent' | |
| if 'important' in lowered: | |
| return 'important' | |
| if 'fyi' in lowered: | |
| return 'FYI' | |
| return 'informational' | |
| questions = [ | |
| 'What is this document about?', | |
| 'What action is requested?', | |
| 'Is there a deadline or date mentioned?', | |
| 'Is there an amount, phone number, or next step mentioned?', | |
| ] | |
| source_snippet = excerpt if excerpt else text[:180].strip() | |
| triage_prompt = ( | |
| "Classify the document into exactly one label: urgent, important, FYI, informational.\n" | |
| "Rules:\n" | |
| "- urgent: immediate danger, same-day emergency, urgent medical action.\n" | |
| "- important: routine appointment notices, follow-up visits, insurance notices, medication lists, or forms needing action soon.\n" | |
| "- FYI: optional informational notices.\n" | |
| "- informational: archival or purely informational documents.\n" | |
| "For routine follow-up appointment notices, the correct label is important.\n" | |
| f"Document:\n{text[:3000]}\n\n" | |
| "Return exactly one label." | |
| ) | |
| triage_raw, triage_meta = generate_text_completion( | |
| llm=llm, | |
| model=model, | |
| system_prompt='Return only the label.', | |
| user_prompt=triage_prompt, | |
| temperature=0.0, | |
| max_tokens=512, | |
| ) | |
| triage = _normalize_triage(_final_text(triage_raw)) | |
| summary_prompt = ( | |
| "Document text:\n" | |
| f"{text[:3500]}\n\n" | |
| "Write one concise sentence summarizing the document." | |
| ) | |
| summary_raw, summary_meta = generate_text_completion( | |
| llm=llm, | |
| model=model, | |
| system_prompt='Write one concise sentence only.', | |
| user_prompt=summary_prompt, | |
| temperature=0.0, | |
| max_tokens=96, | |
| ) | |
| summary = _final_text(summary_raw) | |
| safety_raw, safety_meta = generate_text_completion( | |
| llm=llm, | |
| model=model, | |
| system_prompt='Return one short clause only.', | |
| user_prompt=( | |
| "Based on the document below, state whether more information is needed in one short clause. " | |
| "Use phrasing like 'missing info likely' or 'sufficient detail'.\n\n" | |
| f"{text[:2200]}" | |
| ), | |
| temperature=0.0, | |
| max_tokens=24, | |
| ) | |
| safety_note = _final_text(safety_raw) | |
| qa_items: list[dict[str, str]] = [] | |
| qa_stats: list[dict[str, Any]] = [] | |
| for question in questions: | |
| answer_raw, answer_meta = generate_text_completion( | |
| llm=llm, | |
| model=model, | |
| system_prompt='Answer the question using only the document text.', | |
| user_prompt=( | |
| f"QUESTION: {question}\n\n" | |
| f"DOCUMENT TEXT:\n{text[:3500]}\n\n" | |
| "Return one short sentence only." | |
| ), | |
| temperature=0.0, | |
| max_tokens=96, | |
| ) | |
| qa_items.append({'question': question, 'answer': _final_text(answer_raw), 'citation': source_snippet}) | |
| qa_stats.append(answer_meta['generation_stats']) | |
| generation_stats = { | |
| 'triage': triage_meta['generation_stats'], | |
| 'summary': summary_meta['generation_stats'], | |
| 'safety': safety_meta['generation_stats'], | |
| 'qa': qa_stats, | |
| } | |
| inference_meta = { | |
| 'model_id': model.model_id, | |
| 'model_path': str(model.model_path), | |
| 'model_source': model.source, | |
| 'backend': model.backend, | |
| 'generation_stats': generation_stats, | |
| } | |
| payload = { | |
| 'triage': triage, | |
| 'summary': summary, | |
| 'qa': qa_items, | |
| 'citations': [{'question': question, 'snippet': source_snippet} for question in questions], | |
| 'ocr_preview': summary, | |
| 'ocr_text': text, | |
| 'safety': {'missing_info_policy': safety_note, 'invented_values': False}, | |
| 'inbox_items': [ | |
| { | |
| 'record_id': 'pending', | |
| 'title': pack.pack_id, | |
| 'triage': triage, | |
| 'summary': summary, | |
| 'file_type': _first_input_kind(pack), | |
| }, | |
| ], | |
| 'expected_signals': pack.expected_signals, | |
| 'evidence': keywords, | |
| 'inference': inference_meta, | |
| 'model_id': inference_meta['model_id'], | |
| 'adapter_name': inference_meta['backend'], | |
| 'generation_stats': generation_stats, | |
| 'source_excerpt': source_snippet, | |
| } | |
| search_text = ' '.join([text, summary, triage, ' '.join(keywords)]) | |
| result = _base_result(pack, store, 'p1', f'P1: {pack.pack_id}', payload['summary'], payload, search_text) | |
| result['record_ids'] = [result['record_id']] | |
| result['documents'] = [payload] | |
| result['triage'] = triage | |
| result['summary'] = summary | |
| result['qa'] = qa_items | |
| result['citations'] = payload['citations'] | |
| result['ocr_preview'] = payload['ocr_preview'] | |
| result['ocr_text'] = payload['ocr_text'] | |
| result['safety'] = payload['safety'] | |
| result['inbox_items'] = payload['inbox_items'] | |
| LOGGER.info( | |
| json.dumps( | |
| { | |
| 'event': 'p1_model_inference', | |
| 'pack_id': pack.pack_id, | |
| 'model_id': inference_meta['model_id'], | |
| 'adapter_name': inference_meta['backend'], | |
| 'generation_stats': generation_stats, | |
| 'triage': triage, | |
| }, | |
| ensure_ascii=False, | |
| ) | |
| ) | |
| return result | |