2.1 Function Calling & Tool Selection
Key Takeaways
- Function calling enables LLMs to interface deterministically with external APIs, system tools, and databases by emitting structured JSON requests.
- Clear JSON schemas with detailed field descriptions, explicit types, and enum restrictions drastically reduce model hallucination and improve tool invocation accuracy.
- System prompt injection and context window management ensure only relevant tool definitions are exposed, optimizing token budgets and response latency.
- Tool selection control modes (auto, required, none, or explicit function selection) govern whether models autonomously choose tools or execute mandatory steps.
- Multi-tool execution patterns support sequential dependency chaining and parallel function calls using unique tool_call_id identifiers.
2.1 Function Calling & Tool Selection
Modern artificial intelligence developer platforms, such as GitHub Copilot and autonomous software engineering agents, have evolved far beyond basic text generation and code completion. At the core of agentic functionality is function calling (also known as tool selection or tool calling). Function calling enables Large Language Models (LLMs) to interface deterministically with external software systems, enterprise databases, version control platforms, build tools, and custom web services. Rather than attempting to guess internal data or manipulate environments directly through unstructured text, an LLM equipped with function calling can emit a structured JSON payload representing a intent to execute a specific function with precise arguments.
Understanding the mechanics of function calling, mastering JSON Schema design, managing context window overhead, and controlling tool selection behavior are essential competencies for developers configuring AI assistants and preparing for the GH-600 certification.
The Function Calling Execution Loop
Function calling operates through a multi-turn, multi-party request-response architecture involving three primary entities: the End User, the Host Application / AI Client (such as GitHub Copilot CLI, an IDE extension, or an agent orchestrator), and the LLM. In setups involving external execution, an External Tool Server or API execution engine is also involved.
The standard function calling lifecycle proceeds through six distinct stages:
- Tool Definition Injection: The host application initializes a request to the LLM. Along with the system prompt and conversation history, the application sends an array of available tool definitions formatted according to the JSON Schema standard.
- User Request Processing: The end user submits a prompt (for example, "Check the open pull requests assigned to me on repository octocat/hello-world and summarize the build failures").
- Model Evaluation & Tool Call Generation: The LLM evaluates the prompt against the available tool schemas. Recognizing that it lacks real-time access to GitHub's database, the model halts text generation and returns a structured response containing a
tool_callsarray instead of standard message text. This response specifies the function name to invoke (e.g.,list_pull_requests) and a JSON-encoded string of arguments (e.g.,{"repo": "octocat/hello-world", "assignee": "current_user", "state": "open"}). - Host Application Interception: The host application receives the model's tool call request, validates the JSON arguments against the function's schema, and executes the underlying local function or remote API call.
- Tool Result Feedback: The host application captures the output of the function execution (whether success data, empty results, or error tracebacks) and appends it to the conversation history as a new message object with the role set to
"tool"(or"function"), linking it directly to the original request using a uniquetool_call_id. - Final Synthesis: The host application resends the updated conversation history back to the LLM. The LLM processes the tool result and either generates a final natural-language answer to the user or emits subsequent tool calls if multi-step execution is required.
{
"role": "assistant",
"content": null,
"tool_calls": [
{
"id": "call_abc123xyz",
"type": "function",
"function": {
"name": "search_github_issues",
"arguments": "{\"query\": \"is:open is:pr author:app-bot\", \"limit\": 5}"
}
}
]
}
JSON Schema Design & Prompt Engineering for Tools
The quality, accuracy, and reliability of tool selection depend heavily on the clarity of the JSON Schema definitions provided to the LLM. Models rely entirely on the semantic descriptions and type constraints in the schema to determine when to invoke a tool and how to construct valid parameter payloads.
Core Elements of a Tool Schema
A tool definition consists of a top-level tool type (typically "function") and a function object containing:
name: A concise, unambiguous identifier using snake_case or camelCase (e.g.,create_git_branch).description: A clear markdown-formatted summary explaining what the tool does, when it should be used, and any critical constraints or prerequisites.parameters: A JSON Schema object (typically adhering to Draft 7 or OpenAPI 3.0 specs) detailing the parameters expected by the tool.
{
"type": "function",
"function": {
"name": "trigger_ci_workflow",
"description": "Triggers a GitHub Actions workflow run for a specified repository and ref. Use this tool only when the user explicitly requests to start a build or test run.",
"parameters": {
"type": "object",
"properties": {
"owner": {
"type": "string",
"description": "The GitHub organization or account owner of the repository."
},
"repo": {
"type": "string",
"description": "The repository name without the owner prefix."
},
"workflow_id": {
"type": "string",
"description": "The filename (e.g., 'main.yml') or integer ID of the workflow."
},
"ref": {
"type": "string",
"description": "The git branch or tag name to run the workflow on."
},
"environment": {
"type": "string",
"enum": ["development", "staging", "production"],
"description": "Target environment deployment tier."
}
},
"required": ["owner", "repo", "workflow_id", "ref"]
}
}
}
Schema Best Practices to Eliminate Hallucinations
- Use Explicit Enum Values: When a parameter only accepts fixed values (such as log severity levels or environment tiers), explicitly declare an
enumarray. This prevents the LLM from inventing invalid string formats. - Detailed Parameter Descriptions: Never leave parameter descriptions blank. Explain expected date formats (e.g.,
YYYY-MM-DD), units of measurement, or string formatting rules. - Strict Parameter Requirements: Define the
requiredarray accurately. If a parameter is mandatory for the underlying API to succeed, ensure it is present inrequiredso the model generates it or asks the user for clarification before calling the function. - Avoid Overly Complex Nested Objects: While JSON Schema supports deep nesting, flat schemas with descriptive parameter names yield significantly higher model accuracy and fewer JSON parsing failures.
Tool Selection Protocols and Control Modes
Developer platforms allow engineers to control model tool choice behavior using the tool_choice directive. Controlling tool choice is crucial for enforcing rigid developer workflows or allowing autonomous agent exploration.
| Tool Choice Mode | Configuration Syntax | Model Behavior | Best Used For |
|---|---|---|---|
| Auto (Default) | "auto" | The model dynamically decides whether to respond with text or invoke one or more tools based on prompt intent. | General-purpose developer chat and exploratory coding assistants. |
| None | "none" | The model is prohibited from invoking tools and must respond using only conversational text. | Pure code explanations, documentation summarization, or safe modes. |
| Required / Any | "required" (or "any") | The model is forced to invoke at least one available tool from the schema list. | Agentic workflows where step execution is mandatory before returning control. |
| Forced Function | {"type": "function", "function": {"name": "<name>"}} | The model is explicitly forced to call the specified function name. | Fixed multi-step state machines, automated triage pipelines, and structured extraction. |
Context Window Optimization & Dynamic Tool Filtering
Each tool definition injected into a request consumes tokens within the LLM's context window. In complex enterprise systems featuring hundreds of microservice endpoints or dozens of developer integrations, injecting every available tool schema into every API call causes severe performance bottlenecks:
- Context Bloat: Including 50 comprehensive tool schemas can consume 4,000 to 10,000 tokens of input context on every turn, increasing latency and API costs.
- Model Confusion: Exposing too many overlapping tools degrades model reasoning, leading to tool misselection or parameter conflation.
Strategies for Dynamic Tool Filtering
- Intent Routing: Employ a lightweight classifier model or vector search over tool descriptions to select a relevant subset of 3 to 7 tools matching the user's specific request domain (e.g., GitHub Issues tools vs. Cloud Deployment tools).
- State-Based Scoping: In multi-turn agent workflows, restrict available tools based on the current state of the task (e.g., expose search tools during the discovery phase, but switch to editing tools during the code modification phase).
- Response Truncation: Tool response outputs (such as massive git diffs or compiler output logs) must be truncated, summarized, or stored as file references before appending them to the conversation history to prevent context window saturation.
Parallel and Sequential Tool Execution
Modern frontier models support parallel tool calling, enabling the model to issue multiple function call requests within a single generation turn. For example, if a developer asks to compare three pull requests, the model can generate three distinct tool_calls array items simultaneously.
The host application must process all parallel tool requests (often executing them concurrently via asynchronous I/O) and return a corresponding array of tool response messages. Each tool response message must include the exact matching tool_call_id to maintain thread integrity.
In sequential tool chaining, the output of one tool call (e.g., retrieving a repository file tree) provides the necessary inputs for a subsequent tool call (e.g., reading a specific file path). The host application acts as the coordinator, feeding intermediate tool outputs back to the model until the goal is achieved.
In OpenAI and GitHub Copilot tool calling specifications, which configuration syntax forces the model to execute a specific function named 'query_github_issues'?
How does an AI host application submit the result of an executed tool call back to the model in a multi-turn conversation loop?
When designing a JSON Schema for a custom developer tool, which technique is most effective in preventing model hallucinations regarding allowed parameter values?
Which architecture pattern best prevents context window exhaustion when managing dozens of custom developer tools within an AI assistant?