Spaces:
Runtime error
Runtime error
Upload app.py with huggingface_hub
Browse files
app.py
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import gradio as gr
|
| 2 |
+
import json
|
| 3 |
+
import spaces
|
| 4 |
+
import torch
|
| 5 |
+
from transformers import AutoTokenizer, AutoModelForCausalLM
|
| 6 |
+
|
| 7 |
+
MODEL_ID = "dispatchAI/Llama-3.2-1B-FunctionCall-mobile"
|
| 8 |
+
|
| 9 |
+
tokenizer = None
|
| 10 |
+
model = None
|
| 11 |
+
|
| 12 |
+
def load_model():
|
| 13 |
+
global tokenizer, model
|
| 14 |
+
if tokenizer is None:
|
| 15 |
+
tokenizer = AutoTokenizer.from_pretrained(MODEL_ID)
|
| 16 |
+
model = AutoModelForCausalLM.from_pretrained(MODEL_ID, torch_dtype=torch.float16, device_map="auto")
|
| 17 |
+
return tokenizer, model
|
| 18 |
+
|
| 19 |
+
# Available functions the agent can call
|
| 20 |
+
AVAILABLE_FUNCTIONS = {
|
| 21 |
+
"set_alarm": {"description": "Set an alarm", "params": {"time": "string", "label": "string"}},
|
| 22 |
+
"send_message": {"description": "Send a message to a contact", "params": {"to": "string", "message": "string"}},
|
| 23 |
+
"call_contact": {"description": "Call a contact", "params": {"contact": "string"}},
|
| 24 |
+
"search_web": {"description": "Search the web", "params": {"query": "string"}},
|
| 25 |
+
"open_app": {"description": "Open an application", "params": {"app_name": "string"}},
|
| 26 |
+
"set_timer": {"description": "Set a timer", "params": {"duration_minutes": "integer"}},
|
| 27 |
+
"get_weather": {"description": "Get weather for a location", "params": {"location": "string"}},
|
| 28 |
+
}
|
| 29 |
+
|
| 30 |
+
@spaces.GPU
|
| 31 |
+
def parse_function_call(user_input: str) -> str:
|
| 32 |
+
"""Parse user input into a function call using the mobile function-calling model.
|
| 33 |
+
|
| 34 |
+
Args:
|
| 35 |
+
user_input: Natural language user input (e.g., "Set an alarm for 7am")
|
| 36 |
+
|
| 37 |
+
Returns:
|
| 38 |
+
JSON with the parsed function call
|
| 39 |
+
"""
|
| 40 |
+
tokenizer, model = load_model()
|
| 41 |
+
|
| 42 |
+
functions_text = json.dumps(AVAILABLE_FUNCTIONS, indent=2)
|
| 43 |
+
prompt = f"""You are a function-calling assistant. Parse the user's request into a function call.
|
| 44 |
+
|
| 45 |
+
Available functions:
|
| 46 |
+
{functions_text}
|
| 47 |
+
|
| 48 |
+
User request: "{user_input}"
|
| 49 |
+
|
| 50 |
+
Respond with ONLY a JSON object, no other text:
|
| 51 |
+
{{"function": "function_name", "parameters": {{...}}}}"""
|
| 52 |
+
|
| 53 |
+
inputs = tokenizer(prompt, return_tensors="pt").to(model.device)
|
| 54 |
+
with torch.no_grad():
|
| 55 |
+
outputs = model.generate(**inputs, max_new_tokens=100, temperature=0.1, do_sample=True, pad_token_id=tokenizer.eos_token_id)
|
| 56 |
+
|
| 57 |
+
response = tokenizer.decode(outputs[0][inputs["input_ids"].shape[1]:], skip_special_tokens=True).strip()
|
| 58 |
+
|
| 59 |
+
# Try to parse as JSON
|
| 60 |
+
try:
|
| 61 |
+
# Find JSON in response
|
| 62 |
+
start = response.find("{")
|
| 63 |
+
end = response.rfind("}") + 1
|
| 64 |
+
if start >= 0 and end > start:
|
| 65 |
+
parsed = json.loads(response[start:end])
|
| 66 |
+
func_name = parsed.get("function", "unknown")
|
| 67 |
+
params = parsed.get("parameters", {})
|
| 68 |
+
|
| 69 |
+
# Check if function exists
|
| 70 |
+
if func_name in AVAILABLE_FUNCTIONS:
|
| 71 |
+
return json.dumps({
|
| 72 |
+
"status": "success",
|
| 73 |
+
"function": func_name,
|
| 74 |
+
"parameters": params,
|
| 75 |
+
"description": AVAILABLE_FUNCTIONS[func_name]["description"],
|
| 76 |
+
"user_input": user_input,
|
| 77 |
+
}, indent=2)
|
| 78 |
+
else:
|
| 79 |
+
return json.dumps({
|
| 80 |
+
"status": "unknown_function",
|
| 81 |
+
"function": func_name,
|
| 82 |
+
"parameters": params,
|
| 83 |
+
"available_functions": list(AVAILABLE_FUNCTIONS.keys()),
|
| 84 |
+
}, indent=2)
|
| 85 |
+
except json.JSONDecodeError:
|
| 86 |
+
pass
|
| 87 |
+
|
| 88 |
+
# Fallback: keyword-based parsing
|
| 89 |
+
lower = user_input.lower()
|
| 90 |
+
if "alarm" in lower:
|
| 91 |
+
return json.dumps({"status": "fallback", "function": "set_alarm", "parameters": {"time": "07:00", "label": "alarm"}, "source": "keyword_fallback"}, indent=2)
|
| 92 |
+
elif "call" in lower:
|
| 93 |
+
contact = user_input.replace("call", "").strip()
|
| 94 |
+
return json.dumps({"status": "fallback", "function": "call_contact", "parameters": {"contact": contact}, "source": "keyword_fallback"}, indent=2)
|
| 95 |
+
elif "message" in lower or "text" in lower or "send" in lower:
|
| 96 |
+
return json.dumps({"status": "fallback", "function": "send_message", "parameters": {"to": "unknown", "message": ""}, "source": "keyword_fallback"}, indent=2)
|
| 97 |
+
elif "timer" in lower:
|
| 98 |
+
return json.dumps({"status": "fallback", "function": "set_timer", "parameters": {"duration_minutes": 5}, "source": "keyword_fallback"}, indent=2)
|
| 99 |
+
elif "weather" in lower:
|
| 100 |
+
return json.dumps({"status": "fallback", "function": "get_weather", "parameters": {"location": "current"}, "source": "keyword_fallback"}, indent=2)
|
| 101 |
+
|
| 102 |
+
return json.dumps({"status": "no_match", "message": "Could not parse function call", "available_functions": list(AVAILABLE_FUNCTIONS.keys())}, indent=2)
|
| 103 |
+
|
| 104 |
+
def list_functions() -> str:
|
| 105 |
+
"""List all available functions the agent can call."""
|
| 106 |
+
return json.dumps(AVAILABLE_FUNCTIONS, indent=2)
|
| 107 |
+
|
| 108 |
+
with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue"), title="dispatchAI Function Calling Agent") as demo:
|
| 109 |
+
gr.Markdown("""
|
| 110 |
+
# 🔧 dispatchAI Function Calling Agent
|
| 111 |
+
|
| 112 |
+
An on-device agent that parses natural language into structured function calls.
|
| 113 |
+
|
| 114 |
+
**Model:** [Llama-3.2-1B-FunctionCall-mobile](https://huggingface.co/dispatchAI/Llama-3.2-1B-FunctionCall-mobile) (1B params, Q4 quantized)
|
| 115 |
+
|
| 116 |
+
Try: "Set an alarm for 7am", "Call mom", "Send a message to John saying I'll be late"
|
| 117 |
+
""")
|
| 118 |
+
|
| 119 |
+
with gr.Row():
|
| 120 |
+
inp = gr.Textbox(label="User Input", placeholder="Set an alarm for 7am tomorrow", scale=3)
|
| 121 |
+
btn = gr.Button("Parse Function Call", variant="primary", scale=1)
|
| 122 |
+
|
| 123 |
+
out = gr.Textbox(label="Parsed Function Call (JSON)", lines=12)
|
| 124 |
+
btn.click(fn=parse_function_call, inputs=inp, outputs=out)
|
| 125 |
+
|
| 126 |
+
with gr.Accordion("Available Functions", open=False):
|
| 127 |
+
fn_btn = gr.Button("List Functions")
|
| 128 |
+
fn_out = gr.Textbox(label="Functions (JSON)", lines=15)
|
| 129 |
+
fn_btn.click(fn=list_functions, outputs=fn_out)
|
| 130 |
+
|
| 131 |
+
gr.Markdown("---\n🚀 [dispatchAI](https://huggingface.co/dispatchAI) — Small. Mobile. Free. UAE-built.")
|
| 132 |
+
|
| 133 |
+
demo.launch(mcp_server=True)
|