From d4917a0663a94058ea38945b6f2a2b9474666bd2 Mon Sep 17 00:00:00 2001 From: NguyenND Date: Mon, 29 Jun 2026 17:44:53 -0400 Subject: [PATCH] Jun 29 - Improve AI analysis function fix 413 error --- routes/ai_summary.py | 165 ++++++++++++++++++++----------------------- 1 file changed, 77 insertions(+), 88 deletions(-) diff --git a/routes/ai_summary.py b/routes/ai_summary.py index b146b2c..60307e1 100644 --- a/routes/ai_summary.py +++ b/routes/ai_summary.py @@ -182,27 +182,29 @@ def _extract_text(file_obj, ext: str) -> str: # Office address used as origin for distance/travel-time estimates. _OFFICE_ADDRESS = "2815 Hartland Road, Falls Church, VA 22043, USA" -# Text character limit sent to the API per stage-1 call. -# Groq's HTTP payload limit is ~100 KB; our prompt template adds ~2.5 KB of overhead, -# so 30K chars of document text keeps the total JSON body safely under that ceiling. -_TEXT_LIMIT = 30_000 +# Document text limit per API call. +# Stage 1 prompt template is ~1.8 KB overhead; 14 KB of doc text keeps the total +# JSON payload well under Groq's request size limit on all plan tiers. +_TEXT_LIMIT = 14_000 -# Analyst persona injected as the system message in every API call. +# Summary passed to stage 2. Stage 1 can produce up to ~16 KB of output; +# capping it here prevents stage 2's payload from growing unbounded. +_SUMMARY_LIMIT = 8_000 + +# Analyst persona — injected as the system message in every API call. _SYSTEM_PROMPT = ( "You are a senior government procurement analyst supporting a small business BD team. " - "Your role is to extract, organize, and evaluate information from solicitation documents " - "with precision and clarity. Format all output in well-structured Markdown using tables " - "and headers. Never invent or assume information not present in the source documents — " - "write \"Not specified\" for any missing field." + "Extract and organize solicitation information with precision. " + "Format all output in clean Markdown with tables and section headers. " + "Write \"Not specified\" for any field not found in the documents — never guess." ) -# Stage 1 — extraction prompt (always sent) +# Stage 1 — extraction prompt (always sent). Kept compact to minimise payload size. _EXTRACTION_PROMPT = """\ -Analyze the {n} attached government solicitation document(s) as one combined package. -Complete every section below exactly as structured. Write **"Not specified"** for any field -not found in the documents. Do not guess or invent details. +Analyze the {n} solicitation document(s) below as one combined package. +Fill in every table and section. Write **"Not specified"** for missing fields. -Our office is located at: **{office}** — use this as the origin for all travel estimates. +Our office: **{office}** — use as origin for travel estimates. --- @@ -210,37 +212,37 @@ Our office is located at: **{office}** — use this as the origin for all travel | Field | Value | |---|---| -| **Solicitation Number** | | -| **Solicitation Type** | (RFP / RFQ / IFB / IDIQ / BPA / etc.) | -| **Issuing Agency / Office** | | -| **Set-Aside** | (Small Business / 8(a) / SDVOSB / HUBZone / WOSB / Unrestricted / etc.) | -| **Contract Type** | (Firm-Fixed-Price / T&M / Cost-Plus / IDIQ / etc.) | -| **Contract Period** | | -| **NAICS Code** | | -| **Estimated Value** | | +| Solicitation Number | | +| Solicitation Type | (RFP / RFQ / IFB / IDIQ / BPA / etc.) | +| Issuing Agency / Office | | +| Set-Aside | (Small Business / 8(a) / SDVOSB / HUBZone / WOSB / Unrestricted / etc.) | +| Contract Type | (FFP / T&M / Cost-Plus / IDIQ / etc.) | +| Contract Period | | +| NAICS Code | | +| Estimated Value | | --- ## Scope of Work -*Describe what is required in full detail: services, deliverables, performance standards, and any technical requirements. Use bullet points.* +*(Detail required services, deliverables, and performance standards. Use bullet points.)* --- ## Performance Location(s) -*List all work sites. Note whether remote or on-site work is permitted.* +*(All work sites; whether remote or on-site work is permitted.)* --- ## Key Dates & Deadlines -| Milestone | Date & Time (with timezone) | +| Milestone | Date & Time | |---|---| | Pre-Proposal Conference / Site Visit | | | Questions Due | | | **Proposal Due** | | -| Award Date (if stated) | | +| Award Date | | | Period of Performance Start | | | Other Deadlines | | @@ -250,33 +252,33 @@ Our office is located at: **{office}** — use this as the origin for all travel | | | |---|---| -| **Date & Time** | | -| **Full Address** | | -| **Attendance** | (Mandatory / Optional / Not applicable) | -| **RSVP Required** | (Yes — deadline & method / No) | +| Date & Time | | +| Full Address | | +| Attendance | (Mandatory / Optional / N/A) | +| RSVP Required | | --- -## Point of Contact (POC) +## Point of Contact | | | |---|---| -| **Name / Title** | | -| **Phone** | | -| **Email** | | -| **Questions Submission Method** | | +| Name / Title | | +| Phone | | +| Email | | +| Questions Submission Method | | --- ## Proposal Requirements -*Summarize format, page limits, required sections/volumes, submission method (portal/email/mail), and number of copies. Use bullet points.* +*(Format, page limits, volumes, submission method, number of copies.)* --- ## Evaluation Factors -*List evaluation criteria and their weights or order of priority as stated in the solicitation.* +*(Government's evaluation criteria and weights as stated in the solicitation.)* --- @@ -284,49 +286,45 @@ Our office is located at: **{office}** — use this as the origin for all travel | | | |---|---| -| **Origin** | {office} | -| **Destination** | (Pre-Proposal / Primary Work Site address) | -| **Estimated Driving Distance** | | -| **Estimated Drive Time** | (normal traffic conditions) | +| Origin | {office} | +| Destination | | +| Estimated Driving Distance | | +| Estimated Drive Time | | -*(AI estimate — verify with a mapping service before scheduling travel.)* +*(AI estimates — verify with a mapping service before scheduling.)* --- -## Notable Requirements & Red Flags +## Red Flags & Notable Requirements -*List anything that affects bid/no-bid decisions: unusual insurance or bonding levels, required security clearances, certifications, teaming or subcontracting restrictions, incumbent advantage indicators, aggressive timelines, or any other risk factors.* +*(Security clearances, bonding, certifications, teaming restrictions, tight timelines, incumbent indicators, or other bid/no-bid risk factors.)* --- ## Total Square Footage -*(If applicable to the scope of work; write "N/A" if not.)* +*(If applicable; write "N/A" if not.)* --- ## Overall Summary -Provide a concise but thorough summary covering: - -- **What is Being Procured** — Scope and key deliverables. -- **Contract Period & Estimated Value** — Duration and any stated ceiling or estimate. -- **Key Constraints & Requirements** — Timeline, location, special certifications, etc. -- **Immediate Action Items** — What the team must do and by when. +- **What Is Being Procured:** +- **Contract Period & Value:** +- **Key Constraints:** +- **Immediate Action Items & Deadlines:** --- DOCUMENTS: {documents}""" -# Stage 2 — criteria evaluation (sent as a separate API call when active criteria exist). -# Receives the clean Stage 1 extraction output as {summary}, not the raw documents, -# so the model can focus entirely on the evaluation without re-parsing document noise. +# Stage 2 — criteria evaluation (separate API call, only when active criteria exist). +# Receives a capped slice of the stage 1 summary — not the raw documents — so the +# model can focus on evaluation without re-parsing source noise. _CRITERIA_PROMPT = """\ -Below is an extracted and summarized government solicitation opportunity. -Evaluate whether our company should pursue it based on our evaluation criteria. - ---- +Below is an extracted summary of a government solicitation. Evaluate whether our +company should pursue it based on the criteria listed. ## OPPORTUNITY SUMMARY @@ -336,42 +334,32 @@ Evaluate whether our company should pursue it based on our evaluation criteria. ## ALIGNMENT EVALUATION -**Our evaluation criteria:** - +Our criteria: {criteria_list} -For **each criterion** listed above, provide: -- A status: ✅ **MEETS** / ⚠️ **PARTIALLY MEETS** / ❌ **DOES NOT MEET** / ❓ **CANNOT DETERMINE** -- A 1–2 sentence explanation citing specific details from the summary above. +For each criterion, state: +- ✅ MEETS / ⚠️ PARTIALLY MEETS / ❌ DOES NOT MEET / ❓ CANNOT DETERMINE +- One or two sentences citing specific details from the summary. --- ## Overall Recommendation -State your recommendation on its own line in **exactly** this format (required for system parsing — do not alter the label): +Write your recommendation on its own line in exactly one of these forms +(machine-read — do not alter the label): -``` -RECOMMENDATION: PURSUE -``` -or -``` -RECOMMENDATION: PASS -``` -or -``` -RECOMMENDATION: UNCLEAR -``` + RECOMMENDATION: PURSUE + RECOMMENDATION: PASS + RECOMMENDATION: UNCLEAR -**PURSUE** — opportunity clearly aligns with most criteria and is competitive. -**PASS** — fails one or more critical criteria or presents unacceptable risk. -**UNCLEAR** — insufficient information to make a confident determination. - ---- +PURSUE = clearly aligns with most criteria and is competitive. +PASS = fails one or more critical criteria or presents unacceptable risk. +UNCLEAR = insufficient information for a confident decision. ## Executive Summary -Write 3–4 sentences in plain business language explaining your recommendation: -the strongest reasons to pursue or pass, and the single biggest risk or opportunity.""" +3–4 sentences in plain business language: strongest reasons to pursue or pass, +and the single biggest risk or opportunity.""" def _call_groq_api(api_key: str, model: str, user_message: str) -> str: @@ -388,7 +376,7 @@ def _call_groq_api(api_key: str, model: str, user_message: str) -> str: {"role": "system", "content": _SYSTEM_PROMPT}, {"role": "user", "content": user_message}, ], - "max_tokens": 8192, + "max_tokens": 4096, "temperature": 0.1, }, timeout=120, @@ -402,9 +390,9 @@ def _call_groq_api(api_key: str, model: str, user_message: str) -> str: def _call_groq(api_key: str, model: str, text: str, criteria: list) -> dict: """ Two-stage Groq analysis: - Stage 1 (always): extract structured fields + overall summary from documents. - Stage 2 (optional): evaluate criteria against the Stage 1 output (not raw docs), - producing a focused RECOMMENDATION with per-criterion scoring. + Stage 1 (always): extract structured fields + summary from the documents. + Stage 2 (optional): evaluate criteria against the capped stage-1 summary, + not the raw docs, for a focused and reliable result. """ truncated = len(text) > _TEXT_LIMIT doc_text = text[:_TEXT_LIMIT] @@ -419,13 +407,14 @@ def _call_groq(api_key: str, model: str, text: str, criteria: list) -> dict: if not criteria: return {"verdict": None, "summary": summary, "truncated": truncated} - # ── Stage 2: criteria evaluation (uses clean summary, not raw docs) ──────── + # ── Stage 2: criteria evaluation ─────────────────────────────────────────── criteria_list = "\n".join( f"{i+1}. **{c['title']}**: {c['description']}" for i, c in enumerate(criteria) ) stage2_prompt = _CRITERIA_PROMPT.format( - summary=summary, criteria_list=criteria_list + summary=summary[:_SUMMARY_LIMIT], # cap to control payload size + criteria_list=criteria_list, ) evaluation = _call_groq_api(api_key, model, stage2_prompt)