Tool-Calling with kit
Modern LLM runtimes (OpenAI function-calling, Anthropic tools, etc.) let you hand the model a menu of functions in JSON-Schema form. The model then plans its own calls – no hard-coded if/else trees required.
With kit
you can expose its code-intelligence primitives (search, symbol extraction, summaries, etc) and let the LLM decide which one to execute in each turn. Your app stays declarative: “Here are the tools, here is the user’s question, please help.”
In practice that means you can drop kit
into an existing chat agent with by just registering the schema and making the calls as needed. The rest of this guide shows the small amount of glue code needed and the conversational patterns that emerge.
You may also be interested in kit’s MCP integration, which can achieve similar goals.
kit
exposes its code-intelligence primitives as callable tools. Inside Python you can grab the JSON-Schema list with a single helper (kit.get_tool_schemas()
) and hand that straight to your LLM runtime. Once the schema is registered the model can decide when to call:
open_repository
search_code
extract_symbols
find_symbol_usages
get_file_tree
,get_file_content
get_code_summary
This page shows the minimal JSON you need, the decision patterns the model will follow, and a multi-turn example.
1 Register the tools
OpenAI Chat Completions
from openai import OpenAIfrom kit import get_tool_schemas
client = OpenAI()
# JSON-Schema for every kit toolfunctions = get_tool_schemas()
messages = [ {"role": "system", "content": "You are an AI software engineer, some refer to as the 'Scottie Scheffler of Programming'. Feel free to call tools when you need context."},]
functions
is a list of JSON-Schema objects. Pass it directly as the tools
/functions
parameter to client.chat.completions.create()
.
Anthropic (messages-v2)
from anthropic import Anthropicanthropic = Anthropic()
# JSON-Schema for every kit toolfunctions = get_tool_schemas()
response = anthropic.messages.create( model="claude-3-7-sonnet-20250219", system="You are an AI software engineer…", tools=functions, messages=[{"role": "user", "content": "I got a test failure around FooBar. Help me."}],)
2 When should the model call which tool?
Below is the heuristic kit’s own prompts (and our internal dataset) encourage. You don’t need to hard-code this logic—the LLM will pick it up from the tool names / descriptions—but understanding the flow helps you craft better conversation instructions.
Situation | Suggested tool(s) |
---|---|
No repo open yet | open_repository (first turn) |
“What files mention X?” | search_code (fast regex) |
“Show me the function/class definition” | get_file_content or extract_symbols |
”Where else is my_func used?“ | 1) extract_symbols (file-level) → 2) find_symbol_usages |
”Summarize this file/function for the PR description” | get_code_summary |
IDE-like navigation | get_file_tree + get_file_content |
A typical multi-turn session:
User: I keep getting KeyError("user_id") in prod.
Assistant (tool call): search_code {"repo_id": "42", "query": "KeyError(\"user_id\")"}
Tool result → 3 hits returned (files + line numbers)
Assistant: The error originates in `auth/session.py` line 88. Shall I show you that code?
User: yes, show me.
Assistant (tool call): get_file_content {"repo_id": "42", "file_path": "auth/session.py"}
Tool result → file text
Assistant: Here is the snippet … (explanatory text)
3 Prompt orchestration: system / developer messages
Tool-calling conversations have three channels of intent:
- System prompt – your immutable instructions (e.g. “You are an AI software-engineer agent.”)
- Developer prompt – app-level steering: “If the user asks for code you have not seen, call
get_file_content
first.” - User prompt – the human’s actual message.
kit
does not impose a format—you simply include the JSON-schema from kit.get_tool_schemas()
in your tools
/ functions
field and add whatever system/developer guidance you choose.
A common pattern is:
system = """You are an AI software-engineer.Feel free to call tools when they help you answer precisely.When showing code, prefer the smallest snippet that answers the question."""
developer = """Available repos are already open in the session.Call `search_code` before you attempt to answer questions like *"Where is X defined?"* or *"Show references to Y"*.Use `get_code_summary` before writing long explanations of unfamiliar files."""
messages = [ {"role": "system", "content": system}, {"role": "system", "name": "dev-instructions", "content": developer}, {"role": "user", "content": user_query},]
Because the developer message is separate from the user’s content it can be updated dynamically by your app (e.g. after each tool result) without contaminating the visible chat transcript.
4 Streaming multi-tool conversations
Nothing prevents the LLM from chaining calls:
extract_symbols
on the failing file.- Pick the function, then
get_code_summary
for a concise explanation.
Frameworks like LangChain, LlamaIndex or CrewAI can route these calls automatically when you surface kit’s tool schema as their “tool” object.
from langchain_community.tools import Toolkit_tools = [Tool.from_mcp_schema(t, call_kit_server) for t in functions]
5 Security considerations
get_file_content
streams raw code. If you expose kit to an external service:
- Restrict
open_repository
to a safe path. - Consider stripping secrets from returned text.
- Disable
get_file_content
for un-trusted queries and rely onextract_symbols
+get_code_summary
instead.
6 In-process (no extra server)
If your application is already written in Python you don’t have to spawn any servers at all—just keep a Repository
instance in memory and expose thin wrappers as tools/functions:
from typing import TypedDictfrom kit import Repository
repo = Repository("/path/to/repo")
class SearchArgs(TypedDict): query: str pattern: str
def search_code(args: SearchArgs): return repo.search_text(args["query"], file_pattern=args.get("pattern", "*.py"))
Then register the wrapper with your tool-calling framework:
from openai import OpenAIclient = OpenAI()
functions = [ { "name": "search_code", "description": "Regex search across the active repository", "parameters": { "type": "object", "properties": { "query": {"type": "string"}, "pattern": {"type": "string", "default": "*.py"}, }, "required": ["query"], }, },]
client.chat.completions.create( model="gpt-4o", tools=functions, messages=[...])
Because everything stays in the same process you avoid IPC/JSON overhead and can share caches (e.g. the RepoMapper
symbol index) across calls.
Tip: if you later need multi-language tooling or a separate sandbox you can still swap in the MCP server without touching your prompt logic—the function schema stays the same.