Spaces:
Sleeping
Sleeping
import gradio as gr | |
import json, os, time, uuid, traceback | |
from smolagents import CodeAgent, tool | |
from smolagents.models import LiteLLMModel | |
try: | |
from smolagents import HfApiModel | |
except Exception: | |
HfApiModel = None | |
# --------- Model factory (latest-compatible) ---------- | |
def get_model(prefer_openai=True): | |
""" | |
Pick an LLM for the agent. | |
- Prefer OpenAI via LiteLLM if OPENAI_API_KEY is set. | |
- Else fall back to Hugging Face Inference (HF_MODEL). | |
NOTE: Temperature is set on the *model* in current smolagents. | |
""" | |
last_error = None | |
if prefer_openai and os.getenv("OPENAI_API_KEY"): | |
model_id = os.getenv("OPENAI_MODEL", "gpt-4o-mini") # safer default than gpt-5 | |
try: | |
return LiteLLMModel( | |
model_id=model_id, | |
api_key=os.getenv("OPENAI_API_KEY"), | |
base_url=os.getenv("OPENAI_BASE_URL"), | |
temperature=0 # determinism for orchestration | |
) | |
except Exception as e: | |
last_error = f"OpenAI model init failed for '{model_id}': {e}" | |
# one more fallback attempt | |
try: | |
return LiteLLMModel( | |
model_id="gpt-4o-mini", | |
api_key=os.getenv("OPENAI_API_KEY"), | |
base_url=os.getenv("OPENAI_BASE_URL"), | |
temperature=0 | |
) | |
except Exception as e2: | |
last_error += f" | Fallback gpt-4o-mini failed: {e2}" | |
if HfApiModel is not None: | |
try: | |
return HfApiModel(os.getenv("HF_MODEL", "Qwen/Qwen2.5-7B-Instruct")) | |
except Exception as e: | |
last_error = f"HF model init failed: {e}" | |
raise RuntimeError(last_error or "No model available. Set OPENAI_API_KEY or HF_MODEL in Space secrets.") | |
# --------------- Diagnostics (OpenAI ping) --------------- | |
def diag_openai_ping(prompt="Say 'pong' and nothing else."): | |
""" | |
Quick connectivity test to your OpenAI key/model. | |
Newer smolagents: no system_prompt arg; put instructions in the goal text. | |
""" | |
try: | |
model = get_model(prefer_openai=True) | |
def echo_tool(x: str) -> str: | |
"""Echo the provided text. | |
Args: | |
x (str): Text to echo back. | |
Returns: | |
str: The same text. | |
""" | |
return x | |
agent = CodeAgent( | |
tools=[echo_tool], | |
model=model, | |
add_base_tools=False, | |
# no temperature or system_prompt here (unsupported on latest agent ctor) | |
) | |
goal = "Reply with the user message verbatim. No extra words.\nMessage: " + prompt | |
out = agent.run(goal) | |
return f"β OpenAI ping ok.\nModel: {getattr(model, 'model_id', 'unknown')}\nOutput: {out}" | |
except Exception: | |
return "β OpenAI ping failed:\n" + traceback.format_exc() | |
# ===================== Exercise 1 ===================== | |
def add(a: float, b: float) -> float: | |
"""Add two numbers. | |
Args: | |
a (float): First addend. | |
b (float): Second addend. | |
Returns: | |
float: Sum a + b. | |
""" | |
return a + b | |
def mul(a: float, b: float) -> float: | |
"""Multiply two numbers. | |
Args: | |
a (float): First factor. | |
b (float): Second factor. | |
Returns: | |
float: Product a * b. | |
""" | |
return a * b | |
def run_ex1(): | |
try: | |
agent = CodeAgent( | |
tools=[add, mul], | |
model=get_model(), | |
add_base_tools=False, | |
) | |
goal = ( | |
"You are a careful math assistant. Use the available tools to compute the answer.\n" | |
"Finally, PRINT ONLY a JSON object like {\"result\": <number>}.\n" | |
"Task: Compute 2 * (3 + 4) and return {\"result\": 14}." | |
) | |
out = agent.run(goal) | |
return str(out) | |
except Exception: | |
return "β Exercise 1 error:\n" + traceback.format_exc() | |
# ===================== Exercise 2 ===================== | |
def run_ex2(): | |
try: | |
agent = CodeAgent( | |
tools=[add, mul], | |
model=get_model(), | |
add_base_tools=False, | |
) | |
goal = ( | |
"Follow this fixed plan exactly:\n" | |
"1) Call add(a=3, b=4) and store the result in x.\n" | |
"2) Call mul(a=2, b=x) to compute y.\n" | |
"3) PRINT ONLY this JSON: {\"result\": y}\n" | |
"Never print explanations.\n" | |
"Now execute the plan." | |
) | |
out = agent.run(goal) | |
return str(out) | |
except Exception: | |
return "β Exercise 2 error:\n" + traceback.format_exc() | |
# ===================== Exercise 3 ===================== | |
def validate_pr(pr: dict) -> dict: | |
"""Validate basic PR fields and structure. | |
Args: | |
pr (dict): Purchase Requisition with keys: pr_id, requester, cost_center, currency, items. | |
Returns: | |
dict: {"ok": bool, "errors": list[str]} | |
""" | |
req = ["pr_id", "requester", "cost_center", "currency", "items"] | |
errors = [f"Missing {k}" for k in req if k not in pr] | |
if not isinstance(pr.get("items", []), list) or not pr["items"]: | |
errors.append("Items must be a non-empty list") | |
return {"ok": len(errors) == 0, "errors": errors} | |
def create_po(pr: dict) -> dict: | |
"""Create a simple Purchase Order (PO) from a PR. | |
Args: | |
pr (dict): Validated PR dict with items[{sku, quantity, unit_price}] and currency. | |
Returns: | |
dict: PO JSON with po_id, items, subtotal, tax, total, source_pr_id. | |
""" | |
subtotal = 0.0 | |
items_out = [] | |
for it in pr.get("items", []): | |
line = float(it["quantity"]) * float(it["unit_price"]) | |
items_out.append({"sku": it.get("sku", "UNKNOWN"), "line_total": round(line, 2)}) | |
subtotal += line | |
tax = round(subtotal * 0.08, 2) | |
total = round(subtotal + tax, 2) | |
return { | |
"po_id": f"PO-{int(time.time())}-{uuid.uuid4().hex[:6].upper()}", | |
"currency": pr.get("currency", "SGD"), | |
"items": items_out, | |
"subtotal": round(subtotal, 2), | |
"tax": tax, | |
"total": total, | |
"source_pr_id": pr.get("pr_id"), | |
} | |
DEFAULT_PR = json.dumps({ | |
"pr_id": "PR-1001", | |
"requester": "A. Tan", | |
"cost_center": "CC-SG-OPS", | |
"currency": "SGD", | |
"items": [ | |
{"sku": "PAPER-A4-80G", "quantity": 20, "unit_price": 6.8}, | |
{"sku": "STAPLER-01", "quantity": 5, "unit_price": 18.5} | |
] | |
}, indent=2) | |
def run_ex3(pr_text): | |
# Validate JSON, then inline into goal (latest run() has no additional_context kw) | |
try: | |
_ = json.loads(pr_text) | |
except Exception as e: | |
return f"Invalid JSON: {e}" | |
try: | |
agent = CodeAgent( | |
tools=[validate_pr, create_po], | |
model=get_model(), | |
add_base_tools=False, | |
) | |
goal = ( | |
"You are a procurement agent.\n" | |
"Use the tools as follows:\n" | |
"1) validate_pr(pr). If ok==false, PRINT ONLY {\"error\": errors}.\n" | |
"2) If ok==true, call create_po(pr).\n" | |
"3) PRINT ONLY the resulting PO JSON. No extra text.\n" | |
"The PR object you must operate on is provided below as JSON. " | |
"Parse it into a Python dict variable named `pr` before calling tools.\n\n" | |
"PR JSON:\n```json\n" + pr_text + "\n```\n" | |
) | |
out = agent.run(goal) | |
return str(out) | |
except Exception: | |
return "β Exercise 3 error:\n" + traceback.format_exc() | |
# --------------------- Gradio UI --------------------- | |
with gr.Blocks(title="Smolagents Beginner Lab (Online)") as demo: | |
gr.Markdown("# Smolagents Beginner Lab (Online)") | |
gr.Markdown("Set `OPENAI_API_KEY` in Space secrets (recommended). Optional: `OPENAI_MODEL` (e.g., gpt-4o-mini). " | |
"Fallback: set `HF_MODEL` (e.g., Qwen/Qwen2.5-7B-Instruct).") | |
with gr.Tab("Diagnostics"): | |
ping_btn = gr.Button("Run OpenAI ping test") | |
ping_out = gr.Textbox(label="Diagnostics Output", lines=12) | |
ping_btn.click(lambda: diag_openai_ping(), inputs=None, outputs=ping_out) | |
with gr.Tab("1) Hello Tools"): | |
btn1 = gr.Button("Run Exercise 1") | |
out1 = gr.Textbox(label="Output", lines=6) | |
btn1.click(lambda: run_ex1(), inputs=None, outputs=out1) | |
with gr.Tab("2) Guardrails (deterministic)"): | |
btn2 = gr.Button("Run Exercise 2") | |
out2 = gr.Textbox(label="Output", lines=6) | |
btn2.click(lambda: run_ex2(), inputs=None, outputs=out2) | |
with gr.Tab("3) Mini PR β PO"): | |
pr_input = gr.Textbox(label="PR JSON", value=DEFAULT_PR, lines=16) | |
btn3 = gr.Button("Run Exercise 3") | |
out3 = gr.Textbox(label="Output", lines=10) | |
btn3.click(run_ex3, inputs=pr_input, outputs=out3) | |
demo.launch() | |