Skip to content

Agent Tools

The agent uses a set of tools during its agentic loop to introspect the database, search knowledge, execute SQL, and persist learnings. Each tool is registered with the LLM as a callable function with a JSON Schema describing its parameters.

This page documents every tool, its parameters, the JSON sent to the LLM, and what the tool returns.

Tools are serialized into the format expected by each LLM provider. The package handles this automatically via ToolFormatter.

{
"type": "function",
"function": {
"name": "tool_name",
"description": "What the tool does.",
"parameters": {
"type": "object",
"properties": { ... },
"required": [ ... ]
}
}
}
{
"name": "tool_name",
"description": "What the tool does.",
"input_schema": {
"type": "object",
"properties": { ... },
"required": [ ... ]
}
}

The parameters (OpenAI) and input_schema (Anthropic) objects are identical — only the wrapper differs.


Execute a SQL query against the database. This is the primary tool the agent uses to answer questions.

Description sent to LLM:

Execute a SQL query against the database. Only SELECT and WITH statements are allowed. Returns query results as JSON.

{
"type": "object",
"properties": {
"sql": {
"type": "string",
"description": "The SQL query to execute. Must be a SELECT or WITH statement."
}
},
"required": ["sql"]
}
ParameterTypeRequiredDescription
sqlstringYesThe SQL query to execute. Must start with SELECT or WITH.
{
"rows": [
{"id": 1, "name": "Alice"},
{"id": 2, "name": "Bob"}
],
"row_count": 2,
"total_rows": 2,
"truncated": false
}
FieldTypeDescription
rowsarrayThe query result rows as objects.
row_countintegerNumber of rows returned (after truncation).
total_rowsintegerTotal rows the query produced before truncation.
truncatedbooleanWhether results were truncated to the configured sql.max_rows limit.
  • Only SELECT and WITH statements are allowed (configurable via sql.allowed_statements).
  • Forbidden keywords (DROP, DELETE, UPDATE, INSERT, ALTER, CREATE, TRUNCATE, etc.) are rejected even inside subqueries.
  • Multiple statements separated by ; are blocked.
  • Results are capped at sql.max_rows (default: 1000).
  • On error, a SqlErrorOccurred event is dispatched for auto-learning.
{
"sql": "SELECT COUNT(*) as total FROM orders WHERE status = 'delivered'"
}

Inspect the database schema. The agent uses this to discover tables, columns, types, foreign keys, and sample data before writing SQL.

Description sent to LLM:

Get detailed schema information about database tables. Can inspect a specific table or list all available tables.

{
"type": "object",
"properties": {
"table_name": {
"type": "string",
"description": "Optional: The name of a specific table to inspect. If not provided, lists all tables."
},
"include_sample_data": {
"type": "boolean",
"description": "Whether to include sample data from the table (up to 3 rows). This data is for understanding the schema only - never use it directly in responses to the user.",
"default": false
}
}
}
ParameterTypeRequiredDefaultDescription
table_namestringNoA specific table to inspect. Omit to list all tables.
include_sample_databooleanNofalseInclude up to 3 sample rows for schema understanding.

When listing all tables (no table_name provided):

{
"tables": ["users", "orders", "products"],
"count": 3
}

When inspecting a specific table:

{
"table": "orders",
"description": "Table comment if set in the database",
"columns": [
{
"name": "id",
"type": "bigint",
"nullable": false,
"primary_key": true,
"foreign_key": false,
"references": null,
"default": null,
"description": null
},
{
"name": "customer_id",
"type": "bigint",
"nullable": false,
"primary_key": false,
"foreign_key": true,
"references": "customers.id",
"default": null,
"description": null
}
],
"relationships": [
{
"type": "belongsTo",
"related_table": "customers",
"foreign_key": "customer_id",
"local_key": "id"
}
],
"sample_data": [
{"id": 1, "customer_id": 42, "status": "delivered", "total_amount": 9999}
]
}
FieldTypeDescription
tablestringTable name.
descriptionstring|nullTable comment from the database, if set.
columnsarrayColumn details including type, nullability, keys, and defaults.
relationshipsarrayForeign key relationships detected from the schema.
sample_dataarrayUp to 3 sample rows (only when include_sample_data is true).
{
"table_name": "orders",
"include_sample_data": true
}

Search the knowledge base for relevant query patterns and learnings. The agent calls this before writing SQL to find proven patterns and avoid known pitfalls.

Description sent to LLM:

Search the knowledge base for relevant query patterns and learnings. Use this to find similar queries, understand business logic, or discover past learnings about the database.

{
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "The search query to find relevant knowledge."
},
"type": {
"type": "string",
"description": "Filter results by index: 'all' (default) searches query patterns, learnings and any custom indexes. Pass 'table_metadata' to look up the schema of a table that is not described in the context.",
"enum": ["all", "query_patterns", "learnings", "table_metadata"]
},
"limit": {
"type": "integer",
"description": "Maximum number of results to return.",
"minimum": 1,
"maximum": 20
}
},
"required": ["query"]
}
ParameterTypeRequiredDefaultDescription
querystringYesThe search query text.
typestringNoallFilter by index name, or all. The enum also lists any custom indexes you register.
limitintegerNo5Max results to return (1–20).
{
"query_patterns": [
{
"name": "monthly_revenue",
"question": "Calculate total revenue by month",
"sql": "SELECT DATE_FORMAT(created_at, '%Y-%m') as month, SUM(total_amount) / 100 as revenue FROM orders WHERE status != 'cancelled' GROUP BY month ORDER BY month DESC",
"summary": "Monthly revenue from non-cancelled orders",
"tables_used": ["orders"],
"relevance_score": 8.5
}
],
"learnings": [
{
"title": "orders.total_amount is in cents",
"description": "Always divide total_amount by 100 when displaying dollar amounts.",
"category": "data_quality",
"sql": null,
"relevance_score": 7.2
}
],
"total_found": 2
}
FieldTypeDescription
query_patternsarrayMatching query patterns (from knowledge files and saved validated queries).
learningsarrayMatching learnings (agent-discovered patterns). Only included when learning is enabled.
table_metadataarrayMatching table schemas. Only included when table_metadata is requested explicitly.
total_foundintegerTotal number of results across the searched indexes.

The table_metadata index is left out of an all search: table schemas are large, and the relevant ones are already in the context. Requesting it explicitly is the escape hatch when schema retrieval left out a table the agent needs.

{
"query": "monthly revenue calculation",
"type": "query_patterns",
"limit": 5
}

Save a new learning to the knowledge base. The agent uses this when it discovers something important — typically after recovering from a SQL error or when a user provides a correction.

Description sent to LLM:

Save a new learning to the knowledge base. Use this when you discover something important about the database schema, business logic, or query patterns that would be useful for future queries.

{
"type": "object",
"properties": {
"title": {
"type": "string",
"description": "A short, descriptive title for the learning (max 100 characters)."
},
"description": {
"type": "string",
"description": "A detailed description of what was learned and why it matters."
},
"category": {
"type": "string",
"description": "The category of this learning.",
"enum": ["type_error", "schema_fix", "query_pattern", "data_quality", "business_logic"]
},
"sql": {
"type": "string",
"description": "Optional: The SQL query related to this learning."
}
},
"required": ["title", "description", "category"]
}
ParameterTypeRequiredDescription
titlestringYesShort title, max 100 characters.
descriptionstringYesDetailed description of what was learned.
categorystringYesOne of type_error, schema_fix, query_pattern, data_quality, business_logic.
sqlstringNoRelated SQL query.
CategoryDescription
type_errorA correction for a data type mismatch or casting issue.
schema_fixA correction for incorrect schema assumptions.
query_patternA learned pattern for constructing queries.
data_qualityAn observation about data quality or anomalies.
business_logicA learned business rule or domain knowledge.
{
"success": true,
"message": "Learning saved successfully.",
"learning_id": 12,
"title": "users.status is VARCHAR not INT",
"category": "type_error"
}
{
"title": "orders.total_amount is in cents",
"description": "The total_amount column stores values in cents, not dollars. Always divide by 100 when displaying monetary values.",
"category": "data_quality",
"sql": "SELECT total_amount / 100 as amount_dollars FROM orders"
}

Save a validated query pattern after successfully answering a question. This builds the knowledge base organically — future similar questions can reference proven SQL.

Description sent to LLM:

Save a validated query pattern to the knowledge base. Use this when you have successfully executed a SQL query that correctly answers a user question. This helps future queries by providing proven patterns.

{
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "A short, descriptive name for the query pattern (max 100 characters)."
},
"question": {
"type": "string",
"description": "The natural language question this query answers."
},
"sql": {
"type": "string",
"description": "The validated SQL query that correctly answers the question."
},
"summary": {
"type": "string",
"description": "A brief summary of what the query does and what data it returns."
},
"tables_used": {
"type": "array",
"description": "List of table names used in the query.",
"items": {
"type": "string"
}
},
"data_quality_notes": {
"type": "string",
"description": "Optional: Notes about data quality issues, edge cases, or important considerations for this query."
}
},
"required": ["name", "question", "sql", "summary", "tables_used"]
}
ParameterTypeRequiredDescription
namestringYesShort name for the pattern, max 100 characters.
questionstringYesThe natural language question this query answers.
sqlstringYesThe validated SQL (must start with SELECT or WITH).
summarystringYesBrief summary of what the query returns.
tables_usedarray of stringsYesTables referenced in the query.
data_quality_notesstringNoNotes about edge cases or data quality considerations.
{
"success": true,
"message": "Query pattern saved successfully.",
"pattern_id": 7,
"name": "monthly_active_users",
"tables_used": ["users", "logins"]
}

If a query pattern with the same question already exists, the tool returns an error instead of creating a duplicate. This prevents the knowledge base from accumulating redundant patterns.

{
"name": "monthly_active_users",
"question": "How many active users were there last month?",
"sql": "SELECT COUNT(DISTINCT user_id) as active_users FROM logins WHERE created_at >= DATE_SUB(CURDATE(), INTERVAL 1 MONTH)",
"summary": "Count of unique users who logged in during the last calendar month",
"tables_used": ["logins"],
"data_quality_notes": "Only counts users with at least one login event"
}

Ask the user a clarifying question when their request is ambiguous. The agent pauses and presents a question card in the web UI. The card can include clickable suggestion buttons with optional descriptions, supports multi-select mode, and always includes a free-text input so the user can type a custom answer. Once the user responds, the agent continues with that answer in context.

Description sent to LLM:

Ask the user a clarifying question when their request is ambiguous. Use this when you need more information before proceeding. You may provide suggested options with optional descriptions. Set multiple=true to let the user pick more than one option. The user can always type a custom free-text response instead of picking a suggestion.

{
"type": "object",
"properties": {
"question": {
"type": "string",
"description": "The clarifying question to ask the user."
},
"suggestions": {
"type": "array",
"items": {
"type": "object",
"properties": {
"label": {
"type": "string",
"description": "The short label for this suggestion (displayed on the button)."
},
"description": {
"type": "string",
"description": "Optional longer description explaining this option."
}
},
"required": ["label"]
},
"description": "Optional list of suggested answers. Each has a label and optional description."
},
"multiple": {
"type": "boolean",
"description": "Set to true to allow the user to select multiple suggestions. Defaults to false."
}
},
"required": ["question"]
}
ParameterTypeRequiredDefaultDescription
questionstringYesThe clarifying question to display.
suggestionsobject[]No[]Suggested answers, each with a label (string, required) and description (string, optional).
multiplebooleanNofalseWhen true, the user can select multiple suggestions before submitting.

The tool returns a plain string to the LLM:

  • "User answered: <answer>" — when the user clicks a suggestion, submits a multi-selection, or types a custom answer.
  • A fallback message when the user doesn’t respond in time or the connection is lost.

For multi-select, the answer is a comma-separated list of selected labels (e.g., "User answered: Revenue trends, Customer segments").

  1. The LLM calls ask_user with a question, optional suggestions, and an optional multiple flag.
  2. An ask_user SSE event is sent to the frontend with the question, suggestions, multiple flag, and a unique request ID.
  3. The frontend renders a question card:
    • Single-select (default): clicking a suggestion immediately submits it.
    • Multi-select (multiple: true): suggestions have checkboxes that toggle on/off. A “Submit selection” button sends all selected labels.
    • Suggestions with a description show the description text below the label.
    • A free-text input is always available at the bottom.
  4. The user’s answer POSTs to /ask-user-reply and writes to the cache.
  5. The tool polls the cache, picks up the answer, and returns it to the LLM.
  6. The LLM continues with the user’s answer in context.

The timeout for waiting on a user reply is configurable:

'agent' => [
'ask_user_timeout' => env('SQL_AGENT_ASK_USER_TIMEOUT', 300), // seconds
],

Simple single-select:

{
"question": "Which time period are you interested in?",
"suggestions": [
{ "label": "Last 7 days" },
{ "label": "Last 30 days" },
{ "label": "Last quarter" },
{ "label": "All time" }
]
}

With descriptions:

{
"question": "What kind of analysis would you like?",
"suggestions": [
{ "label": "Revenue trends", "description": "Monthly revenue breakdown with growth rates" },
{ "label": "Customer segments", "description": "RFM analysis grouping customers by behavior" },
{ "label": "Product performance", "description": "Sales volume and margin by product category" }
]
}

Multi-select:

{
"question": "Which metrics should I include in the report?",
"suggestions": [
{ "label": "Total revenue", "description": "Sum of all completed orders" },
{ "label": "Order count", "description": "Number of orders placed" },
{ "label": "Average order value" },
{ "label": "Customer count" }
],
"multiple": true
}

Not all tools are available in every configuration:

ToolAlways AvailableCondition
run_sqlYes
introspect_schemaYes
search_knowledgeYes
save_learningNoRequires sql-agent.learning.enabled = true
save_validated_queryNoRequires sql-agent.learning.enabled = true
ask_userYesRemove from agent.tools to disable

All tools are registered via the agent.tools config array. Remove any entry to disable that tool. When learning is disabled (SQL_AGENT_LEARNING_ENABLED=false), the save_learning and save_validated_query tools are automatically skipped even if present in the array.

You can also register your own tools by adding class names to the agent.tools array. See the Custom Tools guide for details.