Minimal AI agent with tool calling and zero dependencies in 60 lines of Python
importjsonimporturllib.requestfromsubprocessimportcheck_output, STDOUT, CalledProcessErrorheaders= {
#"Authorization": f"Bearer SET_YOUR_API_KEY_HERE_IF_YOU_HAVE_ONE","Content-Type": "application/json",
"User-Agent": "Minimal Agent/0.1",
}
defcall_llm(messages, url="https://opencode.ai/zen/v1/chat/completions"): # Set URL herepayload= {
"model": "big-pickle", # Set model here"messages": messages,
"tools": [{
"type": "function",
"function": {
"name": "bash",
"description": "Runs a bash command.",
"parameters": {
"type": "object",
"properties": {"command": {"type": "string"}},
"required": ["command"],
},
},
}],
}
data=json.dumps(payload).encode("utf-8")
req=urllib.request.Request(url, data=data, headers=headers, method="POST")
withurllib.request.urlopen(req) asresponse:
returnjson.loads(response.read().decode("utf-8"))
defbash(command):
try:
returncheck_output(command, stderr=STDOUT, text=True)
exceptCalledProcessErrorase:
returnf"[ERROR: exit code {e.returncode} for `{command}`]\n"+e.outputmessages= [{"role": "system", "content": "You are a helpful assistant."}]
whileTrue:
prompt=input(">> ")
messages.append({"role": "user", "content": prompt})
whileTrue:
response=call_llm(messages)
msg=response["choices"][0]["message"]
messages.append(msg)
tool_calls=msg.get("tool_calls")
ifnottool_calls:
print(f"Reply:\n{msg.get('content', '')}")
breakfortool_callintool_calls:
fn=tool_call["function"]["name"]
tool_args=json.loads(tool_call["function"]["arguments"])
tool_call_id=tool_call["id"]
command= ["bash", "-c", tool_args["command"]]
print(f"[Tool: {fn}] {json.dumps(tool_args)}")
result=bash(command) iffn=="bash"elsef"Unknown tool {fn}"print(result)
messages.append({
"role": "tool",
"tool_call_id": tool_call_id,
"content": result,
})git clone https://github.com/99991/MinimalAgent.git
cd MinimalAgent
python3 agent.py
By default, the big-pickle model (GLM-4.6) provided by OpenCode is used, which might be overloaded.
If you have your own OpenAI-compatible API that you want to use, change the API key, URL and model in the code. You can also use a self-hosted model:
- Install llama.cpp'sllama app:
curl -LsSf https://llama.app/install.sh | sh- Serve a model, e.g. Qwen3.5-4B:
llama serve --no-mmproj -hf unsloth/Qwen3.5-4B-GGUF:Q5_K_M- This model is very small (only about 4GB of VRAM) and has only been chosen due to its accessibility. See llama.app and HuggingFace for more models. Qwen3.6-27B is pretty good if you have the (V)RAM, but might get stuck in loops if quantized too heavily (gets better with Q5 and up).
- Change the URL in the
call_llmfunction to thellama-serverURL:
defcall_llm(messages, url="http://127.0.0.1:8080/v1/chat/completions"):- Run
python agent.py
At its core, AI agents are very simple. All they do is:
- Append the user's prompt to the current context
- Query an LLM for tool calls and text replies
- Run the tools (
bashin our case) - Append the tool outputs and text reply to the context
- Continue at 1.
Since LLMs are very proficient in using bash, they don't necessarily require any other tools. They can just use bash to do whatever they need, e.g. edit files using sed or make HTTP requests using curl.
