MangoAgent

MangoAgent is the central class that orchestrates the LLM ↔ tool loop. Every question goes through it.

Constructor

from mango import MangoAgent

agent = MangoAgent(
    llm_service=llm,         # required
    tool_registry=tools,     # required
    db=db,                   # required
    agent_memory=memory,     # optional
    schema=None,             # optional — pre-introspected schema
    introspect=False,        # whether setup() runs introspection
    max_iterations=8,        # safety cap on tool calls per question
    max_retries=2,           # retries on fixable tool errors
    memory_top_k=3,          # how many memory examples to inject
    training_top_k=3,        # how many verified training examples to inject
    max_turns=5,             # conversation turns to keep in history
    schema_top_k=3,          # collections detailed per question
    schema_always_all=4,     # detail every collection when the DB has ≤ N
    middlewares=None,        # access-control middlewares
)

Parameters

ParameterTypeDefaultDescription
llm_serviceLLMService—LLM backend to use
tool_registryToolRegistry—Registered tools
dbNoSQLRunner—Connected database (required)
agent_memoryMemoryService | NoneNoneVector store for memory
schemadict | NoneNonePre-computed schema (skips introspection)
introspectboolFalseRun schema introspection on setup()
max_iterationsint8Max tool calls before forcing a final answer
max_retriesint2Retries on fixable (query-level) tool errors
memory_top_kint3Memory examples to inject per question
training_top_kint3Verified training examples to inject per question
max_turnsint5Conversation history turns to keep
schema_top_kint3Collections shown in full detail per question
schema_always_allint4If the DB has at most this many collections, detail all of them
enable_text_memoryboolTrueRetrieve and inject text/domain notes
text_memory_top_kint2Max text notes per question
text_memory_similarity_thresholdfloat0.55Cosine similarity floor for text notes
text_memory_max_chars_per_noteint500Per-note character budget in the prompt
text_memory_max_total_charsint1200Total character budget for text notes
text_memory_include_unverifiedboolTrueWhen False, skip llm/legacy notes
auto_save_memoryboolTrueAuto-save the last successful run_mql of each turn
middlewareslist[Middleware] | NoneNoneAccess-control middlewares — see Access Control

Methods

setup()

Initializes the agent: runs schema introspection (if introspect=True) and builds the system prompt. Call once after connecting the database, before the first ask().

agent.setup()

With introspect=False and no schema, setup() only builds the static system prompt (no database calls); the LLM then discovers collections through the tools.

ask(question, on_tool_call=None, *, user=None)

Ask a question and get a complete answer. user is an opaque object describing the caller (Mango never inspects it) that is handed to the access-control middlewares; ask() raises AccessDenied when a before_turn middleware blocks the turn.

response = await agent.ask("How many orders were placed last week?", user=current_user)
print(response.answer)

Optional on_tool_call callback receives (tool_name, tool_args, result_text) after each tool execution:

def log_tool(name, args, result):
    print(f"Tool: {name} | Args: {args}")

response = await agent.ask("...", on_tool_call=log_tool)

ask_stream(question, *, user=None)

Same as ask() but streams events as they happen via an async generator. Useful for real-time UIs. Takes the same keyword-only user and may raise AccessDenied as well.

async for event in agent.ask_stream("What are the top 10 products by revenue?"):
    if event["type"] == "tool_call":
        print(f"Calling {event['tool_name']}...")
    elif event["type"] == "answer":
        print(event["text"])

Event types:

TypeFields
tool_calltool_name, tool_args
tool_resulttool_name, success, preview
answertext
doneiterations, input_tokens, output_tokens, memory_hits, tool_calls_made, retries_made

new_session()

Returns a new agent with the same configuration but a fresh conversation history. Schema and system prompt are shared — no re-introspection.

session = agent.new_session()
response = await session.ask("...")

reset_conversation()

Clears the conversation history in place.

agent.reset_conversation()

Access control: use(), hook decorators, check_tool_access()

agent.use(middleware) registers a Middleware (runs in registration order). The decorators @agent.before_turn, @agent.filter_tools, @agent.before_tool, @agent.before_query, @agent.after_query, @agent.after_tool, @agent.before_answer and @agent.after_turn register single-hook middlewares from plain functions. agent.check_tool_access(user, tool_name, args=None) runs the before_tool hooks outside a turn (raising AccessDenied) so the host can gate non-agent actions such as the memory REST endpoints with the same policy. See Access Control.

Properties

PropertyTypeDescription
llm_serviceLLMServiceThe configured LLM
tool_registryToolRegistryThe tool registry
dbNoSQLRunnerThe connected database
agent_memoryMemoryService | NoneThe memory service
conversation_lengthintNumber of messages in history

Multi-turn conversations

Conversation history is preserved across ask() calls. Follow-up questions work naturally:

r1 = await agent.ask("How many orders were placed last week?")
r2 = await agent.ask("And how many of those were delivered?")
r3 = await agent.ask("Which customer placed the most?")

History is automatically pruned to max_turns complete turns to keep token usage stable.