Schema Introspection

Mango samples your collections to infer field types, indexes, and cross-collection references. The result is injected into the system prompt so the LLM generates correct queries without guessing.

How it works

When you call agent.setup() with introspect=True, Mango runs introspect_schema() on your database:

  1. Sample documents — fetches up to 100 documents per collection (mix of sequential + random via $sample)
  2. Infer fields — walks every document, records field paths, types, and presence frequency
  3. Detect indexes — reads index definitions via index_information()
  4. Detect references — heuristic: user_id → looks for users collection, movieId → looks for movies
  5. Inject per turn — the schema is serialized into the dynamic part of the prompt on every question (see below)

MongoRunner caches the introspection result for introspect_ttl_s (default 300 s), and field_types() — used for literal coercion — keeps a per-collection 50-document sample cache.

Enable introspection

agent = MangoAgent(
    ...,
    introspect=True,   # run introspect_schema() on setup()
)
agent.setup()

Pass pre-computed schema

For production deployments you may want to introspect once and reuse:

schema = db.introspect_schema()   # run once, save/cache the result

agent = MangoAgent(
    ...,
    schema=schema,     # pass directly, introspect=False (default)
)
agent.setup()

SchemaInfo

Each collection produces a SchemaInfo:

@dataclass
class SchemaInfo:
    collection_name: str
    document_count: int
    fields: list[FieldInfo]
    indexes: list[dict]
    sample_documents: list[dict]   # 5 representative docs

Each field produces a FieldInfo:

@dataclass
class FieldInfo:
    name: str
    path: str              # dotted path e.g. "address.city"
    types: list[str]       # e.g. ["string", "null"]
    frequency: float       # 0.0–1.0, how often field appears
    is_indexed: bool
    is_unique: bool
    is_reference: bool
    reference_collection: str | None
    sub_fields: list[FieldInfo] | None    # for subdocuments
    array_element_types: list[str] | None
    sample_values: list[str] | None       # distinct string values when the field looks categorical (≤ 25 distinct)

Large databases

The prompt never lists grouped collections. On every question, when the database has more than schema_always_all (default 4) collections, Mango lists all collection names and shows full field details only for the schema_top_k (default 3) collections best matching the question (keyword and stem matching against collection names and field names). With schema_always_all or fewer collections, every collection is detailed. The grouped-by-pattern view exists only in the list_collections tool output for databases with more than 50 collections.

For databases with more than 10 collections, Mango also enables schema linking enforcement: before the first run_mql call on any collection, the agent automatically runs describe_collection and prepends the result to the query response. The LLM always sees the correct field names and types at query time — without an extra round-trip and without relying on the LLM to remember to inspect the schema first.

For smaller databases (≤ 10 collections), the full schema is already in the system prompt, so enforcement is skipped.

agent = MangoAgent(..., schema_top_k=3, schema_always_all=4)

Manual schema inspection

schema = db.introspect_schema()

for collection_name, info in schema.items():
    print(f"{collection_name}: {info.document_count} docs, {len(info.fields)} fields")
    for field in info.fields:
        print(f"  {field.path} ({', '.join(field.types)}) freq={field.frequency:.0%}")