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
| Parameter | Type | Default | Description |
|---|---|---|---|
llm_service | LLMService | — | LLM backend to use |
tool_registry | ToolRegistry | — | Registered tools |
db | NoSQLRunner | — | Connected database (required) |
agent_memory | MemoryService | None | None | Vector store for memory |
schema | dict | None | None | Pre-computed schema (skips introspection) |
introspect | bool | False | Run schema introspection on setup() |
max_iterations | int | 8 | Max tool calls before forcing a final answer |
max_retries | int | 2 | Retries on fixable (query-level) tool errors |
memory_top_k | int | 3 | Memory examples to inject per question |
training_top_k | int | 3 | Verified training examples to inject per question |
max_turns | int | 5 | Conversation history turns to keep |
schema_top_k | int | 3 | Collections shown in full detail per question |
schema_always_all | int | 4 | If the DB has at most this many collections, detail all of them |
enable_text_memory | bool | True | Retrieve and inject text/domain notes |
text_memory_top_k | int | 2 | Max text notes per question |
text_memory_similarity_threshold | float | 0.55 | Cosine similarity floor for text notes |
text_memory_max_chars_per_note | int | 500 | Per-note character budget in the prompt |
text_memory_max_total_chars | int | 1200 | Total character budget for text notes |
text_memory_include_unverified | bool | True | When False, skip llm/legacy notes |
auto_save_memory | bool | True | Auto-save the last successful run_mql of each turn |
middlewares | list[Middleware] | None | None | Access-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:
| Type | Fields |
|---|---|
tool_call | tool_name, tool_args |
tool_result | tool_name, success, preview |
answer | text |
done | iterations, 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
| Property | Type | Description |
|---|---|---|
llm_service | LLMService | The configured LLM |
tool_registry | ToolRegistry | The tool registry |
db | NoSQLRunner | The connected database |
agent_memory | MemoryService | None | The memory service |
conversation_length | int | Number 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.