forked from BasedHardware/omi
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathapp_generator.py
More file actions
259 lines (209 loc) · 10.7 KB
/
Copy pathapp_generator.py
File metadata and controls
259 lines (209 loc) · 10.7 KB
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
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
"""
AI App Generator utility
Generates app configuration from a natural language prompt using LLM
"""
import json
import re
import base64
import httpx
from typing import Any, Dict, Optional, cast
from pydantic import BaseModel
from langchain_core.messages import SystemMessage, HumanMessage
from utils.executors import llm_executor, run_blocking
from utils.llm.clients import get_llm
from utils.llm.gateway_client import generate_image_via_gateway
def _content_str(response: Any) -> str:
"""Extract string content from an LLM response (langchain content is typed as a union)."""
return cast(str, response.content)
# App categories available in the system
APP_CATEGORIES = [
{'title': 'Conversation Analysis', 'id': 'conversation-analysis'},
{'title': 'Personality Clone', 'id': 'personality-emulation'},
{'title': 'Health', 'id': 'health-and-wellness'},
{'title': 'Education', 'id': 'education-and-learning'},
{'title': 'Communication', 'id': 'communication-improvement'},
{'title': 'Emotional Support', 'id': 'emotional-and-mental-support'},
{'title': 'Productivity', 'id': 'productivity-and-organization'},
{'title': 'Entertainment', 'id': 'entertainment-and-fun'},
{'title': 'Financial', 'id': 'financial'},
{'title': 'Travel', 'id': 'travel-and-exploration'},
{'title': 'Safety', 'id': 'safety-and-security'},
{'title': 'Shopping', 'id': 'shopping-and-commerce'},
{'title': 'Social', 'id': 'social-and-relationships'},
{'title': 'News', 'id': 'news-and-information'},
{'title': 'Utilities', 'id': 'utilities-and-tools'},
{'title': 'Other', 'id': 'other'},
]
class GeneratedAppData(BaseModel):
"""Structure for AI-generated app data"""
name: str
description: str
category: str
capabilities: list[str] # 'chat' or 'memories' or both
chat_prompt: Optional[str] = None
memory_prompt: Optional[str] = None
SYSTEM_PROMPT = """You are an expert app designer for Omi, an AI-powered wearable device that records conversations and provides intelligent insights.
Your task is to design an app based on the user's description. Apps in Omi can have two main capabilities:
1. **Chat Apps** (capability: "chat"): These apps allow users to chat with an AI persona or assistant. They require a `chat_prompt` that defines the personality, expertise, and behavior of the chat assistant. Chat apps are great for:
- AI personas (like cloning a celebrity or expert)
- Specialized assistants (coaches, tutors, advisors)
- Interactive conversations about specific topics
2. **Conversation/Memory Apps** (capability: "memories"): These apps analyze user conversations and generate insights or summaries. They require a `memory_prompt` that tells the AI what to extract or analyze from conversations. Memory apps are great for:
- Summarizing conversations into specific formats
- Extracting action items, decisions, or key points
- Organizing information into structures (like mind maps, bullet points)
- Tracking specific topics over time
An app can have BOTH capabilities if it makes sense (e.g., an app that analyzes conversations AND allows chatting about the analysis).
Available categories (pick the most appropriate one):
{categories}
IMPORTANT GUIDELINES:
- Write prompts that are detailed and specific
- For chat_prompt: Define the persona's personality, expertise, speaking style, and what they should help with
- For memory_prompt: Be specific about what information to extract, how to format it, and what insights to provide
- Choose capabilities based on what the user is asking for:
- If they want to "talk to" or "chat with" something → include "chat"
- If they want to "analyze", "summarize", "organize", or "extract" from conversations → include "memories"
- If both make sense, include both
Return your response as a valid JSON object with this exact structure:
{{
"name": "App Name (short, catchy, max 30 chars)",
"description": "A compelling description of what the app does (50-150 words)",
"category": "category-id from the list above",
"capabilities": ["chat", "memories"], // include relevant ones
"chat_prompt": "Detailed prompt for chat persona (only if chat capability is included)",
"memory_prompt": "Detailed prompt for conversation analysis (only if memories capability is included)"
}}
Only include chat_prompt if "chat" is in capabilities.
Only include memory_prompt if "memories" is in capabilities."""
async def generate_app_from_prompt(user_prompt: str) -> GeneratedAppData:
"""
Generate app configuration from a natural language prompt using LLM.
Args:
user_prompt: The user's description of what kind of app they want
Returns:
GeneratedAppData with all the app configuration
"""
categories_str = "\n".join([f"- {cat['title']} (id: {cat['id']})" for cat in APP_CATEGORIES])
system_message = SYSTEM_PROMPT.format(categories=categories_str)
messages = [
SystemMessage(content=system_message),
HumanMessage(content=f"Create an app based on this description:\n\n{user_prompt}"),
]
response = await get_llm('app_generator').ainvoke(messages)
# Parse the JSON response
content = _content_str(response).strip()
# Handle potential markdown code blocks
if content.startswith("```"):
# Remove markdown code block markers
lines = content.split("\n")
content = "\n".join(lines[1:-1] if lines[-1] == "```" else lines[1:])
try:
app_data = json.loads(content)
except json.JSONDecodeError:
# Try to extract JSON from the response
json_match = re.search(r'\{[\s\S]*\}', content)
if json_match:
app_data = json.loads(json_match.group())
else:
raise ValueError("Failed to parse LLM response as JSON")
# Coerce present-but-null LLM fields to their defaults. app_data.get(k, default) only applies the
# default when k is ABSENT, so a null value ({"name": null}, {"capabilities": null}) slips through
# and then crashes here (None[:50], "chat" in None) or fails GeneratedAppData validation - all
# outside the JSON try/except above, so an uncaught 500 on app generation.
caps = app_data.get("capabilities") or ["chat"]
return GeneratedAppData(
name=(app_data.get("name") or "My App")[:50],
description=app_data.get("description") or "An AI-powered app",
category=app_data.get("category") or "other",
capabilities=caps,
chat_prompt=app_data.get("chat_prompt") if "chat" in caps else None,
memory_prompt=app_data.get("memory_prompt") if "memories" in caps else None,
)
async def generate_app_icon(app_name: str, app_description: str, category: str) -> bytes:
"""
Generate an app icon through the internal LLM gateway.
Args:
app_name: Name of the app
app_description: Description of the app
category: Category of the app
Returns:
PNG image bytes of the generated icon
"""
# Create a prompt for icon generation
icon_prompt = f"""Create a modern, minimal app icon for an AI app called "{app_name}".
App description: {app_description}
Category: {category}
Design requirements:
- Clean, minimal design with a single focal element
- Modern gradient or solid color background
- Simple geometric shapes or abstract representation
- Professional and polished look
- Should work well at small sizes (app icon)
- No text or letters in the icon
- Vibrant but not overwhelming colors
- Style: Similar to modern iOS/Android app icons"""
# gpt-image-1 with an explicit size/quality pair the gateway rate card prices
# (openai.gpt-image-1.medium.1024x1024). The retired dall-e-3 `standard` quality and the
# `response_format` parameter are both rejected by the images API now, and it always
# returns base64 image data.
response = await run_blocking(
llm_executor,
generate_image_via_gateway,
model="gpt-image-1",
prompt=icon_prompt,
size="1024x1024",
quality="medium",
n=1,
)
# Get the base64 image data and decode it
image_data = cast("list[dict[str, Any]]", response["data"])[0]["b64_json"]
return base64.b64decode(cast(str, image_data))
async def download_image_from_url(url: str) -> bytes:
"""Download image from URL and return bytes."""
async with httpx.AsyncClient() as client:
response = await client.get(url)
response.raise_for_status()
return response.content
def generate_description(app_name: str, description: str) -> str:
"""
Generate an improved app description from a basic one.
Used by the app submission flow.
"""
prompt = f"""
You are an AI assistant specializing in crafting detailed and engaging descriptions for apps.
You will be provided with the app's name and a brief description which might not be that good. Your task is to expand on the given information, creating a captivating and detailed app description that highlights the app's features, functionality, and benefits.
The description should be concise, professional, and not more than 40 words, ensuring clarity and appeal. Respond with only the description, tailored to the app's concept and purpose.
App Name: {app_name}
Description: {description}
"""
prompt = prompt.replace(' ', '').strip()
return _content_str(get_llm('app_integration').invoke(prompt))
def generate_description_and_emoji(app_name: str, prompt: str) -> Dict[str, str]:
"""
Generate an app description and a representative emoji for the app.
Used by the quick template creator feature.
"""
system_prompt = """You are an AI assistant that creates app descriptions and selects representative emojis.
Given an app name and what it should do, respond with a JSON object containing:
1. "description": A concise, engaging description (max 40 words) highlighting what the app does
2. "emoji": A single emoji that best represents the app's purpose
Respond ONLY with the JSON object, no other text."""
user_prompt = f"""App Name: {app_name}
What it does: {prompt}"""
response = get_llm('app_integration').invoke(
[SystemMessage(content=system_prompt), HumanMessage(content=user_prompt)]
)
content = _content_str(response).strip()
# Parse JSON from response
if content.startswith("```"):
lines = content.split("\n")
content = "\n".join(lines[1:-1])
try:
result = json.loads(content)
return {
"description": result.get("description", f"A custom app that {prompt}"),
"emoji": result.get("emoji", "✨"),
}
except (json.JSONDecodeError, KeyError):
# Fallback if JSON parsing fails
return {"description": f"A custom app that {prompt}", "emoji": "✨"}