On this tutorial, we construct a Groq-powered agentic analysis workflow that runs immediately utilizing Groq’s free OpenAI-compatible inference endpoint. We configure LangChain’s ChatOpenAI interface to work with Groq by setting the Groq API key and base URL, permitting us to make use of quick hosted fashions corresponding to llama-3.3-70b-versatile for tool-based reasoning. We then join the mannequin with sensible instruments for internet search, webpage fetching, file dealing with, Python execution, talent loading, sub-agent delegation, and long-term reminiscence. By the top of the tutorial, now we have a working Groq-based multi-step agent that may analysis a subject, delegate centered subtasks, generate structured outputs, and save helpful info for later runs.
import subprocess, sys
def _pip(*a): subprocess.check_call([sys.executable,"-m","pip","install","-q",*a])
_pip("langgraph>=0.2.50", "langchain>=0.3.0", "langchain-openai>=0.2.0",
"langchain-community>=0.3.0", "ddgs", "requests", "beautifulsoup4",
"tiktoken", "pydantic>=2.0")
import os, getpass
if not os.environ.get("GROQ_API_KEY"):
os.environ["GROQ_API_KEY"] = getpass.getpass("GROQ_API_KEY (free at console.groq.com/keys): ")
os.environ["OPENAI_API_KEY"] = os.environ["GROQ_API_KEY"]
os.environ["OPENAI_BASE_URL"] = "https://api.groq.com/openai/v1"
MODEL_NAME = "llama-3.3-70b-versatile"
import json, re, io, contextlib, pathlib
from typing import Annotated, TypedDict, Sequence, Literal, Listing, Dict, Any
from datetime import datetime, timezone
from langchain_openai import ChatOpenAI
from langchain_core.messages import (
SystemMessage, HumanMessage, AIMessage, ToolMessage, BaseMessage)
from langchain_core.instruments import device
from langgraph.graph import StateGraph, END
from langgraph.graph.message import add_messages
from langgraph.prebuilt import ToolNode
We set up the core libraries required to construct the Groq-powered agent workflow, together with LangGraph, LangChain, DuckDuckGo search utilities, and supporting parsing libraries. We securely acquire the Groq API key and configure Groq as an OpenAI-compatible endpoint by setting the API key and base URL. We then import all required modules for messages, instruments, graph development, typing, filesystem dealing with, and mannequin initialization.
SANDBOX = pathlib.Path("/content material/deerflow_sandbox").resolve()
for sub in ["uploads","workspace","outputs","skills/public","skills/custom","memory"]:
(SANDBOX/sub).mkdir(dad and mom=True, exist_ok=True)
def _safe(p: str) -> pathlib.Path:
full = (SANDBOX/p.lstrip("/")).resolve()
if not str(full).startswith(str(SANDBOX)):
elevate ValueError(f"path escapes sandbox: {p}")
return full
SKILLS: Dict[str, Dict[str,str]] = {}
def register_skill(identify, description, content material, location="public"):
d = SANDBOX/"expertise"/location/identify; d.mkdir(dad and mom=True, exist_ok=True)
(d/"SKILL.md").write_text(content material)
SKILLS[name] = {"description": description, "content material": content material,
"path": str(d/"SKILL.md")}
register_skill("analysis",
"Conduct multi-source internet analysis on a subject and produce structured notes.",
"""# Analysis Ability
## Workflow
1. Decompose the query into 3-5 sub-questions.
2. For every sub-question name `web_search` and choose 2 authoritative URLs.
3. `web_fetch` these URLs; extract concrete information, numbers, dates.
4. Cross-reference for consensus vs. disagreement.
5. Append findings to `workspace/research_notes.md`: declare → proof → URL.
## Greatest practices
- Choose major sources. Observe dates. By no means fabricate URLs or numbers.""")
register_skill("report-generation",
"Synthesize analysis notes into a sophisticated markdown report in outputs/.",
"""# Report Era Ability
## Workflow
1. file_read('workspace/research_notes.md').
2. Define: exec abstract, key findings, evaluation, conclusion, sources.
3. file_write('outputs/report.md', ...).
## Construction
- # Title
- ## Govt Abstract (3–5 sentences)
- ## Key Findings (bullets)
- ## Detailed Evaluation (sections)
- ## Conclusion
- ## Sources (numbered URL record)""")
register_skill("code-execution",
"Run Python within the sandbox for computation, information wrangling, charts.",
"""# Code Execution Ability
1. Plan in plain language first.
2. python_exec the code; persistent artifacts go to /outputs/.
3. Confirm earlier than quoting outcomes.""")
MEM = SANDBOX/"reminiscence/long_term.json"
if not MEM.exists():
MEM.write_text(json.dumps({"information":[],"preferences":{}}, indent=2))
def _load_mem(): return json.masses(MEM.read_text())
def _save_mem(m): MEM.write_text(json.dumps(m, indent=2))
We create a sandboxed venture listing in Colab to maintain uploads, workspace information, outputs, expertise, and reminiscence organized in a single managed location. We outline reusable expertise for analysis, report technology, and code execution so the agent can uncover and comply with structured workflows. We additionally initialize a easy long-term reminiscence JSON file that shops information and preferences throughout a number of runs throughout the similar sandbox.
@device
def list_skills() -> str:
"""Listing all expertise with one-line descriptions. Name this primary for complicated duties."""
return "n".be part of(f"- {n}: {s['description']}" for n,s in SKILLS.objects())
@device
def load_skill(identify: str) -> str:
"""Load full SKILL.md for `identify`. Name earlier than operating its workflow."""
if identify not in SKILLS: return f"Unknown. Obtainable: {record(SKILLS)}"
return SKILLS[name]["content"]
@device
def web_search(question: str, max_results: int = 5) -> str:
"""Search the net (DuckDuckGo). Returns titles, URLs, snippets."""
from ddgs import DDGS
out = []
strive:
with DDGS() as d:
for r in d.textual content(question, max_results=max_results):
out.append(f"- {r.get('title','')}n URL: {r.get('href','')}n "
f"{(r.get('physique') or '')[:220]}")
besides Exception as e:
return f"search error: {e}"
return "n".be part of(out) or "no outcomes"
@device
def web_fetch(url: str, max_chars: int = 4000) -> str:
"""Fetch a URL, return cleaned textual content (scripts/nav stripped)."""
import requests
from bs4 import BeautifulSoup
strive:
r = requests.get(url, timeout=15,
headers={"Consumer-Agent":"Mozilla/5.0 DeerFlow-Lite"})
soup = BeautifulSoup(r.textual content, "html.parser")
for s in soup(["script","style","nav","footer","aside","header"]): s.decompose()
textual content = re.sub(r"ns*n", "nn", soup.get_text("n")).strip()
return textual content[:max_chars] or "(empty web page)"
besides Exception as e:
return f"fetch error: {e}"
@device
def file_write(path: str, content material: str) -> str:
"""Write content material to a sandbox path, e.g. 'workspace/notes.md' or 'outputs/x.md'."""
p = _safe(path); p.mum or dad.mkdir(dad and mom=True, exist_ok=True)
p.write_text(content material)
return f"wrote {len(content material)} chars → {path}"
@device
def file_read(path: str) -> str:
"""Learn a sandbox file (first 8 KB)."""
p = _safe(path)
return p.read_text()[:8000] if p.exists() else f"not discovered: {path}"
@device
def file_list(path: str = "") -> str:
"""Listing information below a sandbox dir."""
base = _safe(path) if path else SANDBOX
if not base.exists(): return "not discovered"
objects = []
for c in sorted(base.rglob("*")):
if "reminiscence" in c.relative_to(SANDBOX).components: proceed
objects.append(f" {'D' if c.is_dir() else 'F'} {c.relative_to(SANDBOX)}")
return "n".be part of(objects[:60]) or "(empty)"
@device
def python_exec(code: str) -> str:
"""Run Python within the sandbox. SANDBOX_ROOT is preset."""
g = {"__name__":"__sb__", "SANDBOX_ROOT": str(SANDBOX)}
buf = io.StringIO()
strive:
with contextlib.redirect_stdout(buf), contextlib.redirect_stderr(buf):
exec(code, g)
return (buf.getvalue() or "(no stdout)")[:4000]
besides Exception as e:
return f"{kind(e).__name__}: {e}n{buf.getvalue()[:1500]}"
@device
def bear in mind(truth: str) -> str:
"""Persist a single truth to long-term reminiscence (survives throughout runs)."""
m = _load_mem()
m["facts"].append({"truth": truth, "ts": datetime.now(timezone.utc).isoformat()})
_save_mem(m)
return f"remembered ({len(m['facts'])} whole)"
@device
def recall() -> str:
"""Retrieve every little thing in long-term reminiscence."""
m = _load_mem()
if not m["facts"]: return "(reminiscence empty)"
return "n".be part of(f"- {f['fact']}" for f in m["facts"][-20:])
We outline the principle instruments the Groq-backed agent can name throughout execution, together with itemizing expertise, loading talent directions, looking the net, fetching webpages, studying information, and writing information. We additionally present the agent with a sandboxed Python execution setting so it could actually run computations or generate artifacts when wanted. We add reminiscence instruments that permit the agent to recollect vital information and recall beforehand saved info.
@device
def spawn_subagent(function: str, process: str,
allowed_tools: str = "web_search,web_fetch,file_write,file_read") -> str:
"""Spawn an remoted sub-agent with a centered function and scoped instruments.
Returns its closing report string. Use for parallelizable / centered subtasks."""
bag = {t.identify: t for t in BASE_TOOLS}
sub_tools = [bag[n.strip()] for n in allowed_tools.break up(",") if n.strip() in bag]
sub_llm = ChatOpenAI(mannequin=MODEL_NAME, temperature=0.2).bind_tools(sub_tools)
sys_msg = SystemMessage(content material=(
f"You're a specialised sub-agent. Position: {function}.n"
f"You use in an ISOLATED context — no entry to steer historical past.n"
f"Instruments: {', '.be part of(t.identify for t in sub_tools)}.n"
"Finish with a closing assistant message beginning 'FINAL REPORT:' "
"containing a structured ≤700-word abstract together with any URLs."))
msgs: Listing[BaseMessage] = [sys_msg, HumanMessage(content=task)]
for _ in vary(8):
r = sub_llm.invoke(msgs); msgs.append(r)
if not getattr(r, "tool_calls", None):
return f"[sub-agent: {role}]n" + (r.content material if isinstance(r.content material,str) else str(r.content material))
for tc in r.tool_calls:
t = bag.get(tc["name"])
strive:
res = t.invoke(tc["args"]) if t else f"unknown device {tc['name']}"
besides Exception as e:
res = f"device error: {e}"
msgs.append(ToolMessage(content material=str(res)[:3000], tool_call_id=tc["id"]))
return f"[sub-agent: {role}] step-limit reached."
BASE_TOOLS = [list_skills, load_skill, web_search, web_fetch, file_write,
file_read, file_list, python_exec, remember, recall]
ALL_TOOLS = BASE_TOOLS + [spawn_subagent]
LEAD_SYSTEM = f"""You might be DeerFlow-Lite, a long-horizon super-agent harness.
Sandbox format (relative to {SANDBOX}):
uploads/ – person information
workspace/ – your scratchpad
outputs/ – closing deliverables
expertise/ – functionality modules (load_skill)
Rules:
• For non-trivial duties: list_skills → load_skill → execute.
• Use spawn_subagent for centered subtasks (remoted context retains lead lean).
• Persist intermediates to workspace/, deliverables to outputs/.
• Use bear in mind(truth) for cross-session data.
• End with a brief abstract of what was produced and the place.
Right now: {datetime.now(timezone.utc).strftime('%Y-%m-%d')}."""
class AgentState(TypedDict):
messages: Annotated[Sequence[BaseMessage], add_messages]
llm = ChatOpenAI(mannequin=MODEL_NAME, temperature=0.3).bind_tools(ALL_TOOLS)
def call_model(state: AgentState):
msgs = record(state["messages"])
if not msgs or not isinstance(msgs[0], SystemMessage):
msgs = [SystemMessage(content=LEAD_SYSTEM)] + msgs
return {"messages": [llm.invoke(msgs)]}
def route(state: AgentState) -> Literal["tools","__end__"]:
final = state["messages"][-1]
return "instruments" if getattr(final, "tool_calls", None) else END
g = StateGraph(AgentState)
g.add_node("agent", call_model)
g.add_node("instruments", ToolNode(ALL_TOOLS))
g.set_entry_point("agent")
g.add_conditional_edges("agent", route, {"instruments":"instruments", END: END})
g.add_edge("instruments", "agent")
APP = g.compile()
We create a sub-agent device that permits the principle Groq-powered agent to delegate centered duties to an remoted assistant with a restricted set of instruments. We then acquire all obtainable instruments, outline the lead system immediate, initialize the Groq-backed chat mannequin, and bind the instruments to it. We lastly constructed the LangGraph workflow so the agent can alternate between reasoning and gear execution till it reaches a closing reply.
def run(process: str, max_steps: int = 25):
print("="*78); print(f"🦌 TASK: {process}"); print("="*78)
state = {"messages":[HumanMessage(content=task)]}
n = 0
for ev in APP.stream(state, {"recursion_limit": max_steps*2}, stream_mode="updates"):
for node, payload in ev.objects():
for m in payload.get("messages", []):
n += 1
if isinstance(m, AIMessage):
if m.tool_calls:
for tc in m.tool_calls:
args = json.dumps(tc["args"], ensure_ascii=False)
args = args[:140] + ("…" if len(args)>140 else "")
print(f"[{n:02}] 🔧 {tc['name']}({args})")
else:
txt = m.content material if isinstance(m.content material,str) else str(m.content material)
print(f"[{n:02}] 🦌 {txt[:800]}")
elif isinstance(m, ToolMessage):
s = str(m.content material).substitute("n"," ")[:220]
print(f"[{n:02}] 📤 {s}")
print("n"+"="*78); print("✅ COMPLETE — sandbox state:"); print("="*78)
print(file_list.invoke({"path":""}))
print("n🧠 Lengthy-term reminiscence:"); print(recall.invoke({}))
for f in sorted((SANDBOX/"outputs").rglob("*")):
if f.is_file():
print(f"n--- 📄 {f.relative_to(SANDBOX)} (first 800 chars) ---")
print(f.read_text()[:800])
run(
"Give me a briefing on small language fashions (SLMs) in 2025. "
"(1) uncover expertise; (2) spawn a researcher sub-agent to collect "
"specifics on three notable SLMs from 2024-2025 with sizes, benchmarks, "
"and use instances — sub-agent saves to workspace/slm_research.md; "
"(3) load report-generation talent and write outputs/slm_briefing.md "
"(~400 phrases) with a Sources part; (4) save the only most "
"vital takeaway to long-term reminiscence; (5) summarize.",
max_steps=25,
)
We outline the run() perform that begins a person process, streams every agent step, and prints device calls, device outputs, and closing responses in a readable format. We additionally show the sandbox file construction, long-term reminiscence, and generated output information after the workflow completes. We end by operating a demo process during which the Groq-powered agent researches small language fashions, prepares a briefing, saves a report, and shops one key takeaway in reminiscence.
In conclusion, we created a compact but succesful Groq-based agent framework that demonstrates how Groq’s OpenAI-compatible API can function a quick, accessible backend for superior LLM workflows. We used LangGraph to handle the agent loop, LangChain to bind instruments to the Groq-hosted mannequin, and customized Python utilities to present the system managed entry to look, information, code execution, and reminiscence. We additionally demonstrated how remoted sub-agents will help deal with centered analysis duties whereas the principle agent coordinates the general workflow. Additionally, we completed with a sensible Groq-powered agentic system that may be prolonged into analysis assistants, automated briefing turbines, and multi-step AI purposes.
Take a look at the Full Codes with Pocket book right here. Additionally, be happy to comply with us on Twitter and don’t neglect to hitch our 130k+ ML SubReddit and Subscribe to our E-newsletter. Wait! are you on telegram? now you possibly can be part of us on telegram as nicely.
Must accomplice with us for selling your GitHub Repo OR Hugging Face Web page OR Product Launch OR Webinar and so forth.? Join with us
