Spaces:
Sleeping
Sleeping
Create app.py
Browse files
app.py
ADDED
@@ -0,0 +1,179 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# MCP-Powered Culinary Voice Assistant
|
2 |
+
# Hugging Face Space Implementation
|
3 |
+
|
4 |
+
import gradio as gr
|
5 |
+
import numpy as np
|
6 |
+
from mcp.server.fastmcp import FastMCP
|
7 |
+
from agents import Agent, trace
|
8 |
+
from agents.mcp import MCPServerSse, MCPServerStdio
|
9 |
+
from agents.voice import VoicePipeline, TTSModelSettings, AudioInput
|
10 |
+
import sqlite3
|
11 |
+
import json
|
12 |
+
import requests
|
13 |
+
from PIL import Image
|
14 |
+
import io
|
15 |
+
|
16 |
+
# ------ Custom MCP Cooking Tools Server ------
|
17 |
+
mcp = FastMCP("Culinary Tools Server")
|
18 |
+
|
19 |
+
@mcp.tool()
|
20 |
+
def get_recipe_by_ingredients(ingredients: list) -> dict:
|
21 |
+
"""Find recipes based on available ingredients"""
|
22 |
+
print(f"[Culinary Server] Finding recipes with: {', '.join(ingredients)}")
|
23 |
+
# In a real implementation, this would call a recipe API
|
24 |
+
return {
|
25 |
+
"recipes": [
|
26 |
+
{"name": "Vegetable Stir Fry", "time": 20, "difficulty": "Easy"},
|
27 |
+
{"name": "Pasta Primavera", "time": 30, "difficulty": "Medium"}
|
28 |
+
]
|
29 |
+
}
|
30 |
+
|
31 |
+
@mcp.tool()
|
32 |
+
def get_recipe_image(recipe_name: str) -> str:
|
33 |
+
"""Generate an image of the finished recipe"""
|
34 |
+
print(f"[Culinary Server] Generating image for: {recipe_name}")
|
35 |
+
# This would call DALL-E or Stable Diffusion in production
|
36 |
+
return "https://example.com/recipe-image.jpg"
|
37 |
+
|
38 |
+
@mcp.tool()
|
39 |
+
def convert_measurements(amount: float, from_unit: str, to_unit: str) -> dict:
|
40 |
+
"""Convert cooking measurements between units"""
|
41 |
+
print(f"[Culinary Server] Converting {amount} {from_unit} to {to_unit}")
|
42 |
+
# Simple conversion logic - real implementation would handle more units
|
43 |
+
conversions = {
|
44 |
+
("tbsp", "tsp"): lambda x: x * 3,
|
45 |
+
("cups", "ml"): lambda x: x * 240,
|
46 |
+
("oz", "g"): lambda x: x * 28.35
|
47 |
+
}
|
48 |
+
conversion_key = (from_unit.lower(), to_unit.lower())
|
49 |
+
if conversion_key in conversions:
|
50 |
+
return {"result": conversions[conversion_key](amount), "unit": to_unit}
|
51 |
+
return {"error": "Conversion not supported"}
|
52 |
+
|
53 |
+
# ------ Recipe Database (SQLite) ------
|
54 |
+
def init_recipe_db():
|
55 |
+
conn = sqlite3.connect('file:recipes.db?mode=memory&cache=shared', uri=True)
|
56 |
+
c = conn.cursor()
|
57 |
+
c.execute('''CREATE TABLE IF NOT EXISTS recipes
|
58 |
+
(id INTEGER PRIMARY KEY, name TEXT, ingredients TEXT, instructions TEXT, prep_time INT)''')
|
59 |
+
|
60 |
+
# Sample recipes
|
61 |
+
recipes = [
|
62 |
+
("Classic Pancakes", "['flour', 'eggs', 'milk', 'baking powder']",
|
63 |
+
"1. Mix dry ingredients\n2. Add wet ingredients\n3. Cook on griddle", 15),
|
64 |
+
("Tomato Soup", "['tomatoes', 'onion', 'garlic', 'vegetable stock']",
|
65 |
+
"1. Sauté onions\n2. Add tomatoes\n3. Simmer and blend", 30)
|
66 |
+
]
|
67 |
+
|
68 |
+
c.executemany("INSERT INTO recipes (name, ingredients, instructions, prep_time) VALUES (?,?,?,?)", recipes)
|
69 |
+
conn.commit()
|
70 |
+
return conn
|
71 |
+
|
72 |
+
# ------ Voice Assistant Setup ------
|
73 |
+
def create_culinary_agent(mcp_servers):
|
74 |
+
"""Create the culinary assistant agent"""
|
75 |
+
culinary_agent = Agent(
|
76 |
+
name="ChefAssistant",
|
77 |
+
instructions="""
|
78 |
+
You are a professional chef assistant. Help users with cooking tasks:
|
79 |
+
1. Use get_recipe_by_ingredients when users have specific ingredients
|
80 |
+
2. Use get_recipe_details for known recipes
|
81 |
+
3. Use convert_measurements for unit conversions
|
82 |
+
4. Use get_recipe_image when the user asks to see a dish
|
83 |
+
5. Keep responses concise and practical for kitchen use
|
84 |
+
6. Use a warm, encouraging tone suitable for cooking
|
85 |
+
""",
|
86 |
+
mcp_servers=mcp_servers,
|
87 |
+
model="gpt-4.1-mini",
|
88 |
+
)
|
89 |
+
return culinary_agent
|
90 |
+
|
91 |
+
# ------ Gradio Interface ------
|
92 |
+
def process_voice_command(audio, state):
|
93 |
+
"""Process voice command through the agent system"""
|
94 |
+
sr, audio_data = audio
|
95 |
+
audio_array = (audio_data / np.iinfo(audio_data.dtype).max).astype(np.float32)
|
96 |
+
|
97 |
+
# Initialize on first run
|
98 |
+
if state is None:
|
99 |
+
init_recipe_db()
|
100 |
+
state = {
|
101 |
+
"mcp_servers": [],
|
102 |
+
"agent": None,
|
103 |
+
"voice_pipeline": VoicePipeline(
|
104 |
+
workflow=None,
|
105 |
+
config=VoicePipelineConfig(
|
106 |
+
tts_settings=TTSModelSettings(
|
107 |
+
instructions="Warm, encouraging chef voice"
|
108 |
+
)
|
109 |
+
)
|
110 |
+
)
|
111 |
+
}
|
112 |
+
|
113 |
+
# Start MCP servers
|
114 |
+
with MCPServerSse(
|
115 |
+
name="Culinary Tools",
|
116 |
+
params={"url": "http://localhost:8000/sse"},
|
117 |
+
client_session_timeout_seconds=15,
|
118 |
+
) as culinary_server:
|
119 |
+
with MCPServerStdio(
|
120 |
+
params={"command": "uvx", "args": ["mcp-server-sqlite", "--db-path", "file:recipes.db?mode=memory&cache=shared"]},
|
121 |
+
) as db_server:
|
122 |
+
state["mcp_servers"] = [culinary_server, db_server]
|
123 |
+
state["agent"] = create_culinary_agent(state["mcp_servers"])
|
124 |
+
|
125 |
+
# Process audio through agent
|
126 |
+
audio_input = AudioInput(buffer=audio_array, sample_rate=sr)
|
127 |
+
response = state["voice_pipeline"].run(state["agent"], audio_input)
|
128 |
+
|
129 |
+
# For demo purposes, return mock response
|
130 |
+
return (
|
131 |
+
"https://example.com/response.wav",
|
132 |
+
"I found 3 recipes for your ingredients! Vegetable Stir Fry (20 mins) and Pasta Primavera (30 mins).",
|
133 |
+
"https://example.com/stir-fry.jpg",
|
134 |
+
state
|
135 |
+
)
|
136 |
+
|
137 |
+
# ------ Hugging Face Space UI ------
|
138 |
+
with gr.Blocks(title="MCP Culinary Voice Assistant") as demo:
|
139 |
+
state = gr.State(value=None)
|
140 |
+
|
141 |
+
with gr.Row():
|
142 |
+
gr.Markdown("# 🧑🍳 MCP-Powered Culinary Voice Assistant")
|
143 |
+
|
144 |
+
with gr.Row():
|
145 |
+
audio_input = gr.Audio(source="microphone", type="numpy", label="Speak to Chef Assistant")
|
146 |
+
audio_output = gr.Audio(label="Assistant Response", interactive=False)
|
147 |
+
|
148 |
+
with gr.Row():
|
149 |
+
text_output = gr.Textbox(label="Transcription", interactive=False)
|
150 |
+
image_output = gr.Image(label="Recipe Image", interactive=False)
|
151 |
+
|
152 |
+
with gr.Row():
|
153 |
+
submit_btn = gr.Button("Process Command", variant="primary")
|
154 |
+
|
155 |
+
submit_btn.click(
|
156 |
+
fn=process_voice_command,
|
157 |
+
inputs=[audio_input, state],
|
158 |
+
outputs=[audio_output, text_output, image_output, state]
|
159 |
+
)
|
160 |
+
|
161 |
+
gr.Examples(
|
162 |
+
examples=[
|
163 |
+
["What can I make with eggs and flour?", "", ""],
|
164 |
+
["Show me how tomato soup looks", "", ""],
|
165 |
+
["Convert 2 cups to milliliters", "", ""]
|
166 |
+
],
|
167 |
+
inputs=[text_output],
|
168 |
+
label="Example Queries"
|
169 |
+
)
|
170 |
+
|
171 |
+
if __name__ == "__main__":
|
172 |
+
# Start MCP server in background thread
|
173 |
+
import threading
|
174 |
+
server_thread = threading.Thread(target=mcp.run, kwargs={"transport": "sse"})
|
175 |
+
server_thread.daemon = True
|
176 |
+
server_thread.start()
|
177 |
+
|
178 |
+
# Launch Gradio interface
|
179 |
+
demo.launch(server_name="0.0.0.0", server_port=7860)
|