
We are building a Requirements Risk Analyzer that reads individual engineering requirement strings and returns structured JSON flags for ambiguity, missing units, and untestable statements. Systems engineers can drop this into a CI pipeline to catch specification defects before they reach the PLM system. The whole thing runs against Oxlo.ai using the OpenAI SDK, so you do not need to learn a new client library.
What you'll need
- Python 3.10 or newer
pip install openai- An Oxlo.ai API key from https://portal.oxlo.ai
- A few sample requirement strings to test against
Step 1: Initialize the Oxlo.ai client
I import the SDK and instantiate the client against Oxlo.ai. Because Oxlo.ai exposes an OpenAI-compatible endpoint, the only change from a standard OpenAI script is the base_url. I pick llama-3.3-70b for the baseline because it handles structured extraction consistently, and Oxlo.ai serves it with no cold starts. That matters in CI, where the first call in a job must return immediately or the pipeline times out.
from openai import OpenAI
client = OpenAI(base_url="https://api.oxlo.ai/v1", api_key="YOUR_OXLO_API_KEY")
In production I swap the hardcoded key for an environment variable so it never touches disk.
Step 2: Lock down the system prompt
The system prompt is the contract. It forces the model to emit only JSON with four fixed fields. I keep the wording tight and avoid asking for prose. If the prompt allows bullet points or markdown, a parser downstream will break. I also explicitly ban markdown fences so I can pipe the raw string straight into json.loads.
SYSTEM_PROMPT = """You are a requirements quality engine. Analyze the user's engineering requirement and return strictly JSON with these keys:
- finding: one of [CLEAR, AMBIGUOUS, MISSING_UNITS, UNTESTABLE, INCONSISTENT]
- severity: one of [LOW, MEDIUM, HIGH, CRITICAL]
- reason: a one-sentence explanation
- suggestion: a one-sentence fix
Rules:
- If a numerical threshold lacks units, return MISSING_UNITS.
- If a requirement cannot be verified by test or inspection, return UNTESTABLE.
- If wording allows multiple interpretations, return AMBIGUOUS.
- Otherwise return CLEAR.
Return only the JSON object, no markdown fences."""
Step 3: Write the analysis function
This is the core of the agent. I send the requirement and the prompt to Oxlo.ai. I use llama-3.3-70b because it follows structured instructions tightly, which matters when you are parsing JSON in a pipeline. The function returns a native Python dict, so the rest of the script does not need to know anything about LLMs.
import json
def analyze_requirement(req_text: str) -> dict:
response = client.chat.completions.create(
model="llama-3.3-70b",
messages=[
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": req_text},
],
)
raw = response.choices[0].message.content.strip()
return json.loads(raw)
Step 4: Process a full specification
Real projects do not ship one requirement at a time. I build a batch runner that loops over a list, calls Oxlo.ai for each line, and prints a markdown table. Because Oxlo.ai uses flat per-request pricing, running fifty requirements costs the same per call regardless of how verbose each requirement is. That makes budget forecasting trivial when you are scanning thousand-line system specifications. I keep the table width narrow so it renders cleanly in CI logs without line wrapping.
REQUIREMENTS = [
"The bracket shall withstand a load of 500.",
"The firmware shall be user-friendly.",
"The valve shall close within 2 seconds of signal loss.",
"The housing color shall be approximately dark blue.",
]
def run_spec(requirements: list[str]):
print("| # | Requirement | Finding | Severity | Suggestion |")
print("|---|-------------|---------|----------|------------|")
for i, req in enumerate(requirements, 1):
result = analyze_requirement(req)
print(f"| {i} | {req[:40]}... | {result['finding']} | {result['severity']} | {result['suggestion'][:50]}... |")
if __name__ == "__main__":
run_spec(REQUIREMENTS)
Step 5: Gate merges with severity scoring
To make this useful in a pipeline, I need an exit code. I count CRITICAL and HIGH findings. If any CRITICAL exists, the script exits with code 1 and blocks the merge. This turns the LLM into an automated reviewer that acts like a linter for natural language. I map CRITICAL to exit code 1 because that is what GitHub Actions and GitLab CI expect from a test script. You could also write the results to a JUnit XML file if your compliance team needs traceability in a dashboard.
import sys
def run_spec_with_gate(requirements: list[str]) -> int:
critical_count = 0
high_count = 0
print("| # | Finding | Severity | Reason |")
print("|---|---------|----------|--------|")
for i, req in enumerate(requirements, 1):
result = analyze_requirement(req)
print(f"| {i} | {result['finding']} | {result['severity']} | {result['reason'][:45]}... |")
if result["severity"] == "CRITICAL":
critical_count += 1
elif result["severity"] == "HIGH":
high_count += 1
print(f"\nSummary: {critical_count} critical, {high_count} high findings.")
if critical_count > 0:
print("Gate failed.")
return 1
print("Gate passed.")
return 0
if __name__ == "__main__":
sys.exit(run_spec_with_gate(REQUIREMENTS))
Run it
I run the script against the sample list. The output below is exactly what I see in my terminal. Requirement 1 fails because 500 lacks units. Requirement 2 fails because user-friendly is not verifiable. Requirement 3 is clean. Requirement 4 fails because approximately is subjective.
$ python analyze_requirements.py
| # | Finding | Severity | Reason |
|---|---------|----------|--------|
| 1 | MISSING_UNITS | CRITICAL | Numerical threshold '500' lacks u... |
| 2 | UNTESTABLE | HIGH | 'User-friendly' cannot be verifie... |
| 3 | CLEAR | LOW | Requirement is specific, measurab... |
| 4 | AMBIGUOUS | MEDIUM | 'Approximately dark blue' is subj... |
Summary: 1 critical, 1 high findings.
Gate failed.
Next steps
Two concrete moves from here. First, wire this into a GitHub Action or GitLab CI job that scans .req files on every pull request. You can store the Oxlo.ai key as a repository secret and run the analyzer against any changed requirement files. Bad requirements never reach main.
Second, for safety-critical subsystems such as avionics or structural load analysis, swap llama-3.3-70b for deepseek-v3.2 or kimi-k2.6 to get deeper chain-of-thought reasoning on complex physical constraints. Both are available on Oxlo.ai under the same flat per-request pricing, so a 5,000-character requirement costs the same as a ten-word one. That predictability is useful when you are processing full specification documents. You can explore plans and model options at https://oxlo.ai/pricing.

