Skip to main content

Catch unclear inputs before they become bad AI outputs.

Project description

InputGuard

PyPI version

Catch unclear inputs before they become bad AI outputs.

InputGuard is a pre-flight input clarity layer. It sits between a user's input and an LLM call. It detects vague, incomplete, or unspecific inputs before they reach the AI — saving the correction cycle that wastes time and tokens when the AI guesses wrong.

Zero LLM calls. Zero external dependencies. Pure local Python.


The problem it solves

Without InputGuard

vague input → AI guesses → wrong output → correction loop → more tokens → repeat

With InputGuard

vague input → InputGuard flags what's missing → user clarifies → AI gets it right first time

A non-technical user asks for "an app". The AI invents a stack, a schema, an auth scheme. Five rounds of correction later, you're closer to what they actually wanted. InputGuard catches the gaps locally, in milliseconds, before any tokens are spent.


Install

pip install inputguard

Python 3.9+. No external dependencies.


Quick start

from inputguard import InputGuard

guard = InputGuard()
result = guard.analyze("build a REST API")

print(result.status)         # 'needs_clarification'
print(result.clarity_score)  # 50
print(result.gaps)           # ['programming language', 'api structure']
print(result.is_clear())     # False

for rec in result.recommendations:
    print(rec["gap"])
    print(rec["what_is_missing"])
    print(rec["what_to_provide"])
    print(rec["why_it_matters"])
    print()

Actual output of the recommendations loop:

programming language
You haven't told it which programming language or technology to use.
Add something like 'using Python', 'in JavaScript with React', or 'with Node.js'. If you're not sure which to use, say what device or platform you want it to run on, like 'runs in a web browser' or 'runs on my Mac'.
Without this, the AI picks a language on its own. You might get something built in a language you don't have installed or can't run on your machine.

api structure
You mentioned an API but didn't describe what it should do or how it should work.
Describe the actions it needs to support. For example: 'it needs to get a list of users, create a new user, and delete a user by their ID'. You don't need to use technical terms, just describe what it does.
Without this, the AI invents its own structure. You will spend hours correcting routes, field names, and data shapes you never asked for.

analyze() takes an optional domain argument, which defaults to "coding". Phase 1 supports "coding" only.


Modes

from inputguard import InputGuard

# Warning mode (default) — flags gaps but allows the input through
warn_guard = InputGuard(mode="warning")

# Strict mode — blocks inputs that fall below the clarity threshold
strict_guard = InputGuard(mode="strict")

result_warn   = warn_guard.analyze("build a REST API")
result_strict = strict_guard.analyze("build a REST API")

print(result_warn.status)    # 'needs_clarification'
print(result_strict.status)  # 'blocked'
print(result_warn.clarity_score == result_strict.clarity_score)  # True

The clarity score is mode-independent. Only the status threshold changes.

Status Warning mode Strict mode
ready score ≥ 85 score ≥ 85
usable_with_warnings score 60–84 never
needs_clarification score < 60 score 65–84
blocked never score < 65

Use warning when you want to surface gaps to the user without blocking. Use strict when you want to refuse to forward vague input to the LLM.


What gets checked

Phase 1 covers coding build inputs: requests like "build me an X", "integrate with Y", "add Z". Eight rules run against the normalized input. Six look for specific missing pieces. Two are safety nets that catch inputs the term sets would otherwise miss.

Rule code What it catches Severity
missing_language A build/action verb is present but no programming language or framework is named. high
missing_api_structure API terms appear (REST, GraphQL, endpoint…) but no routes, HTTP methods, or request/response shape are described. high
missing_data_model Storage terms appear (database, CRUD, schema…) but no fields, entities, or model are described. high
missing_integration_specifics A third-party service is named (Stripe, Twilio, AWS…) but no specific action or feature is described. medium
missing_auth_type Authentication is mentioned but no concrete type (JWT, OAuth, magic link…) is named. high
missing_output_format A top-level build verb is present but no output format (web app, CLI, REST API, script…) is named. Does not fire on connector verbs like "integrate" or "add". medium
intent_without_language Build intent is expressed without a creation verb ("I need…", "I want…", "looking for…", "put together…") and no language is named. high
insufficient_context Catch-all. Fires when nothing else fires, the input is at least 5 words, is not a question, and contains no specificity signal at all. high

The result object

analyze() returns an immutable AnalysisResult with these fields:

Field Type Description
status str One of "ready", "usable_with_warnings", "needs_clarification", "blocked"
clarity_score int 0 to 100
gaps List[str] Gap names, in the order rules fired
recommendations List[dict] One dict per gap (see next section)
findings List[RuleFinding] Raw rule findings (code, message, severity, gap)
interpretation_note Optional[str] Set when the input is highly ambiguous (score < 50 or two or more high-severity findings)

Helpers:

  • result.is_clear()True only when status is "ready"
  • result.to_dict() — full result as a plain JSON-serializable dict

Example result.to_dict() for guard.analyze("build a CRUD app with a database"):

{
  "status": "needs_clarification",
  "clarity_score": 35,
  "gaps": [
    "programming language",
    "data model",
    "output format"
  ],
  "recommendations": [
    {
      "gap": "programming language",
      "what_is_missing": "You haven't told it which programming language or technology to use.",
      "what_to_provide": "Add something like 'using Python', 'in JavaScript with React', or 'with Node.js'. If you're not sure which to use, say what device or platform you want it to run on, like 'runs in a web browser' or 'runs on my Mac'.",
      "why_it_matters": "Without this, the AI picks a language on its own. You might get something built in a language you don't have installed or can't run on your machine."
    },
    {
      "gap": "data model",
      "what_is_missing": "You mentioned storing data but didn't say what data or what details need to be saved.",
      "what_to_provide": "List the main things you need to store and what information matters for each. For example: 'store users with their name, email address, and password' or 'products with a title, price, and how many are in stock'.",
      "why_it_matters": "Without this, the AI guesses your entire database structure. The names, fields, and data types will be wrong and you will have to rebuild them from scratch."
    },
    {
      "gap": "output format",
      "what_is_missing": "You didn't say what kind of thing you're building or how it will be used.",
      "what_to_provide": "Add something like: 'as a web app I can open in a browser', 'as a command-line tool I run in my terminal', 'as a REST API', 'as a Python script', or 'as a mobile app'. Pick whichever matches how you plan to use it.",
      "why_it_matters": "A web app, a script, and an API that do the same job look completely different in code. Without this, the AI picks one and you might get the wrong one entirely."
    }
  ],
  "findings": [
    {"code": "missing_language",      "message": "No programming language or framework detected.",                          "severity": "high",   "gap": "programming language"},
    {"code": "missing_data_model",    "message": "Storage or database mentioned but no fields, entities, or model described.","severity": "high",   "gap": "data model"},
    {"code": "missing_output_format", "message": "Build request detected but no output or delivery format specified.",      "severity": "medium", "gap": "output format"}
  ],
  "interpretation_note": "This input is ambiguous in multiple ways. Addressing each gap below before sending will prevent the AI from making assumptions that lead to the wrong output."
}

Recommendations

Every entry in result.recommendations is a plain dict with four keys, all written for non-technical users:

for rec in result.recommendations:
    print(rec["gap"])             # which gap this addresses
    print(rec["what_is_missing"]) # plain English — what the user forgot
    print(rec["what_to_provide"]) # concrete example they can copy
    print(rec["why_it_matters"])  # what goes wrong if they skip it

The seven gap names that can appear are: programming language, api structure, data model, integration specifics, authentication type, output format, task context.


Real-world examples

from inputguard import InputGuard
guard = InputGuard()

guard.analyze("build me an app")
#   score:  60
#   status: usable_with_warnings
#   gaps:   ['programming language', 'output format']

guard.analyze("integrate with Stripe")
#   score:  60
#   status: usable_with_warnings
#   gaps:   ['programming language', 'integration specifics']

guard.analyze(
    "Build a REST API using FastAPI. "
    "Store users in PostgreSQL with fields: id, name, email. "
    "Expose GET /users and POST /users endpoints. "
    "Add JWT authentication."
)
#   score:  100
#   status: ready
#   gaps:   []

The vague input is flagged in two places. The partial input is flagged on the missing language and on the unspecified Stripe action. The specific input passes cleanly.


Integration pattern

Drop it in front of your existing LLM call. Two minimal patterns:

from inputguard import InputGuard

guard = InputGuard(mode="warning")

def handle_user_input(user_input: str):
    result = guard.analyze(user_input)

    if result.status in ("needs_clarification", "blocked"):
        # Return feedback to the user before calling the LLM
        return {
            "status": result.status,
            "gaps": result.gaps,
            "recommendations": result.recommendations,
        }

    # Input is clear enough — proceed to LLM
    return call_your_llm(user_input)

For a hard gate, use mode="strict" and check result.is_clear():

guard = InputGuard(mode="strict")

def handle_user_input(user_input: str):
    result = guard.analyze(user_input)
    if not result.is_clear():
        return {
            "status": result.status,
            "gaps": result.gaps,
            "recommendations": result.recommendations,
        }
    return call_your_llm(user_input)

Package layout

inputguard/
├── inputguard/
│   ├── __init__.py
│   ├── analyzer.py
│   ├── recommender.py
│   ├── scorer.py
│   ├── types.py
│   ├── py.typed
│   └── rules/
│       ├── __init__.py
│       └── coding.py
├── tests/
│   └── test_coding.py
├── pyproject.toml
└── README.md

Development

pip install -e ".[dev]"
python -m pytest tests/ -v

Publishing

python -m build
python -m twine check dist/*
python -m twine upload dist/*

License

MIT — see LICENSE for the full text.

Copyright © 2026 Nihanth Kalisetti.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

inputguard-0.2.0.tar.gz (23.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

inputguard-0.2.0-py3-none-any.whl (24.1 kB view details)

Uploaded Python 3

File details

Details for the file inputguard-0.2.0.tar.gz.

File metadata

  • Download URL: inputguard-0.2.0.tar.gz
  • Upload date:
  • Size: 23.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.2

File hashes

Hashes for inputguard-0.2.0.tar.gz
Algorithm Hash digest
SHA256 2f916b1d99e985d3249e0e32623d286807db4b3e596f43959c9ac1a090ece2cf
MD5 aff618c397d98ebda4fdbb0128320940
BLAKE2b-256 9c2417d45232a9e8486034986210535bbdada4a2527b77b02285507c7a346785

See more details on using hashes here.

File details

Details for the file inputguard-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: inputguard-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 24.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.2

File hashes

Hashes for inputguard-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 9c9899ebe20e76d2b236bb69bfd19d0cbe03fc6fe651654f9a0c9a55b11cdee7
MD5 b27d50974d8ee8fa0c2fa3d6e014786d
BLAKE2b-256 bab2c2869bb7bcfa0745b11cb0cdbe8cf0ec1084c34ccbf2e10905cb54738a04

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page