Jun 29 - Improve AI analysis function fix 413 error

This commit is contained in:
2026-06-29 17:44:53 -04:00
parent c4acb93e65
commit d4917a0663
+74 -85
View File
@@ -182,27 +182,29 @@ def _extract_text(file_obj, ext: str) -> str:
# Office address used as origin for distance/travel-time estimates. # Office address used as origin for distance/travel-time estimates.
_OFFICE_ADDRESS = "2815 Hartland Road, Falls Church, VA 22043, USA" _OFFICE_ADDRESS = "2815 Hartland Road, Falls Church, VA 22043, USA"
# Text character limit sent to the API per stage-1 call. # Document text limit per API call.
# Groq's HTTP payload limit is ~100 KB; our prompt template adds ~2.5 KB of overhead, # Stage 1 prompt template is ~1.8 KB overhead; 14 KB of doc text keeps the total
# so 30K chars of document text keeps the total JSON body safely under that ceiling. # JSON payload well under Groq's request size limit on all plan tiers.
_TEXT_LIMIT = 30_000 _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 = ( _SYSTEM_PROMPT = (
"You are a senior government procurement analyst supporting a small business BD team. " "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 " "Extract and organize solicitation information with precision. "
"with precision and clarity. Format all output in well-structured Markdown using tables " "Format all output in clean Markdown with tables and section headers. "
"and headers. Never invent or assume information not present in the source documents — " "Write \"Not specified\" for any field not found in the documents — never guess."
"write \"Not specified\" for any missing field."
) )
# Stage 1 — extraction prompt (always sent) # Stage 1 — extraction prompt (always sent). Kept compact to minimise payload size.
_EXTRACTION_PROMPT = """\ _EXTRACTION_PROMPT = """\
Analyze the {n} attached government solicitation document(s) as one combined package. Analyze the {n} solicitation document(s) below as one combined package.
Complete every section below exactly as structured. Write **"Not specified"** for any field Fill in every table and section. Write **"Not specified"** for missing fields.
not found in the documents. Do not guess or invent details.
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 | | Field | Value |
|---|---| |---|---|
| **Solicitation Number** | | | Solicitation Number | |
| **Solicitation Type** | (RFP / RFQ / IFB / IDIQ / BPA / etc.) | | Solicitation Type | (RFP / RFQ / IFB / IDIQ / BPA / etc.) |
| **Issuing Agency / Office** | | | Issuing Agency / Office | |
| **Set-Aside** | (Small Business / 8(a) / SDVOSB / HUBZone / WOSB / Unrestricted / etc.) | | Set-Aside | (Small Business / 8(a) / SDVOSB / HUBZone / WOSB / Unrestricted / etc.) |
| **Contract Type** | (Firm-Fixed-Price / T&M / Cost-Plus / IDIQ / etc.) | | Contract Type | (FFP / T&M / Cost-Plus / IDIQ / etc.) |
| **Contract Period** | | | Contract Period | |
| **NAICS Code** | | | NAICS Code | |
| **Estimated Value** | | | Estimated Value | |
--- ---
## Scope of Work ## 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) ## 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 ## Key Dates & Deadlines
| Milestone | Date & Time (with timezone) | | Milestone | Date & Time |
|---|---| |---|---|
| Pre-Proposal Conference / Site Visit | | | Pre-Proposal Conference / Site Visit | |
| Questions Due | | | Questions Due | |
| **Proposal Due** | | | **Proposal Due** | |
| Award Date (if stated) | | | Award Date | |
| Period of Performance Start | | | Period of Performance Start | |
| Other Deadlines | | | Other Deadlines | |
@@ -250,33 +252,33 @@ Our office is located at: **{office}** — use this as the origin for all travel
| | | | | |
|---|---| |---|---|
| **Date & Time** | | | Date & Time | |
| **Full Address** | | | Full Address | |
| **Attendance** | (Mandatory / Optional / Not applicable) | | Attendance | (Mandatory / Optional / N/A) |
| **RSVP Required** | (Yes — deadline & method / No) | | RSVP Required | |
--- ---
## Point of Contact (POC) ## Point of Contact
| | | | | |
|---|---| |---|---|
| **Name / Title** | | | Name / Title | |
| **Phone** | | | Phone | |
| **Email** | | | Email | |
| **Questions Submission Method** | | | Questions Submission Method | |
--- ---
## Proposal Requirements ## 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 ## 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} | | Origin | {office} |
| **Destination** | (Pre-Proposal / Primary Work Site address) | | Destination | |
| **Estimated Driving Distance** | | | Estimated Driving Distance | |
| **Estimated Drive Time** | (normal traffic conditions) | | 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 ## Total Square Footage
*(If applicable to the scope of work; write "N/A" if not.)* *(If applicable; write "N/A" if not.)*
--- ---
## Overall Summary ## Overall Summary
Provide a concise but thorough summary covering: - **What Is Being Procured:**
- **Contract Period & Value:**
- **What is Being Procured** — Scope and key deliverables. - **Key Constraints:**
- **Contract Period & Estimated Value** — Duration and any stated ceiling or estimate. - **Immediate Action Items & Deadlines:**
- **Key Constraints & Requirements** — Timeline, location, special certifications, etc.
- **Immediate Action Items** — What the team must do and by when.
--- ---
DOCUMENTS: DOCUMENTS:
{documents}""" {documents}"""
# Stage 2 — criteria evaluation (sent as a separate API call when active criteria exist). # Stage 2 — criteria evaluation (separate API call, only when active criteria exist).
# Receives the clean Stage 1 extraction output as {summary}, not the raw documents, # Receives a capped slice of the stage 1 summary not the raw documents — so the
# so the model can focus entirely on the evaluation without re-parsing document noise. # model can focus on evaluation without re-parsing source noise.
_CRITERIA_PROMPT = """\ _CRITERIA_PROMPT = """\
Below is an extracted and summarized government solicitation opportunity. Below is an extracted summary of a government solicitation. Evaluate whether our
Evaluate whether our company should pursue it based on our evaluation criteria. company should pursue it based on the criteria listed.
---
## OPPORTUNITY SUMMARY ## OPPORTUNITY SUMMARY
@@ -336,42 +334,32 @@ Evaluate whether our company should pursue it based on our evaluation criteria.
## ALIGNMENT EVALUATION ## ALIGNMENT EVALUATION
**Our evaluation criteria:** Our criteria:
{criteria_list} {criteria_list}
For **each criterion** listed above, provide: For each criterion, state:
- A status: ✅ **MEETS** / ⚠️ **PARTIALLY MEETS** / ❌ **DOES NOT MEET** / ❓ **CANNOT DETERMINE** - MEETS / ⚠️ PARTIALLY MEETS / ❌ DOES NOT MEET / ❓ CANNOT DETERMINE
- A 12 sentence explanation citing specific details from the summary above. - One or two sentences citing specific details from the summary.
--- ---
## Overall Recommendation ## 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 RECOMMENDATION: PURSUE
```
or
```
RECOMMENDATION: PASS RECOMMENDATION: PASS
```
or
```
RECOMMENDATION: UNCLEAR RECOMMENDATION: UNCLEAR
```
**PURSUE** — opportunity clearly aligns with most criteria and is competitive. PURSUE = clearly aligns with most criteria and is competitive.
**PASS** — fails one or more critical criteria or presents unacceptable risk. PASS = fails one or more critical criteria or presents unacceptable risk.
**UNCLEAR** — insufficient information to make a confident determination. UNCLEAR = insufficient information for a confident decision.
---
## Executive Summary ## Executive Summary
Write 34 sentences in plain business language explaining your recommendation: 34 sentences in plain business language: strongest reasons to pursue or pass,
the strongest reasons to pursue or pass, and the single biggest risk or opportunity.""" and the single biggest risk or opportunity."""
def _call_groq_api(api_key: str, model: str, user_message: str) -> str: 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": "system", "content": _SYSTEM_PROMPT},
{"role": "user", "content": user_message}, {"role": "user", "content": user_message},
], ],
"max_tokens": 8192, "max_tokens": 4096,
"temperature": 0.1, "temperature": 0.1,
}, },
timeout=120, 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: def _call_groq(api_key: str, model: str, text: str, criteria: list) -> dict:
""" """
Two-stage Groq analysis: Two-stage Groq analysis:
Stage 1 (always): extract structured fields + overall summary from documents. Stage 1 (always): extract structured fields + summary from the documents.
Stage 2 (optional): evaluate criteria against the Stage 1 output (not raw docs), Stage 2 (optional): evaluate criteria against the capped stage-1 summary,
producing a focused RECOMMENDATION with per-criterion scoring. not the raw docs, for a focused and reliable result.
""" """
truncated = len(text) > _TEXT_LIMIT truncated = len(text) > _TEXT_LIMIT
doc_text = 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: if not criteria:
return {"verdict": None, "summary": summary, "truncated": truncated} 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( criteria_list = "\n".join(
f"{i+1}. **{c['title']}**: {c['description']}" f"{i+1}. **{c['title']}**: {c['description']}"
for i, c in enumerate(criteria) for i, c in enumerate(criteria)
) )
stage2_prompt = _CRITERIA_PROMPT.format( 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) evaluation = _call_groq_api(api_key, model, stage2_prompt)