Last active
April 21, 2026 01:27
-
-
Save Hassan-Naeem-code/f265b0ee835d64f61f997b1fa99a8122 to your computer and use it in GitHub Desktop.
Python: AI agent tool-calling loop from scratch — no LangChain, no framework, just the core loop
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| """ | |
| Agent tool-calling loop from scratch — no LangChain, no framework. | |
| Shows exactly what's happening when an "AI agent" uses tools: | |
| 1. Send user message + tool definitions to the model | |
| 2. If the model asks to call a tool, run it locally | |
| 3. Send the tool's result back | |
| 4. Repeat until the model gives a final answer | |
| Install: | |
| pip install openai requests | |
| Env: | |
| export OPENAI_API_KEY=sk-... | |
| """ | |
| import os | |
| import json | |
| import requests | |
| from openai import OpenAI | |
| client = OpenAI(api_key=os.environ["OPENAI_API_KEY"]) | |
| MODEL = "gpt-4o-mini" | |
| # --- Tool definitions (what the model sees) --- | |
| TOOLS = [ | |
| { | |
| "type": "function", | |
| "function": { | |
| "name": "get_weather", | |
| "description": "Get the current temperature for a city in Celsius.", | |
| "parameters": { | |
| "type": "object", | |
| "properties": {"city": {"type": "string"}}, | |
| "required": ["city"], | |
| }, | |
| }, | |
| }, | |
| { | |
| "type": "function", | |
| "function": { | |
| "name": "calculator", | |
| "description": "Evaluate a simple arithmetic expression.", | |
| "parameters": { | |
| "type": "object", | |
| "properties": {"expression": {"type": "string"}}, | |
| "required": ["expression"], | |
| }, | |
| }, | |
| }, | |
| ] | |
| # --- Tool implementations (what actually runs locally) --- | |
| def get_weather(city: str) -> str: | |
| # Open-Meteo geocoding + forecast, no API key required | |
| geo = requests.get( | |
| "https://geocoding-api.open-meteo.com/v1/search", | |
| params={"name": city, "count": 1}, | |
| timeout=10, | |
| ).json() | |
| if not geo.get("results"): | |
| return f"Could not find city: {city}" | |
| loc = geo["results"][0] | |
| fc = requests.get( | |
| "https://api.open-meteo.com/v1/forecast", | |
| params={"latitude": loc["latitude"], "longitude": loc["longitude"], "current_weather": True}, | |
| timeout=10, | |
| ).json() | |
| temp = fc["current_weather"]["temperature"] | |
| return f"{city}: {temp}°C" | |
| def calculator(expression: str) -> str: | |
| try: | |
| # safe enough for a demo; use a real parser in production | |
| allowed = set("0123456789+-*/(). ") | |
| if not set(expression) <= allowed: | |
| return "Error: expression contains disallowed characters." | |
| return str(eval(expression)) | |
| except Exception as e: | |
| return f"Error: {e}" | |
| TOOL_IMPLS = {"get_weather": get_weather, "calculator": calculator} | |
| def run_tool(name: str, args: dict) -> str: | |
| fn = TOOL_IMPLS.get(name) | |
| if not fn: | |
| return f"Unknown tool: {name}" | |
| return fn(**args) | |
| # --- The loop --- | |
| def agent(user_message: str, max_steps: int = 8) -> str: | |
| messages = [{"role": "user", "content": user_message}] | |
| for step in range(max_steps): | |
| res = client.chat.completions.create( | |
| model=MODEL, | |
| tools=TOOLS, | |
| messages=messages, | |
| ) | |
| msg = res.choices[0].message | |
| # Always append the assistant turn so tool_call_ids line up | |
| messages.append(msg.model_dump(exclude_none=True)) | |
| # No tool calls? We're done. | |
| if not msg.tool_calls: | |
| return msg.content or "" | |
| for call in msg.tool_calls: | |
| args = json.loads(call.function.arguments or "{}") | |
| print(f"[step {step}] → {call.function.name}({args})") | |
| result = run_tool(call.function.name, args) | |
| print(f"[step {step}] ← {result}") | |
| messages.append( | |
| {"role": "tool", "tool_call_id": call.id, "content": result} | |
| ) | |
| return "[agent halted: max steps reached]" | |
| if __name__ == "__main__": | |
| print(agent("What's the weather in Lahore right now, and what is 18 * 47?")) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment