Skip to content

Tool Prompt Architecture

Agent tools in Loquent separate what a tool does (sent to the AI provider as the tool definition) from how the agent should use it (appended to the system prompt as behavioral instructions). This keeps tool descriptions concise for the provider while giving the agent detailed rules in the prompt.

Each tool builder returns a tuple:

fn build_tool() -> (AgentToolDefinition, String)
  • AgentToolDefinition — concise, factual description sent to the AI provider
  • String — markdown section with behavioral rules appended to the system prompt

Conditional tools return Option<(AgentToolDefinition, String)> and yield None when prerequisites aren’t met (no knowledge bases linked, no transfer numbers configured, etc.).

File: src/mods/agent/services/build_end_call_tool_definition_service.rs

pub fn build_end_call_tool_definition() -> (AgentToolDefinition, String)
  • Description: “Ends the current phone call.”
  • Parameters: None
  • Prompt section:
### Ending the Call
- Say goodbye and IMMEDIATELY call `end_call`.
- Do NOT wait for the caller to respond after your farewell.
- Do NOT announce that you are ending the call.

File: src/mods/agent/services/build_client_lookup_tool_definition_service.rs

pub async fn build_client_lookup_tool_definition(
organization_id: Uuid,
) -> Result<Option<(AgentToolDefinition, String)>, AppError>

Returns None when the organization has no client_lookup_url configured.

  • Description: “Retrieves the caller’s client record from the database.”
  • Parameters: None (caller phone number is automatic)
  • Prompt section: Instructs the agent to call lookup_caller proactively at conversation start and personalize the greeting with the returned data.

File: src/mods/agent/services/build_knowledge_tool_definition_service.rs

pub async fn build_knowledge_tool_definition(
agent_id: String,
) -> Result<Option<(AgentToolDefinition, String)>, AppError>

Returns None when the agent has no linked knowledge bases.

  • Description: Includes a dynamic list of available knowledge bases and their documents
  • Parameters: query (string) — natural language search query
  • Prompt section: Instructs the agent to always search knowledge bases before answering covered topics and never guess.

File: src/mods/agent/services/build_transfer_call_tool_definition_service.rs

pub fn build_transfer_call_tool_definition(
settings: Option<TransferCallSettings>,
) -> Option<(AgentToolDefinition, String)>

Returns None when settings is missing or has no numbers. The tool is never registered without at least one configured number.

  • Description: “Transfers the current call to another phone number (cold transfer).”
  • Parameters: phone_number (string) — restricted via enum to configured numbers only
  • Prompt section: Lists available destinations by label and instructs the agent to confirm before transferring.

collect_agent_tools in src/mods/agent/services/collect_agent_tools_service.rs orchestrates all builders:

pub async fn collect_agent_tools(
agent_id: String,
organization_id: Uuid,
tool_config: &AgentToolConfig,
) -> Result<(Vec<AgentToolDefinition>, String), AppError>

It collects all enabled tools and concatenates their prompt sections under a ## Available Tools heading:

## Available Tools
### Knowledge Base
- ALWAYS call `query_knowledge` before answering covered topics.
- Do NOT guess — use the tool.
### Call Transfer (CONFIRMATION_FIRST)
- Transfer destinations:
- +12125551234 — Sales
- +12125559876 — Support
- ALWAYS confirm with the caller before transferring.
### Ending the Call
- Say goodbye and IMMEDIATELY call `end_call`.

Both the OpenAI and Gemini session config builders append this addendum to the agent’s base prompt:

let (agent_tools, prompt_addendum) =
collect_agent_tools(agent_id, organization_id, &tool_config).await?;
let instructions = format!("{base_prompt}\n\n{prompt_addendum}");

The transfer_call tool enforces that at least one phone number is configured at three levels:

LevelBehavior
Backendbuild_transfer_call_tool_definition returns None without valid numbers — tool never registers
ValidationAgentToolConfig::validation_error() returns an error message when transfer is enabled but no numbers exist
UISave button is disabled and inline error text appears below the transfer numbers section
// AgentToolConfig::validation_error()
pub fn validation_error(&self) -> Option<&'static str> {
if self.is_tool_enabled("transfer_call") {
let has_valid_number = self
.get_tool_settings::<TransferCallSettings>("transfer_call")
.is_some_and(|s| s.numbers.iter().any(|n| !n.number.trim().is_empty()));
if !has_valid_number {
return Some("Transfer Call requires at least one phone number.");
}
}
None
}
  1. Create a builder in src/mods/agent/services/ returning (AgentToolDefinition, String) or Option<(...)>
  2. Write a concise, factual description for the tool definition
  3. Write a markdown ### Section with behavioral rules for the prompt
  4. Register the builder in collect_agent_tools — gate it with tool_config.is_tool_enabled("your_tool")
  5. Add the tool name to the UI toggle list in tool_config_form_component.rs
FilePurpose
src/mods/agent/services/collect_agent_tools_service.rsOrchestrates builders, assembles prompt addendum
src/mods/agent/services/build_*_tool_definition_service.rsIndividual tool builders
src/mods/agent/types/agent_tool_config_type.rsAgentToolConfig with validation_error()
src/mods/agent/types/transfer_call_settings_type.rsTransferCallSettings, TransferNumber
src/mods/agent/components/tool_config_form_component.rsUI for tool toggles and transfer number management