File size: 5,996 Bytes
264262c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
import gradio as gr
import json
import spaces
import torch
from transformers import AutoTokenizer, AutoModelForCausalLM

MODEL_ID = "dispatchAI/Llama-3.2-1B-FunctionCall-mobile"

tokenizer = None
model = None

def load_model():
    global tokenizer, model
    if tokenizer is None:
        tokenizer = AutoTokenizer.from_pretrained(MODEL_ID)
        model = AutoModelForCausalLM.from_pretrained(MODEL_ID, torch_dtype=torch.float16, device_map="auto")
    return tokenizer, model

# Available functions the agent can call
AVAILABLE_FUNCTIONS = {
    "set_alarm": {"description": "Set an alarm", "params": {"time": "string", "label": "string"}},
    "send_message": {"description": "Send a message to a contact", "params": {"to": "string", "message": "string"}},
    "call_contact": {"description": "Call a contact", "params": {"contact": "string"}},
    "search_web": {"description": "Search the web", "params": {"query": "string"}},
    "open_app": {"description": "Open an application", "params": {"app_name": "string"}},
    "set_timer": {"description": "Set a timer", "params": {"duration_minutes": "integer"}},
    "get_weather": {"description": "Get weather for a location", "params": {"location": "string"}},
}

@spaces.GPU
def parse_function_call(user_input: str) -> str:
    """Parse user input into a function call using the mobile function-calling model.
    
    Args:
        user_input: Natural language user input (e.g., "Set an alarm for 7am")
    
    Returns:
        JSON with the parsed function call
    """
    tokenizer, model = load_model()
    
    functions_text = json.dumps(AVAILABLE_FUNCTIONS, indent=2)
    prompt = f"""You are a function-calling assistant. Parse the user's request into a function call.

Available functions:
{functions_text}

User request: "{user_input}"

Respond with ONLY a JSON object, no other text:
{{"function": "function_name", "parameters": {{...}}}}"""
    
    inputs = tokenizer(prompt, return_tensors="pt").to(model.device)
    with torch.no_grad():
        outputs = model.generate(**inputs, max_new_tokens=100, temperature=0.1, do_sample=True, pad_token_id=tokenizer.eos_token_id)
    
    response = tokenizer.decode(outputs[0][inputs["input_ids"].shape[1]:], skip_special_tokens=True).strip()
    
    # Try to parse as JSON
    try:
        # Find JSON in response
        start = response.find("{")
        end = response.rfind("}") + 1
        if start >= 0 and end > start:
            parsed = json.loads(response[start:end])
            func_name = parsed.get("function", "unknown")
            params = parsed.get("parameters", {})
            
            # Check if function exists
            if func_name in AVAILABLE_FUNCTIONS:
                return json.dumps({
                    "status": "success",
                    "function": func_name,
                    "parameters": params,
                    "description": AVAILABLE_FUNCTIONS[func_name]["description"],
                    "user_input": user_input,
                }, indent=2)
            else:
                return json.dumps({
                    "status": "unknown_function",
                    "function": func_name,
                    "parameters": params,
                    "available_functions": list(AVAILABLE_FUNCTIONS.keys()),
                }, indent=2)
    except json.JSONDecodeError:
        pass
    
    # Fallback: keyword-based parsing
    lower = user_input.lower()
    if "alarm" in lower:
        return json.dumps({"status": "fallback", "function": "set_alarm", "parameters": {"time": "07:00", "label": "alarm"}, "source": "keyword_fallback"}, indent=2)
    elif "call" in lower:
        contact = user_input.replace("call", "").strip()
        return json.dumps({"status": "fallback", "function": "call_contact", "parameters": {"contact": contact}, "source": "keyword_fallback"}, indent=2)
    elif "message" in lower or "text" in lower or "send" in lower:
        return json.dumps({"status": "fallback", "function": "send_message", "parameters": {"to": "unknown", "message": ""}, "source": "keyword_fallback"}, indent=2)
    elif "timer" in lower:
        return json.dumps({"status": "fallback", "function": "set_timer", "parameters": {"duration_minutes": 5}, "source": "keyword_fallback"}, indent=2)
    elif "weather" in lower:
        return json.dumps({"status": "fallback", "function": "get_weather", "parameters": {"location": "current"}, "source": "keyword_fallback"}, indent=2)
    
    return json.dumps({"status": "no_match", "message": "Could not parse function call", "available_functions": list(AVAILABLE_FUNCTIONS.keys())}, indent=2)

def list_functions() -> str:
    """List all available functions the agent can call."""
    return json.dumps(AVAILABLE_FUNCTIONS, indent=2)

with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue"), title="dispatchAI Function Calling Agent") as demo:
    gr.Markdown("""
    # 🔧 dispatchAI Function Calling Agent
    
    An on-device agent that parses natural language into structured function calls.
    
    **Model:** [Llama-3.2-1B-FunctionCall-mobile](https://huggingface.co/dispatchAI/Llama-3.2-1B-FunctionCall-mobile) (1B params, Q4 quantized)
    
    Try: "Set an alarm for 7am", "Call mom", "Send a message to John saying I'll be late"
    """)
    
    with gr.Row():
        inp = gr.Textbox(label="User Input", placeholder="Set an alarm for 7am tomorrow", scale=3)
        btn = gr.Button("Parse Function Call", variant="primary", scale=1)
    
    out = gr.Textbox(label="Parsed Function Call (JSON)", lines=12)
    btn.click(fn=parse_function_call, inputs=inp, outputs=out)
    
    with gr.Accordion("Available Functions", open=False):
        fn_btn = gr.Button("List Functions")
        fn_out = gr.Textbox(label="Functions (JSON)", lines=15)
        fn_btn.click(fn=list_functions, outputs=fn_out)
    
    gr.Markdown("---\n🚀 [dispatchAI](https://huggingface.co/dispatchAI) — Small. Mobile. Free. UAE-built.")

demo.launch(mcp_server=True)