Agentic AI design patterns: tool use, multi-agent, RAG, and planning
Core agentic patterns
Agentic AI systems are mostly built from a small set of reusable patterns that you combine: tool-use, multi‑agent, RAG, planning/reasoning, and reflection/self‑correction. These patterns are orthogonal: a “serious” agent will often use several at once. Below, each pattern has: when to use it, core structure, and a minimal but realistic code sketch (Python‑style, framework‑agnostic).
Assume a generic llm.chat() that takes messages and optional tools, and that tool calls are represented as function calls in code.
Tool‑use pattern (APIs & databases)
When to use
You need live data (APIs, DBs, search, internal systems).
Business logic and security must stay in your code, not in the model.
You want structured calls (e.g. JSON) rather than the model inventing URLs or SQL.
Core structure
Define tools as normal functions with schemas.
Give the model tool definitions (names, args, descriptions).
On each turn:
Ask the LLM whether to call a tool.
If it does, execute tool(s) in code and feed the results back into the model.
Otherwise, return the final answer.
Example: LLM with HTTP API + SQL DB
python
from typing import List, Dict, Any
import requests
import sqlite3
- Tools -
def fetch_weather(city: str) -> Dict[str, Any]:
# Wrap your real HTTP call here
r = requests.get("https://api.example.com/weather", params={"q": city})
r.raise_for_status()
return r.json()
def query_orders(user_id: str) -> List[Dict[str, Any]]:
conn = sqlite3.connect("orders.db")
cur = conn.cursor()
cur.execute("SELECT id, total, status FROM orders WHERE user_id = ?", (user_id,))
rows = cur.fetchall()
conn.close()
return [
{"id": r, "total": r, "status": r}
for r in rows
]
TOOLS = [
{
"name": "fetch_weather",
"description": "Get current weather by city name.",
"parameters": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"],
},
},
{
"name": "query_orders",
"description": "Get recent orders for a user by user id.",
"parameters": {
"type": "object",
"properties": {"user_id": {"type": "string"}},
"required": ["user_id"],
},
},
]
- Orchestrator loop -
def call_tool(tool_name: str, args: Dict[str, Any]) -> Any:
if tool_name == "fetch_weather":
return fetch_weather(args)
if tool_name == "query_orders":
return query_orders(args)
raise ValueError(f"Unknown tool {tool_name}")
def agent_turn(user_message: str) -> str:
messages = [{"role": "user", "content": user_message}]
# First call: ask model whether to use tools
response = llm.chat(messages=messages, tools=TOOLS)
if response.tool_calls:
# Single-step example; in practice, loop until no more tool calls
tool_call = response.tool_calls
result = call_tool(tool_call.name, tool_call.arguments)
messages.append({
"role": "assistant",
"tool_call_id": tool_call.id,
"content": str(result),
})
final = llm.chat(messages=messages)
return final.content
return response.content
Key ideas:
You own execution; the LLM only “routes” to tools.
You can log and guard tool calls for security and observability.
Same pattern applies to any API/DB, just wrap it as a function.
Multi‑agent pattern (specialised agents)
When to use
Tasks naturally decompose into specialist roles (planner, researcher, coder, reviewer).
Tool set is large or heterogeneous; you want narrow context per agent.
You want clearer debugging via role‑separated logs.
Typical variants:
Orchestrator + sub‑agents (sub‑agents called as tools).
Peer‑to‑peer swarm with no central controller (more complex to manage).
Pipeline / graph where outputs feed into the next agent.
Example: supervisor agent calling specialists as tools
python
class Agent:
def init(self, name: str, system_prompt: str):
self.name = name
self.system_prompt = system_prompt
def run(self, task: str, context: str = "") -> str:
msgs = [
{"role": "system", "content": self.system_prompt},
{"role": "user", "content": f"Task: {task}\n\nContext:\n{context}"},
]
return llm.chat(messages=msgs).content
researcher = Agent(
"researcher",
"You are a factual, citation-focused research assistant."
)
coder = Agent(
"coder",
"You are a senior software engineer. You write clear, working code."
)
reviewer = Agent(
"reviewer",
"You review solutions for correctness, edge cases, and simplicity."
)
-
Rizwan Saleem | https://rizwansaleem.co
Top comments (0)