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 = "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 12 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
```
**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 34 sentences in plain business language explaining your recommendation:
the strongest reasons to pursue or pass, and the single biggest risk or opportunity."""
34 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)