File size: 17,853 Bytes
c510317
 
935119a
 
 
 
 
 
3c7e8c9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
935119a
 
 
 
c510317
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3c7e8c9
c510317
935119a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3c7e8c9
935119a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c510317
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
da6d09e
133a045
c510317
da6d09e
 
 
 
 
 
 
4bcb191
da6d09e
 
 
 
 
 
 
 
c510317
 
4bcb191
c510317
 
 
 
 
 
 
 
 
da6d09e
c510317
 
133a045
 
 
 
3c7e8c9
 
c510317
133a045
 
3c7e8c9
da6d09e
 
c510317
133a045
 
 
 
3c7e8c9
da6d09e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c510317
da6d09e
c510317
133a045
 
 
 
da6d09e
 
 
 
 
 
 
 
 
 
 
 
 
 
133a045
c510317
133a045
935119a
da6d09e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c510317
 
4bcb191
c510317
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3c7e8c9
133a045
 
 
 
 
 
 
 
3c7e8c9
c510317
 
3c7e8c9
 
 
 
c510317
3c7e8c9
c510317
 
4bcb191
 
da6d09e
4bcb191
 
 
 
da6d09e
4bcb191
 
 
 
da6d09e
4bcb191
 
da6d09e
9ab99d7
4bcb191
9ab99d7
4bcb191
9ab99d7
4bcb191
 
 
 
 
da6d09e
4bcb191
 
 
 
da6d09e
4bcb191
da6d09e
 
4bcb191
c510317
935119a
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
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
import gradio as gr
import re
import json
import os
import requests

SYSTEM_INSTRUCTIONS = (
    "Your task is to lightly edit system prompts for AI tools to comply with a specific stylistic convention.\n\n"
    "## Workflow\n\n"
    "You will receive the prompt from the user which will contain the system prompt to be edited. Upon receiving it, you must apply the edits and return the edited system prompt back to the user.\n\n"
    "## Editing Instructions\n\n"
    "- The system prompts should be written in the second person, instructing the assistant (use 'you'/'your').\n"
    "- Organize with brief paragraphs and use markdown headers when helpful.\n"
    "- Make the prompt ecosystem-agnostic. For example: if it includes 'you are a custom GPT', replace with 'you are an assistant'.\n"
    "- Make the prompt suitable for any user. If you encounter language specific to one user, generalize it. For example: rewrite 'your purpose is to help Daniel find restaurant recommendations' as 'your purpose is to help the user find restaurant recommendations'.\n\n"
    "## Additional Outputs\n\n"
    "In addition to the edited system prompt, also provide:\n\n"
    "- A name for the assistant encapsulating its main functionality.\n"
    "- A short single-sentence description describing the assistant's function (you don't need to mention that it's an AI tool).\n\n"
    "## Output Format (JSON only)\n\n"
    "Return a JSON object ONLY with the following exact keys and types. Do not include explanations, code fences, or any extra text.\n\n"
    "{\n"
    "  \"reformatted\": string  // the edited system prompt as plain text (no backticks/code fences)\n"
    "  ,\n"
    "  \"name\": string        // assistant name\n"
    "  ,\n"
    "  \"description\": string // single-sentence description\n"
    "}\n"
)


def reformat_system_prompt_offline(input_text):
    """
    Reformats system prompts according to the specified guidelines
    """
    if not input_text.strip():
        return "Please provide a system prompt to reformat.", "", ""
    
    # Apply reformatting logic
    reformatted = input_text.strip()
    
    # Convert first person to second person
    reformatted = re.sub(r'\bI am\b', 'You are', reformatted, flags=re.IGNORECASE)
    reformatted = re.sub(r'\bI will\b', 'You will', reformatted, flags=re.IGNORECASE)
    reformatted = re.sub(r'\bI should\b', 'You should', reformatted, flags=re.IGNORECASE)
    reformatted = re.sub(r'\bI can\b', 'You can', reformatted, flags=re.IGNORECASE)
    reformatted = re.sub(r'\bI must\b', 'You must', reformatted, flags=re.IGNORECASE)
    reformatted = re.sub(r'\bmy task\b', 'your task', reformatted, flags=re.IGNORECASE)
    reformatted = re.sub(r'\bmy role\b', 'your role', reformatted, flags=re.IGNORECASE)
    reformatted = re.sub(r'\bmy purpose\b', 'your purpose', reformatted, flags=re.IGNORECASE)
    reformatted = re.sub(r'\bmy job\b', 'your job', reformatted, flags=re.IGNORECASE)
    reformatted = re.sub(r'\bmy goal\b', 'your goal', reformatted, flags=re.IGNORECASE)
    
    # Make ecosystem-agnostic
    reformatted = re.sub(r'custom GPT', 'assistant', reformatted, flags=re.IGNORECASE)
    reformatted = re.sub(r'ChatGPT', 'assistant', reformatted, flags=re.IGNORECASE)
    reformatted = re.sub(r'GPT-4', 'assistant', reformatted, flags=re.IGNORECASE)
    reformatted = re.sub(r'GPT model', 'assistant', reformatted, flags=re.IGNORECASE)
    reformatted = re.sub(r'OpenAI', 'AI', reformatted, flags=re.IGNORECASE)
    
    # Generalize user-specific language
    reformatted = re.sub(r'help [A-Z][a-z]+ ', 'help the user ', reformatted)
    reformatted = re.sub(r'assist [A-Z][a-z]+ ', 'assist the user ', reformatted)
    reformatted = re.sub(r'for [A-Z][a-z]+\'s', 'for the user\'s', reformatted)
    
    # Generate assistant name (extract main functionality)
    name = extract_assistant_name(reformatted)
    
    # Generate description
    description = extract_description(reformatted)
    
    return reformatted, name, description


def reformat_system_prompt_llm(input_text: str, api_key: str):
    """
    Use an LLM (OpenAI-compatible chat.completions) to reformat the prompt and
    produce name + description. Returns (reformatted_md, name, description).

    - api_key: user-provided OpenAI API key (not stored). Uses gpt-4o-mini.
    """
    if not input_text.strip():
        return "Please provide a system prompt to reformat.", "", ""

    if not api_key:
        # Try environment fallback (Space secret) before giving up
        api_key = os.environ.get("OPENAI_API_KEY", "").strip()
        if not api_key:
            # Fallback to offline if key still missing
            return reformat_system_prompt_offline(input_text)

    base_url = (os.environ.get("OPENAI_BASE_URL") or "https://api.openai.com/v1").rstrip("/")
    url = f"{base_url}/chat/completions"

    messages = [
        {"role": "system", "content": SYSTEM_INSTRUCTIONS},
        {"role": "user", "content": input_text.strip()},
    ]

    payload = {
        "model": "gpt-4o-mini",
        "messages": messages,
        "temperature": 0.2,
        "response_format": {"type": "json_object"},
    }

    headers = {"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"}

    try:
        resp = requests.post(url, headers=headers, data=json.dumps(payload), timeout=30)
        if resp.status_code != 200:
            # Graceful fallback
            return reformat_system_prompt_offline(input_text)
        data = resp.json()
        content = data["choices"][0]["message"]["content"]
        parsed = json.loads(content)
        reformatted = parsed.get("reformatted", "").strip()
        name = parsed.get("name", "AI Assistant").strip()
        description = parsed.get("description", "Provides AI assistance for various tasks.").strip()
        if not reformatted:
            return reformat_system_prompt_offline(input_text)
        return reformatted, name, description
    except Exception:
        # Any parsing/network error → fallback
        return reformat_system_prompt_offline(input_text)


def reformat_system_prompt(input_text, api_key):
    """
    Use LLM if an API key is provided (or available via OPENAI_API_KEY),
    otherwise use the offline heuristic.
    """
    live_key = api_key or os.environ.get("OPENAI_API_KEY", "").strip()
    if live_key:
        return reformat_system_prompt_llm(input_text, live_key)
    else:
        return reformat_system_prompt_offline(input_text)

def extract_assistant_name(text):
    """
    Extract a name for the assistant based on its functionality
    """
    # Look for key phrases that indicate functionality
    if 'travel' in text.lower() and 'quiet' in text.lower():
        return "Quiet Study Finder"
    elif 'system prompt' in text.lower() and 'reformat' in text.lower():
        return "System Prompt Reformatter"
    elif 'code' in text.lower() and 'review' in text.lower():
        return "Code Reviewer"
    elif 'writing' in text.lower() and 'assistant' in text.lower():
        return "Writing Assistant"
    elif 'travel' in text.lower():
        return "Travel Assistant"
    elif 'restaurant' in text.lower():
        return "Restaurant Finder"
    elif 'recipe' in text.lower() or 'cooking' in text.lower():
        return "Recipe Assistant"
    elif 'learning' in text.lower() or 'tutor' in text.lower():
        return "Learning Assistant"
    elif 'email' in text.lower():
        return "Email Assistant"
    elif 'schedule' in text.lower() or 'calendar' in text.lower():
        return "Schedule Assistant"
    else:
        return "AI Assistant"

def extract_description(text):
    """
    Generate a single-sentence description of the assistant's function
    """
    # Try to find purpose statements
    if 'purpose' in text.lower():
        match = re.search(r'purpose is to ([^.]+)', text.lower())
        if match:
            return f"Helps users {match.group(1).strip()}."
    
    if 'help' in text.lower():
        match = re.search(r'help.*?users?.*?(?:to )?([^.]+)', text.lower())
        if match:
            return f"Helps users {match.group(1).strip()}."
    
    if 'assist' in text.lower():
        match = re.search(r'assist.*?users?.*?(?:with|in) ([^.]+)', text.lower())
        if match:
            return f"Assists users with {match.group(1).strip()}."
    
    # Fallback generic description
    return "Provides AI assistance for various tasks."

# Create the Gradio interface
with gr.Blocks(title="System Prompt Reformatter", theme=gr.themes.Soft(primary_hue="indigo", radius_size="lg")) as demo:
    gr.HTML("<h1 style='text-align: center;'>System Prompt Reformatter</h1>")
    gr.HTML("<p style='text-align: center;'>Transform system prompts to second person and make them ecosystem-agnostic</p>")
    # Light monospace styling for text areas
    gr.HTML(
        """
<style>
.monospace textarea { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace; line-height: 1.5; }
</style>
        """
    )
    # API key beneath subtitle within an Advanced accordion
    with gr.Accordion("Advanced", open=False):
        api_key = gr.Textbox(
            label="OpenAI API Key (optional)",
            placeholder="sk-...  Leave blank to use offline reformatter",
            type="password",
            info="Tip: Set Space Secret OPENAI_API_KEY to avoid typing your key. The key is only used for this session."
        )
    
    with gr.Tabs():
        with gr.Tab("App"):
            with gr.Row():
                with gr.Column(scale=1):
                    input_text = gr.Textbox(
                        label="Original System Prompt",
                        placeholder="Paste your system prompt here...",
                        lines=10,
                        max_lines=20
                    )
                    submit_btn = gr.Button("Reformat Prompt", variant="primary")
                    status_info = gr.Markdown(visible=False)
                
                with gr.Column(scale=1):
                    # Output order: Name -> Description -> System Prompt
                    assistant_name_md = gr.Markdown()
                    assistant_name_copy = gr.Textbox(
                        label="Name",
                        interactive=False,
                        show_copy_button=True
                    )
                    description = gr.Textbox(
                        label="Description",
                        interactive=False,
                        show_copy_button=True,
                        elem_classes=["monospace"],
                    )
                    output_text = gr.Textbox(
                        label="Reformatted System Prompt",
                        lines=10,
                        max_lines=20,
                        interactive=False,
                        show_copy_button=True,
                        elem_classes=["monospace"],
                    )
                    export_md = gr.Textbox(
                        label="Export (Markdown)",
                        lines=10,
                        max_lines=20,
                        interactive=False,
                        show_copy_button=True,
                        elem_classes=["monospace"],
                    )
                    export_json = gr.Textbox(
                        label="Export (JSON)",
                        lines=10,
                        max_lines=20,
                        interactive=False,
                        show_copy_button=True,
                        elem_classes=["monospace"],
                    )
                    clear_btn = gr.Button("Clear")
            
            # Wrapper to format outputs for UI ordering and bold name
            def reformat_system_prompt_for_ui(input_text_val, api_key_val):
                reformatted, name, desc = reformat_system_prompt(input_text_val, api_key_val)
                name_md = f"**{name}**" if name else ""
                export_markdown = (
                    f"## NAME\n\n{name}\n\n"
                    f"## DESCRIPTION\n\n{desc}\n\n"
                    f"## SYSTEM PROMPT\n\n{reformatted}"
                )
                export_json_str = json.dumps({
                    "name": name,
                    "description": desc,
                    "reformatted": reformatted,
                }, indent=2)
                # Status: show offline note if no API key provided
                using_key = bool((api_key_val or "").strip() or os.environ.get("OPENAI_API_KEY", "").strip())
                status = gr.update(visible=not using_key, value=("Using offline reformatter (no API key provided)." if not using_key else ""))
                return name_md, name, desc, reformatted, export_markdown, export_json_str, status

            submit_btn.click(
                fn=reformat_system_prompt_for_ui,
                inputs=[input_text, api_key],
                outputs=[assistant_name_md, assistant_name_copy, description, output_text, export_md, export_json, status_info]
            )
            # Submit on Enter
            input_text.submit(
                fn=reformat_system_prompt_for_ui,
                inputs=[input_text, api_key],
                outputs=[assistant_name_md, assistant_name_copy, description, output_text, export_md, export_json, status_info]
            )
            # Clear button resets input and outputs
            def clear_all():
                return "", "", "", "", "", "", "", gr.update(visible=False, value="")
            clear_btn.click(
                fn=clear_all,
                inputs=None,
                outputs=[input_text, assistant_name_md, assistant_name_copy, description, output_text, export_md, export_json, status_info]
            )
            
            # Add quick input example (fills the App input)
            gr.Examples(
                examples=[
                    ["I am a travel assistant. My purpose is to help Daniel find quiet places to study in busy cities. I should provide recommendations for libraries, cafes, and other quiet spaces."]
                ],
                inputs=input_text,
                label="Example"
            )
        
        with gr.Tab("About"):
            gr.Markdown("""
# System Prompt Reformatter

This tool reformats system prompts for AI assistants to follow specific stylistic conventions.

## The System Prompt Used by This Tool
            """)
            gr.Textbox(
                label="System Prompt (copyable)",
                value=SYSTEM_INSTRUCTIONS.strip(),
                lines=20,
                max_lines=40,
                interactive=False,
                show_copy_button=True
            )
            gr.Markdown("""
## How It Works

- Converts first-person language ("I am", "my task") to second-person ("You are", "your task").
- Replaces platform-specific terms like "custom GPT" with generic "assistant".
- Generalizes user-specific references to "the user".
- Generates an assistant name and single-sentence description.

Copy buttons are available for all output fields.
            """)

        with gr.Tab("Example"):
            gr.Markdown("### End-to-End Example (voice-captured input)")
            example_before_text = (
                "I would like to create an AI assistant whose purpose is taking a list of different agents that I'll provide as a document, describing the list of agents and a short summary of the functionality of each. and it should return a document which suggests how they could be deployed into a multi-agent framework with specific patterns of delegation. In other words, it will take the list I provided and, if possible, identify the agent that could serve as the primary orchestrator, sub-orchestrators, and within under them, individual agents. It can present this information as it wishes, so long as the output is in readable markdown. That might take the format of basic diagrams showing the nesting, or it could use narrative or it could do both, but I'll provide the first and then it should return that as an output."
            )
            gr.Textbox(
                label="Before",
                value=example_before_text,
                lines=10,
                max_lines=20,
                interactive=False,
                show_copy_button=True,
                elem_classes=["monospace"],
            )

            example_after_text = (
                "## NAME\n\n"
                "Multi-Agent Framework Advisor\n\n"
                "## DESCRIPTION\n\n"
                "This assistant helps organize and deploy agents within a multi-agent framework.\n\n"
                "## SYSTEM PROMPT\n\n"
                "You are an assistant designed to analyze a list of different agents provided in a document. Your task is to summarize the functionality of each agent and suggest how they could be deployed within a multi-agent framework, including specific patterns of delegation.\n\n"
                "You will identify the agent that could serve as the primary orchestrator, sub-orchestrators, and the individual agents beneath them. Present this information in a readable markdown format, which may include basic diagrams to illustrate the nesting or a narrative description, or both, based on the information you receive."
            )
            gr.Textbox(
                label="After",
                value=example_after_text,
                lines=18,
                max_lines=36,
                interactive=False,
                show_copy_button=True,
                elem_classes=["monospace"],
            )
            load_btn = gr.Button("Load 'Before' into App")
            load_btn.click(lambda: example_before_text, inputs=None, outputs=input_text)

if __name__ == "__main__":
    demo.launch()