eusholli commited on
Commit
f842afa
Β·
verified Β·
1 Parent(s): bc1686b

Upload folder using huggingface_hub

Browse files
Files changed (6) hide show
  1. .gitignore +213 -0
  2. LICENSE +21 -0
  3. README.md +318 -7
  4. director_bake_off.py +506 -0
  5. gradio_interface.py +421 -0
  6. requirements.txt +4 -0
.gitignore ADDED
@@ -0,0 +1,213 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ share/python-wheels/
24
+ *.egg-info/
25
+ .installed.cfg
26
+ *.egg
27
+ MANIFEST
28
+
29
+ # PyInstaller
30
+ # Usually these files are written by a python script from a template
31
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
32
+ *.manifest
33
+ *.spec
34
+
35
+ # Installer logs
36
+ pip-log.txt
37
+ pip-delete-this-directory.txt
38
+
39
+ # Unit test / coverage reports
40
+ htmlcov/
41
+ .tox/
42
+ .nox/
43
+ .coverage
44
+ .coverage.*
45
+ .cache
46
+ nosetests.xml
47
+ coverage.xml
48
+ *.cover
49
+ *.py,cover
50
+ .hypothesis/
51
+ .pytest_cache/
52
+ cover/
53
+
54
+ # Translations
55
+ *.mo
56
+ *.pot
57
+
58
+ # Django stuff:
59
+ *.log
60
+ local_settings.py
61
+ db.sqlite3
62
+ db.sqlite3-journal
63
+
64
+ # Flask stuff:
65
+ instance/
66
+ .webassets-cache
67
+
68
+ # Scrapy stuff:
69
+ .scrapy
70
+
71
+ # Sphinx documentation
72
+ docs/_build/
73
+
74
+ # PyBuilder
75
+ .pybuilder/
76
+ target/
77
+
78
+ # Jupyter Notebook
79
+ .ipynb_checkpoints
80
+
81
+ # IPython
82
+ profile_default/
83
+ ipython_config.py
84
+
85
+ # pyenv
86
+ # For a library or package, you might want to ignore these files since the code is
87
+ # intended to run in multiple environments; otherwise, check them in:
88
+ # .python-version
89
+
90
+ # pipenv
91
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
93
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
94
+ # install all needed dependencies.
95
+ #Pipfile.lock
96
+
97
+ # poetry
98
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
99
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
100
+ # commonly ignored for libraries.
101
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
102
+ #poetry.lock
103
+
104
+ # pdm
105
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
106
+ #pdm.lock
107
+ # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
108
+ # in version control.
109
+ # https://pdm.fming.dev/#use-with-ide
110
+ .pdm.toml
111
+
112
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
113
+ __pypackages__/
114
+
115
+ # Celery stuff
116
+ celerybeat-schedule
117
+ celerybeat.pid
118
+
119
+ # SageMath parsed files
120
+ *.sage.py
121
+
122
+ # Environments
123
+ .env
124
+ .venv
125
+ env/
126
+ venv/
127
+ ENV/
128
+ env.bak/
129
+ venv.bak/
130
+
131
+ # Spyder project settings
132
+ .spyderproject
133
+ .spyproject
134
+
135
+ # Rope project settings
136
+ .ropeproject
137
+
138
+ # mkdocs documentation
139
+ /site
140
+
141
+ # mypy
142
+ .mypy_cache/
143
+ .dmypy.json
144
+ dmypy.json
145
+
146
+ # Pyre type checker
147
+ .pyre/
148
+
149
+ # pytype static type analyzer
150
+ .pytype/
151
+
152
+ # Cython debug symbols
153
+ cython_debug/
154
+
155
+ # PyCharm
156
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
157
+ # be added to the global gitignore or merged into this project gitignore. For a PyCharm
158
+ # project, it is recommended to ignore the entire .idea directory.
159
+ .idea/
160
+
161
+ # VS Code
162
+ .vscode/
163
+
164
+ # macOS
165
+ .DS_Store
166
+ .AppleDouble
167
+ .LSOverride
168
+
169
+ # Windows
170
+ Thumbs.db
171
+ ehthumbs.db
172
+ Desktop.ini
173
+
174
+ # Linux
175
+ *~
176
+
177
+ # Temporary files
178
+ *.tmp
179
+ *.temp
180
+ *.swp
181
+ *.swo
182
+
183
+ # Logs
184
+ *.log
185
+
186
+ # Database files
187
+ *.db
188
+ *.sqlite
189
+ *.sqlite3
190
+
191
+ # Model files (often large)
192
+ *.pkl
193
+ *.pickle
194
+ *.joblib
195
+ *.h5
196
+ *.hdf5
197
+ *.pt
198
+ *.pth
199
+ *.onnx
200
+
201
+ # Data files
202
+ data/
203
+ datasets/
204
+ *.csv
205
+ *.json
206
+ *.xml
207
+ *.parquet
208
+
209
+ # Output directories
210
+ output/
211
+ outputs/
212
+ results/
213
+ checkpoints/
LICENSE ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MIT License
2
+
3
+ Copyright (c) 2025
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
README.md CHANGED
@@ -1,12 +1,323 @@
1
  ---
2
- title: Dspy Example
3
- emoji: πŸ‘
4
- colorFrom: yellow
5
- colorTo: red
6
  sdk: gradio
7
  sdk_version: 5.38.2
8
- app_file: app.py
9
- pinned: false
10
  ---
 
11
 
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: dspy-example
3
+ app_file: gradio_interface.py
 
 
4
  sdk: gradio
5
  sdk_version: 5.38.2
 
 
6
  ---
7
+ # 🎬 DSPy Director Bake-Off: A Beginner's Guide to DSPy Programming
8
 
9
+ Welcome to the **DSPy Director Bake-Off**! This project demonstrates core DSPy concepts through a fun, practical example: comparing how different movie directors would approach filming the same video idea.
10
+
11
+ ## 🎯 What You'll Learn
12
+
13
+ This tutorial teaches you the fundamental concepts of **DSPy** (Declarative Self-improving Python) through hands-on examples:
14
+
15
+ 1. **Signatures** - Define input/output interfaces for LLM tasks
16
+ 2. **Modules** - Combine multiple signatures into complex workflows
17
+ 3. **Structured Output** - Use Pydantic models for reliable data extraction
18
+ 4. **Async Processing** - Handle multiple LLM calls efficiently
19
+ 5. **Chain of Thought** - Enable reasoning for complex decisions
20
+
21
+ ## πŸš€ Quick Start
22
+
23
+ ### Prerequisites
24
+
25
+ - Python 3.8+
26
+ - An OpenRouter API key (free tier available)
27
+
28
+ ### Installation
29
+
30
+ 1. **Clone or download this project**
31
+ 2. **Install dependencies:**
32
+ ```bash
33
+ pip install -r requirements.txt
34
+ ```
35
+
36
+ 3. **Set up your API key:**
37
+ Create a `.env` file in the project directory:
38
+ ```
39
+ OPENROUTER_API_KEY=your_api_key_here
40
+ ```
41
+
42
+ 4. **Run the demo:**
43
+ ```bash
44
+ python director_bake_off.py
45
+ ```
46
+
47
+ 5. **Try the web interface:**
48
+ ```bash
49
+ python gradio_interface.py
50
+ ```
51
+ Then open http://localhost:7860 in your browser.
52
+
53
+ ## 🎭 How It Works
54
+
55
+ The Director Bake-Off follows this workflow:
56
+
57
+ 1. **User Input**: You provide a video idea and list of directors
58
+ 2. **AI Suggestion**: DSPy suggests an additional director perfect for your idea
59
+ 3. **Parallel Generation**: Each director's unique interpretation is generated simultaneously
60
+ 4. **Intelligent Ranking**: An AI judge ranks all interpretations and explains the reasoning
61
+ 5. **Beautiful Results**: View the winner and detailed breakdowns
62
+
63
+ ## πŸ“š Understanding the Code Structure
64
+
65
+ ### πŸ—οΈ Project Architecture
66
+
67
+ ```
68
+ director_bake_off.py # Main DSPy implementation (heavily commented)
69
+ gradio_interface.py # Beautiful web interface
70
+ requirements.txt # Python dependencies
71
+ .env # Your API keys (create this)
72
+ README.md # This guide
73
+ ```
74
+
75
+ ### πŸ” Code Walkthrough
76
+
77
+ The `director_bake_off.py` file is organized into clear sections:
78
+
79
+ #### **Section 1: LLM Setup**
80
+ ```python
81
+ def setup_dspy_provider():
82
+ # Configure DSPy with your chosen LLM provider
83
+ # Supports OpenAI, Anthropic, OpenRouter, and more
84
+ ```
85
+
86
+ #### **Section 2: Data Structures**
87
+ ```python
88
+ class DirectorCut(BaseModel):
89
+ # Pydantic model defining the structure of cinematic prompts
90
+ # Ensures consistent, validated output from the LLM
91
+ ```
92
+
93
+ #### **Section 3: DSPy Signatures**
94
+ ```python
95
+ class FindDirector(dspy.Signature):
96
+ # Defines what inputs the LLM expects and what outputs it should produce
97
+ # Think of these as "contracts" between your code and the LLM
98
+ ```
99
+
100
+ #### **Section 4: DSPy Module**
101
+ ```python
102
+ class DirectorBakeOff(dspy.Module):
103
+ # Combines multiple signatures into a complete workflow
104
+ # Orchestrates the entire director comparison process
105
+ ```
106
+
107
+ #### **Section 5: Main Functions**
108
+ ```python
109
+ def run_bake_off(video_idea, directors):
110
+ # Easy-to-use interface that handles everything
111
+ # This is what external code calls to use our system
112
+ ```
113
+
114
+ ## πŸŽ“ DSPy Concepts Explained
115
+
116
+ ### What is DSPy?
117
+
118
+ **DSPy** is a framework for programming with Large Language Models (LLMs). Instead of writing prompts as strings, you define structured interfaces that make your LLM applications more reliable, maintainable, and powerful.
119
+
120
+ ### πŸ”₯ Key Concepts
121
+
122
+ #### 1. **Signatures** - The Heart of DSPy
123
+
124
+ Signatures define the interface between your code and the LLM:
125
+
126
+ ```python
127
+ class GenerateDirectorCut(dspy.Signature):
128
+ """Transform a video idea into a cinematic prompt in a director's style."""
129
+
130
+ # What goes IN to the LLM
131
+ video_idea = dspy.InputField(desc="A simple video concept")
132
+ director = dspy.InputField(desc="The director's name")
133
+
134
+ # What comes OUT of the LLM
135
+ director_cut: DirectorCut = dspy.OutputField(desc="Structured cinematic prompt")
136
+ ```
137
+
138
+ **Why this is powerful:**
139
+ - Clear contracts between code and LLM
140
+ - Automatic prompt generation
141
+ - Type safety and validation
142
+ - Reusable across different models
143
+
144
+ #### 2. **Structured Output with Pydantic**
145
+
146
+ Instead of parsing messy text, get structured data:
147
+
148
+ ```python
149
+ class DirectorCut(BaseModel):
150
+ director: str
151
+ subject_description: str
152
+ action_description: str
153
+ setting_description: str
154
+ # ... more fields
155
+
156
+ def assemble_prompt(self) -> str:
157
+ # Combine all parts into a complete prompt
158
+ return ", ".join([self.subject_description, self.action_description, ...])
159
+ ```
160
+
161
+ **Benefits:**
162
+ - Guaranteed data format
163
+ - Automatic validation
164
+ - Easy to work with in code
165
+ - No more regex parsing!
166
+
167
+ #### 3. **Modules** - Complex Workflows
168
+
169
+ Modules combine multiple signatures into sophisticated workflows:
170
+
171
+ ```python
172
+ class DirectorBakeOff(dspy.Module):
173
+ def __init__(self):
174
+ self.findDirector = dspy.Predict(FindDirector)
175
+ self.genDirectorCut = dspy.Predict(GenerateDirectorCut)
176
+ self.directorJudge = dspy.ChainOfThought(DirectorJudge)
177
+
178
+ async def aforward(self, video_idea, directors):
179
+ # Orchestrate multiple LLM calls
180
+ # 1. Find additional director
181
+ # 2. Generate interpretations (in parallel!)
182
+ # 3. Judge and rank results
183
+ ```
184
+
185
+ #### 4. **Different Predictor Types**
186
+
187
+ - **`dspy.Predict`**: Basic, fast predictions
188
+ - **`dspy.ChainOfThought`**: Enables step-by-step reasoning
189
+ - **`dspy.ReAct`**: Combines reasoning with actions
190
+ - **`dspy.ProgramOfThought`**: For mathematical/logical problems
191
+
192
+ #### 5. **Async Processing**
193
+
194
+ Handle multiple LLM calls efficiently:
195
+
196
+ ```python
197
+ # Instead of calling one by one (slow):
198
+ for director in directors:
199
+ result = self.genDirectorCut(video_idea=idea, director=director)
200
+
201
+ # Call them all at once (fast!):
202
+ results = await asyncio.gather(
203
+ *[self.genDirectorCut.acall(video_idea=idea, director=d) for d in directors]
204
+ )
205
+ ```
206
+
207
+ ## πŸ› οΈ Building Your Own DSPy Applications
208
+
209
+ ### Step 1: Define Your Data Structure
210
+
211
+ Start with a Pydantic model for your expected output:
212
+
213
+ ```python
214
+ class MyOutput(BaseModel):
215
+ field1: str = Field(..., description="What this field should contain")
216
+ field2: int = Field(..., description="A number representing...")
217
+ # Add more fields as needed
218
+ ```
219
+
220
+ ### Step 2: Create Signatures
221
+
222
+ Define the interface for each LLM task:
223
+
224
+ ```python
225
+ class MyTask(dspy.Signature):
226
+ """Clear description of what this task should do."""
227
+
228
+ # Inputs
229
+ user_input = dspy.InputField(desc="What the user provides")
230
+
231
+ # Outputs
232
+ result: MyOutput = dspy.OutputField(desc="The structured result")
233
+ ```
234
+
235
+ ### Step 3: Build a Module
236
+
237
+ Combine signatures into a workflow:
238
+
239
+ ```python
240
+ class MyModule(dspy.Module):
241
+ def __init__(self):
242
+ self.task = dspy.Predict(MyTask)
243
+
244
+ def forward(self, user_input):
245
+ return self.task(user_input=user_input)
246
+ ```
247
+
248
+ ### Step 4: Configure and Run
249
+
250
+ ```python
251
+ # Configure DSPy with your LLM
252
+ dspy.configure(lm=dspy.LM("openrouter/model-name", api_key="your-key"))
253
+
254
+ # Use your module
255
+ module = MyModule()
256
+ result = module.forward("user input here")
257
+ print(result.result.field1) # Access structured output
258
+ ```
259
+
260
+ ## 🎨 Customization Ideas
261
+
262
+ Try modifying the Director Bake-Off to explore DSPy further:
263
+
264
+ ### 🎬 **Different Creative Domains**
265
+ - **Music Producer Bake-Off**: Compare how different producers would approach a song
266
+ - **Chef Bake-Off**: See how famous chefs would prepare the same dish
267
+ - **Architect Bake-Off**: Compare building designs for the same space
268
+
269
+ ### πŸ”§ **Technical Enhancements**
270
+ - **Add More Signatures**: Include budget estimation, casting suggestions
271
+ - **Different Predictors**: Try `dspy.ReAct` for more complex reasoning
272
+ - **Optimization**: Use DSPy's optimization features to improve performance
273
+ - **Multiple Models**: Compare results from different LLMs
274
+
275
+ ### 🎯 **New Applications**
276
+ - **Content Planning**: Generate social media strategies
277
+ - **Product Design**: Compare design approaches for products
278
+ - **Educational Content**: Create lesson plans in different teaching styles
279
+
280
+ ## πŸ”— Useful Resources
281
+
282
+ ### DSPy Documentation
283
+ - [Official DSPy Documentation](https://dspy-docs.vercel.app/)
284
+ - [DSPy GitHub Repository](https://github.com/stanfordnlp/dspy)
285
+ - [DSPy Paper](https://arxiv.org/abs/2310.03714)
286
+
287
+ ### LLM Providers
288
+ - [OpenRouter](https://openrouter.ai/) - Access to many models through one API
289
+ - [OpenAI](https://platform.openai.com/) - GPT models
290
+ - [Anthropic](https://www.anthropic.com/) - Claude models
291
+
292
+ ### Learning More
293
+ - [Pydantic Documentation](https://docs.pydantic.dev/) - For data validation
294
+ - [Gradio Documentation](https://gradio.app/docs/) - For building web interfaces
295
+ - [Python Asyncio](https://docs.python.org/3/library/asyncio.html) - For async programming
296
+
297
+ ## 🀝 Contributing
298
+
299
+ Found this helpful? Here are ways to contribute:
300
+
301
+ 1. **Try it out** and share your results
302
+ 2. **Create variations** for different domains
303
+ 3. **Improve the documentation** with your learnings
304
+ 4. **Share examples** of your own DSPy applications
305
+
306
+ ## πŸ“ License
307
+
308
+ This project is open source and available under the MIT License.
309
+
310
+ ## πŸŽ‰ What's Next?
311
+
312
+ After mastering this example, you'll be ready to:
313
+
314
+ - Build production DSPy applications
315
+ - Optimize prompts automatically with DSPy's built-in tools
316
+ - Create complex multi-step reasoning systems
317
+ - Integrate DSPy into larger applications
318
+
319
+ **Happy coding with DSPy!** πŸš€
320
+
321
+ ---
322
+
323
+ *This tutorial was created to make DSPy accessible to beginners. The heavily commented code and step-by-step explanations should help you understand not just how to use DSPy, but why it's such a powerful framework for LLM programming.*
director_bake_off.py ADDED
@@ -0,0 +1,506 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ DSPy Director Bake-Off: A Beginner's Guide to DSPy Programming
4
+
5
+ This script demonstrates core DSPy concepts through a fun example:
6
+ comparing how different movie directors would approach filming the same video idea.
7
+
8
+ Key DSPy Concepts Demonstrated:
9
+ 1. Signatures - Define input/output interfaces for LLM tasks
10
+ 2. Modules - Combine multiple signatures into complex workflows
11
+ 3. Structured Output - Use Pydantic models for reliable data extraction
12
+ 4. Async Processing - Handle multiple LLM calls efficiently
13
+ 5. Chain of Thought - Enable reasoning for complex decisions
14
+
15
+ Author: DSPy Learning Example
16
+ """
17
+
18
+ # Standard library imports
19
+ import os
20
+ import sys
21
+ import asyncio
22
+ from typing import List
23
+
24
+ # Third-party imports
25
+ import dspy # The main DSPy framework for LLM programming
26
+ from dotenv import load_dotenv # For loading environment variables from .env file
27
+ from pydantic import BaseModel, Field # For structured data validation
28
+
29
+ # Load environment variables from .env file (contains API keys)
30
+ load_dotenv()
31
+
32
+ # Global variable to store our configured language model
33
+ # This will be set up once and reused throughout the application
34
+ lm = None
35
+
36
+
37
+ # ==========================================================================
38
+ # SECTION 1: LLM SETUP AND CONFIGURATION
39
+ # ==========================================================================
40
+
41
+ def setup_dspy_provider():
42
+ """
43
+ Configure DSPy with an available LLM provider.
44
+
45
+ DSPy supports many providers (OpenAI, Anthropic, OpenRouter, etc.)
46
+ This function tries to connect to OpenRouter, which provides access
47
+ to many different models through a single API.
48
+
49
+ Returns:
50
+ str: The name of the provider that was successfully configured
51
+ """
52
+ global lm # We'll modify the global lm variable
53
+
54
+ # Check if we have an OpenRouter API key in our environment variables
55
+ if os.getenv('OPENROUTER_API_KEY'):
56
+ print("βœ… Configuring DSPy with OpenRouter...")
57
+
58
+ # Create a DSPy Language Model object
59
+ # Format: "provider/model_name"
60
+ # Here we use a free model from Moonshot AI via OpenRouter
61
+ lm = dspy.LM(
62
+ model="openrouter/moonshotai/kimi-k2:free",
63
+ api_key=os.getenv('OPENROUTER_API_KEY')
64
+ )
65
+
66
+ # Configure DSPy to use this language model globally
67
+ dspy.configure(lm=lm)
68
+ return "openrouter"
69
+ else:
70
+ print("❌ No OpenRouter API key found in environment variables.")
71
+ print("Please add OPENROUTER_API_KEY to your .env file")
72
+ sys.exit(1)
73
+
74
+
75
+ # ==========================================================================
76
+ # SECTION 2: DATA STRUCTURES (PYDANTIC MODELS)
77
+ # ==========================================================================
78
+
79
+ class DirectorCut(BaseModel):
80
+ """
81
+ 🎬 PYDANTIC MODEL: Structured Data for Cinematic Prompts
82
+
83
+ This is a Pydantic model that defines the structure of our data.
84
+ Pydantic ensures that the LLM returns data in exactly the format we expect.
85
+
86
+ Think of this as a "template" that the LLM must fill out completely.
87
+ Each field has a description that helps the LLM understand what to generate.
88
+
89
+ Why use Pydantic with DSPy?
90
+ - Guarantees consistent output format
91
+ - Automatic validation of LLM responses
92
+ - Type safety for your Python code
93
+ - Clear documentation of expected data structure
94
+ """
95
+
96
+ # Basic information (echoed from input)
97
+ director: str = Field(
98
+ ...,
99
+ description="The director sent as part of input, echoed in the output."
100
+ )
101
+ video_idea: str = Field(
102
+ ...,
103
+ description="The video idea sent as part of input, echoed in the output."
104
+ )
105
+
106
+ # The seven components of a cinematic prompt
107
+ # Each field uses Field(...) where ... means "required"
108
+ subject_description: str = Field(
109
+ ...,
110
+ description="A detailed description of the main subject or character."
111
+ )
112
+ action_description: str = Field(
113
+ ...,
114
+ description="A description of the specific action the subject is performing."
115
+ )
116
+ setting_description: str = Field(
117
+ ...,
118
+ description="A rich description of the environment, location, and time of day."
119
+ )
120
+ cinematic_style: str = Field(
121
+ ...,
122
+ description="The overall visual style or medium (e.g., 'Photorealistic, 8K')."
123
+ )
124
+ shot_and_framing: str = Field(
125
+ ...,
126
+ description="The specific camera shot type and framing (e.g., 'Medium shot')."
127
+ )
128
+ camera_movement: str = Field(
129
+ ...,
130
+ description="The movement of the camera during the shot (e.g., 'Slow dolly shot')."
131
+ )
132
+ lighting_and_color: str = Field(
133
+ ...,
134
+ description="The lighting style and color palette that sets the mood."
135
+ )
136
+
137
+ def assemble_prompt(self) -> str:
138
+ """
139
+ Combines all cinematic components into a single, formatted prompt.
140
+
141
+ This method takes all the individual pieces and creates a complete
142
+ prompt that could be used with video generation AI models.
143
+
144
+ Returns:
145
+ str: A complete, formatted cinematic prompt
146
+ """
147
+ # Collect all the cinematic components (excluding director and video_idea)
148
+ components = [
149
+ self.subject_description,
150
+ self.action_description,
151
+ self.setting_description,
152
+ self.cinematic_style,
153
+ self.shot_and_framing,
154
+ self.camera_movement,
155
+ self.lighting_and_color,
156
+ ]
157
+
158
+ # Join components with commas, filtering out empty strings
159
+ prompt_string = ", ".join(filter(None, [c.strip() for c in components if c]))
160
+
161
+ # Return empty string if no components
162
+ if not prompt_string:
163
+ return ""
164
+
165
+ # Capitalize first letter and add period
166
+ return prompt_string[0].upper() + prompt_string[1:] + "."
167
+
168
+ def pretty_print(self):
169
+ """
170
+ Displays the director's interpretation in a nice format.
171
+
172
+ This is a helper method to make the output more readable
173
+ when we're testing or debugging our code.
174
+ """
175
+ print(f"--- Director {self.director} ---")
176
+ print(f"{self.assemble_prompt()}")
177
+ print("--------------------------------")
178
+
179
+
180
+ class ResultClass:
181
+ """
182
+ πŸ“¦ SIMPLE DATA CONTAINER: Holds All Results
183
+
184
+ This is a simple class to package up all our results.
185
+ We could use a Pydantic model here too, but since this is just
186
+ for internal use (not LLM output), a simple class works fine.
187
+ """
188
+ def __init__(self, additional_director, director_ideas, director_ranks):
189
+ self.additional_director = additional_director # The AI-suggested director
190
+ self.director_ideas = director_ideas # List of all DirectorCut objects
191
+ self.director_ranks = director_ranks # Ranking results from the judge
192
+
193
+
194
+ # ==========================================================================
195
+ # SECTION 3: DSPY SIGNATURES (THE HEART OF DSPY)
196
+ # ==========================================================================
197
+
198
+ """
199
+ πŸ”₯ WHAT ARE DSPY SIGNATURES?
200
+
201
+ DSPy Signatures are like "function signatures" but for LLM tasks.
202
+ They define:
203
+ 1. What inputs the LLM should expect
204
+ 2. What outputs the LLM should produce
205
+ 3. The task description (in the docstring)
206
+
207
+ Think of them as contracts between your code and the LLM.
208
+ The LLM will try to fulfill this contract every time.
209
+
210
+ Key Components:
211
+ - InputField: Data going INTO the LLM
212
+ - OutputField: Data coming OUT of the LLM
213
+ - Docstring: Instructions for the LLM about what to do
214
+ """
215
+
216
+ class FindDirector(dspy.Signature):
217
+ """
218
+ 🎯 SIGNATURE 1: Find Additional Director
219
+
220
+ This signature asks the LLM to suggest one additional director
221
+ who would be perfect for the given video idea, but isn't already
222
+ in the user's list.
223
+
224
+ This demonstrates how DSPy can handle creative, open-ended tasks
225
+ where there's no single "correct" answer.
226
+ """
227
+
228
+ # === INPUTS ===
229
+ video_idea = dspy.InputField(
230
+ desc="A simple, high-level user idea or concept for a video."
231
+ )
232
+ director_list: List[str] = dspy.InputField(
233
+ desc="The names of directors the user wants to use."
234
+ )
235
+
236
+ # === OUTPUTS ===
237
+ additonal_director: str = dspy.OutputField(
238
+ desc="The best possible director based on the wanted video idea, that is not already in the provided director list."
239
+ )
240
+
241
+
242
+ class GenerateDirectorCut(dspy.Signature):
243
+ """
244
+ 🎬 SIGNATURE 2: Generate Cinematic Interpretation
245
+
246
+ This is the core signature that transforms a simple video idea
247
+ into a detailed cinematic prompt in the style of a specific director.
248
+
249
+ Notice how the output is a Pydantic model (DirectorCut).
250
+ DSPy will automatically ensure the LLM returns data in exactly
251
+ that structure, with all required fields filled out.
252
+
253
+ This demonstrates DSPy's structured output capabilities.
254
+ """
255
+
256
+ # === INPUTS ===
257
+ video_idea = dspy.InputField(
258
+ desc="A simple, high-level user idea or concept for a video."
259
+ )
260
+ director = dspy.InputField(
261
+ desc="The name of the director to generate a cinematic prompt for (optional).",
262
+ default=None,
263
+ optional=True
264
+ )
265
+
266
+ # === OUTPUTS ===
267
+ director_cut: DirectorCut = dspy.OutputField(
268
+ desc="A structured object containing all seven deconstructed cinematic aspects."
269
+ )
270
+
271
+
272
+ class DirectorJudge(dspy.Signature):
273
+ """
274
+ βš–οΈ SIGNATURE 3: Judge and Rank Director Ideas
275
+
276
+ This signature handles the complex task of comparing multiple
277
+ creative interpretations and ranking them objectively.
278
+
279
+ Notice this takes a List[DirectorCut] as input and returns
280
+ both rankings AND an explanation. This shows how DSPy can
281
+ handle complex, multi-part outputs.
282
+
283
+ This demonstrates DSPy's ability to handle reasoning tasks.
284
+ """
285
+
286
+ # === INPUTS ===
287
+ director_ideas: List[DirectorCut] = dspy.InputField(
288
+ desc="A list of director interpretations to be ranked"
289
+ )
290
+
291
+ # === OUTPUTS ===
292
+ director_rankings: List[int] = dspy.OutputField(
293
+ description="Rank between 1, 2, 3 ... N where 1 is best"
294
+ )
295
+ explanation: str = dspy.OutputField(
296
+ description="Explain why the ranking was given and the winner selected. Format your response with clear sections for each director using HTML formatting: use <h4> tags for director names with their rank, <p> tags for paragraphs, and <br> tags for line breaks. Make it well-structured and easy to read."
297
+ )
298
+
299
+
300
+ # ==========================================================================
301
+ # SECTION 4: DSPY MODULE (COMBINING SIGNATURES INTO WORKFLOWS)
302
+ # ==========================================================================
303
+
304
+ class DirectorBakeOff(dspy.Module):
305
+ """
306
+ πŸ—οΈ DSPY MODULE: The Complete Workflow
307
+
308
+ A DSPy Module combines multiple Signatures into a complete workflow.
309
+ Think of it like a class that orchestrates several LLM calls to solve
310
+ a complex problem.
311
+
312
+ This module demonstrates:
313
+ 1. How to combine multiple signatures
314
+ 2. Different types of DSPy predictors (Predict vs ChainOfThought)
315
+ 3. Async processing for efficiency
316
+ 4. Complex workflow orchestration
317
+
318
+ The workflow:
319
+ 1. Find an additional director suggestion
320
+ 2. Generate cinematic interpretations for all directors (in parallel)
321
+ 3. Judge and rank all interpretations
322
+ 4. Return the best result with explanations
323
+ """
324
+
325
+ def __init__(self):
326
+ """
327
+ Initialize the module with three different DSPy predictors.
328
+
329
+ Notice the different types:
330
+ - dspy.Predict: Basic prediction (fast, direct)
331
+ - dspy.ChainOfThought: Reasoning-enabled prediction (slower, more thoughtful)
332
+ """
333
+ # Basic predictor for finding additional director
334
+ self.findDirector = dspy.Predict(FindDirector)
335
+
336
+ # Basic predictor for generating director cuts
337
+ self.genDirectorCut = dspy.Predict(GenerateDirectorCut)
338
+
339
+ # Chain-of-thought predictor for complex ranking decisions
340
+ # This will make the LLM "think step by step" before ranking
341
+ self.directorJudge = dspy.ChainOfThought(DirectorJudge)
342
+
343
+ async def aforward(self, video_idea: str, directors: List[str] = ["Quentin Tarantino", "Alfred Hitchcock", "Richard Curtis"]):
344
+ """
345
+ πŸš€ ASYNC FORWARD: The Main Workflow
346
+
347
+ This is where the magic happens! This method orchestrates the entire
348
+ director bake-off process using multiple LLM calls.
349
+
350
+ Key DSPy concepts demonstrated:
351
+ 1. Sequential LLM calls (find director first)
352
+ 2. Parallel LLM calls (generate all director cuts simultaneously)
353
+ 3. Complex data flow between signatures
354
+ 4. Async processing for efficiency
355
+
356
+ Args:
357
+ video_idea: The user's video concept
358
+ directors: List of director names to compare
359
+
360
+ Returns:
361
+ ResultClass: Complete results including rankings and explanations
362
+ """
363
+
364
+ # === STEP 1: Display user input ===
365
+ print("\n🎬 User Wanted Directors:")
366
+ for director in directors:
367
+ print(f" - {director}")
368
+
369
+ # === STEP 2: Find additional director suggestion ===
370
+ print("\nπŸ€– Finding AI-suggested director...")
371
+ additional_director_result = self.findDirector(
372
+ video_idea=video_idea,
373
+ director_list=directors
374
+ )
375
+ additional_director = additional_director_result.additonal_director
376
+ print(f" ✨ DSPy Suggested Director: {additional_director}")
377
+
378
+ # === STEP 3: Generate director interpretations (IN PARALLEL!) ===
379
+ print("\n⚑ Generating director interpretations in parallel...")
380
+
381
+ # Combine user directors + AI suggestion
382
+ all_directors = directors + [additional_director]
383
+
384
+ # Use asyncio.gather to run multiple LLM calls simultaneously
385
+ # This is much faster than calling them one by one!
386
+ director_ideas = await asyncio.gather(
387
+ *[self.genDirectorCut.acall(video_idea=video_idea, director=director)
388
+ for director in all_directors]
389
+ )
390
+
391
+ # Display all generated ideas
392
+ print("\n🎭 Generated Director Ideas:")
393
+ for idea in director_ideas:
394
+ idea.director_cut.pretty_print()
395
+
396
+ # === STEP 4: Judge and rank all interpretations ===
397
+ print("\nβš–οΈ Judging and ranking director ideas...")
398
+
399
+ # Extract just the DirectorCut objects for judging
400
+ director_cuts = [idea.director_cut for idea in director_ideas]
401
+
402
+ # Use Chain-of-Thought for complex ranking decision
403
+ director_ranks = self.directorJudge(director_ideas=director_cuts)
404
+
405
+ # === STEP 5: Display rankings ===
406
+ print("\nπŸ† Director Rankings:")
407
+ for rank, idea in zip(director_ranks.director_rankings, director_ideas):
408
+ print(f" Rank {rank}: {idea.director_cut.director}")
409
+
410
+ # === STEP 6: Find and display the winner ===
411
+ best_rank = min(director_ranks.director_rankings)
412
+ best_index = director_ranks.director_rankings.index(best_rank)
413
+ best_idea = director_ideas[best_index]
414
+
415
+ print("\nπŸ₯‡ WINNER - Best Ranked Director Idea:")
416
+ best_idea.director_cut.pretty_print()
417
+
418
+ print(f"\nπŸ’­ Judge's Reasoning:")
419
+ print(f" {director_ranks.explanation}")
420
+ print("=" * 50)
421
+
422
+ # === STEP 7: Return complete results ===
423
+ return ResultClass(
424
+ additional_director=additional_director,
425
+ director_ideas=director_ideas,
426
+ director_ranks=director_ranks
427
+ )
428
+
429
+
430
+ # ==========================================================================
431
+ # SECTION 5: MAIN FUNCTIONS AND ENTRY POINT
432
+ # ==========================================================================
433
+
434
+ def run_bake_off(video_idea: str, directors: str = None) -> ResultClass:
435
+ """
436
+ 🎯 MAIN FUNCTION: Easy-to-use interface for the Director Bake-Off
437
+
438
+ This function provides a simple interface that handles:
439
+ 1. LLM setup and configuration
440
+ 2. Input validation and parsing
441
+ 3. Running the complete workflow
442
+ 4. Error handling
443
+
444
+ This is the function that external code (like our Gradio interface)
445
+ calls to use our DSPy system.
446
+
447
+ Args:
448
+ video_idea: A description of the video concept
449
+ directors: Comma-separated string of director names (optional)
450
+
451
+ Returns:
452
+ ResultClass: Complete results from the bake-off
453
+ """
454
+
455
+ print("πŸš€ Running Director Bake-Off...")
456
+
457
+ # === STEP 1: Ensure LLM is configured ===
458
+ global lm
459
+ if not lm:
460
+ provider = setup_dspy_provider()
461
+ print(f" βœ… DSPy configured with {provider} provider.")
462
+
463
+ # === STEP 2: Parse and validate director input ===
464
+ if not (isinstance(directors, str) and directors.strip()):
465
+ # Use default directors if none provided
466
+ directors = ["Quentin Tarantino", "Alfred Hitchcock", "Richard Curtis"]
467
+ print(" πŸ“ Using default directors")
468
+ else:
469
+ # Parse comma-separated string into list
470
+ directors = [d.strip() for d in directors.split(",") if d.strip()]
471
+ if not directors:
472
+ # Fallback to defaults if parsing failed
473
+ directors = ["Quentin Tarantino", "Alfred Hitchcock", "Richard Curtis"]
474
+ print(" πŸ“ Parsing failed, using default directors")
475
+
476
+ # === STEP 3: Create and run the bake-off ===
477
+ bake_off = DirectorBakeOff()
478
+ result_class = asyncio.run(bake_off.aforward(video_idea=video_idea, directors=directors))
479
+
480
+ return result_class
481
+
482
+
483
+ # ==========================================================================
484
+ # SECTION 6: SCRIPT ENTRY POINT
485
+ # ==========================================================================
486
+
487
+ if __name__ == "__main__":
488
+ """
489
+ 🎬 DEMO: Run the script directly to see it in action!
490
+
491
+ This section only runs when you execute this file directly
492
+ (not when it's imported as a module).
493
+
494
+ Try running: python director_bake_off.py
495
+ """
496
+ print("🎭 DSPy Director Bake-Off Demo")
497
+ print("=" * 40)
498
+
499
+ # Run with a sample video idea
500
+ sample_idea = "A futuristic cityscape with flying cars and neon lights."
501
+ print(f"πŸ“ Sample Video Idea: {sample_idea}")
502
+
503
+ result = run_bake_off(sample_idea)
504
+
505
+ print("\nπŸŽ‰ Demo completed! Check the output above to see how each director")
506
+ print(" would approach filming this futuristic cityscape.")
gradio_interface.py ADDED
@@ -0,0 +1,421 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+
3
+ import gradio as gr
4
+ import asyncio
5
+ from director_bake_off import run_bake_off
6
+ import traceback
7
+
8
+ # Swedish-inspired color palette and styling
9
+ SWEDISH_CSS = """
10
+ /* Swedish Minimalist Design */
11
+ :root {
12
+ --primary-white: #FFFFFF;
13
+ --soft-gray: #F5F5F5;
14
+ --muted-blue: #4A90A4;
15
+ --warm-beige: #E8DCC6;
16
+ --charcoal: #2C2C2C;
17
+ --light-blue: #E8F4F8;
18
+ --accent-gold: #D4AF37;
19
+ --success-green: #7FB069;
20
+ --border-gray: #E0E0E0;
21
+ }
22
+
23
+ /* Global styling */
24
+ .gradio-container {
25
+ font-family: 'Inter', 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif !important;
26
+ background: linear-gradient(135deg, var(--soft-gray) 0%, var(--primary-white) 100%) !important;
27
+ min-height: 100vh;
28
+ }
29
+
30
+ /* Header styling */
31
+ .main-header {
32
+ text-align: center;
33
+ padding: 2rem 0;
34
+ background: var(--primary-white);
35
+ border-bottom: 1px solid var(--border-gray);
36
+ margin-bottom: 2rem;
37
+ }
38
+
39
+ .main-title {
40
+ font-size: 2.5rem;
41
+ font-weight: 300;
42
+ color: var(--charcoal);
43
+ margin-bottom: 0.5rem;
44
+ letter-spacing: -0.02em;
45
+ }
46
+
47
+ .main-subtitle {
48
+ font-size: 1.1rem;
49
+ color: var(--muted-blue);
50
+ font-weight: 400;
51
+ margin-bottom: 0;
52
+ }
53
+
54
+ /* Input section styling */
55
+ .input-section {
56
+ background: var(--primary-white);
57
+ border-radius: 12px;
58
+ padding: 2rem;
59
+ box-shadow: 0 2px 20px rgba(0,0,0,0.05);
60
+ border: 1px solid var(--border-gray);
61
+ margin-bottom: 2rem;
62
+ }
63
+
64
+ .input-label {
65
+ font-size: 1rem;
66
+ font-weight: 500;
67
+ color: var(--charcoal);
68
+ margin-bottom: 0.5rem;
69
+ display: block;
70
+ }
71
+
72
+ /* Button styling */
73
+ .submit-btn {
74
+ background: linear-gradient(135deg, var(--muted-blue) 0%, #5BA0B5 100%) !important;
75
+ border: none !important;
76
+ border-radius: 8px !important;
77
+ padding: 12px 32px !important;
78
+ font-weight: 500 !important;
79
+ font-size: 1rem !important;
80
+ color: white !important;
81
+ transition: all 0.3s ease !important;
82
+ box-shadow: 0 4px 15px rgba(74, 144, 164, 0.3) !important;
83
+ }
84
+
85
+ .submit-btn:hover {
86
+ transform: translateY(-2px) !important;
87
+ box-shadow: 0 6px 25px rgba(74, 144, 164, 0.4) !important;
88
+ }
89
+
90
+ /* Results section */
91
+ .results-container {
92
+ background: var(--primary-white);
93
+ border-radius: 12px;
94
+ padding: 2rem;
95
+ box-shadow: 0 2px 20px rgba(0,0,0,0.05);
96
+ border: 1px solid var(--border-gray);
97
+ margin-top: 2rem;
98
+ }
99
+
100
+ .director-card {
101
+ background: var(--light-blue);
102
+ border-radius: 10px;
103
+ padding: 1.5rem;
104
+ margin-bottom: 1.5rem;
105
+ border-left: 4px solid var(--muted-blue);
106
+ transition: all 0.3s ease;
107
+ }
108
+
109
+ .director-card:hover {
110
+ transform: translateY(-2px);
111
+ box-shadow: 0 8px 25px rgba(0,0,0,0.1);
112
+ }
113
+
114
+ .rank-badge {
115
+ display: inline-block;
116
+ background: var(--accent-gold);
117
+ color: white;
118
+ padding: 4px 12px;
119
+ border-radius: 20px;
120
+ font-size: 0.85rem;
121
+ font-weight: 600;
122
+ margin-bottom: 0.5rem;
123
+ }
124
+
125
+ .rank-1 { background: var(--accent-gold); }
126
+ .rank-2 { background: #C0C0C0; }
127
+ .rank-3 { background: #CD7F32; }
128
+
129
+ .director-name {
130
+ font-size: 1.3rem;
131
+ font-weight: 600;
132
+ color: var(--charcoal);
133
+ margin-bottom: 1rem;
134
+ }
135
+
136
+ .prompt-text {
137
+ font-size: 1rem;
138
+ line-height: 1.6;
139
+ color: var(--charcoal);
140
+ background: var(--primary-white);
141
+ padding: 1rem;
142
+ border-radius: 8px;
143
+ border: 1px solid var(--border-gray);
144
+ margin-bottom: 1rem;
145
+ }
146
+
147
+ .additional-director {
148
+ background: var(--warm-beige);
149
+ border-radius: 10px;
150
+ padding: 1.5rem;
151
+ margin-bottom: 2rem;
152
+ border-left: 4px solid var(--success-green);
153
+ }
154
+
155
+ .explanation-section {
156
+ background: var(--soft-gray);
157
+ border-radius: 10px;
158
+ padding: 1.5rem;
159
+ margin-top: 2rem;
160
+ border-left: 4px solid var(--muted-blue);
161
+ }
162
+
163
+ .section-title {
164
+ font-size: 1.2rem;
165
+ font-weight: 600;
166
+ color: var(--charcoal);
167
+ margin-bottom: 1rem;
168
+ }
169
+
170
+ /* Loading animation */
171
+ .loading {
172
+ text-align: center;
173
+ padding: 2rem;
174
+ color: var(--muted-blue);
175
+ }
176
+
177
+ /* Responsive design */
178
+ @media (max-width: 768px) {
179
+ .main-title {
180
+ font-size: 2rem;
181
+ }
182
+
183
+ .input-section, .results-container {
184
+ padding: 1.5rem;
185
+ margin: 1rem;
186
+ }
187
+
188
+ .director-card {
189
+ padding: 1rem;
190
+ }
191
+ }
192
+
193
+ /* Custom textbox styling */
194
+ .gr-textbox {
195
+ border-radius: 8px !important;
196
+ border: 1px solid var(--border-gray) !important;
197
+ }
198
+
199
+ .gr-textbox:focus {
200
+ border-color: var(--muted-blue) !important;
201
+ box-shadow: 0 0 0 3px rgba(74, 144, 164, 0.1) !important;
202
+ }
203
+ """
204
+
205
+ def format_results_html(result):
206
+ """Format the results into beautiful HTML with Swedish design."""
207
+ if not result:
208
+ return "<div class='loading'>No results to display.</div>"
209
+
210
+ try:
211
+ # Header with additional director suggestion
212
+ html = f"""
213
+ <div class="results-container">
214
+ <div class="additional-director">
215
+ <div class="section-title">🎬 AI Suggested Director</div>
216
+ <div style="font-size: 1.1rem; color: var(--charcoal);">
217
+ <strong>{result.additional_director}</strong> - A perfect match for your vision!
218
+ </div>
219
+ </div>
220
+ """
221
+
222
+ # Director ideas with rankings
223
+ html += '<div class="section-title">πŸ† Director Interpretations (Ranked)</div>'
224
+
225
+ # Sort director ideas by ranking
226
+ ranked_ideas = []
227
+ for i, idea in enumerate(result.director_ideas):
228
+ rank = result.director_ranks.director_rankings[i]
229
+ ranked_ideas.append((rank, idea.director_cut))
230
+
231
+ ranked_ideas.sort(key=lambda x: x[0]) # Sort by rank (1 is best)
232
+
233
+ for rank, director_cut in ranked_ideas:
234
+ rank_class = f"rank-{min(rank, 3)}" # Use rank-1, rank-2, rank-3 classes
235
+
236
+ html += f"""
237
+ <div class="director-card">
238
+ <div class="rank-badge {rank_class}">#{rank}</div>
239
+ <div class="director-name">{director_cut.director}</div>
240
+ <div class="prompt-text">{director_cut.assemble_prompt()}</div>
241
+
242
+ <details style="margin-top: 1rem;">
243
+ <summary style="cursor: pointer; font-weight: 500; color: var(--muted-blue);">
244
+ View Detailed Breakdown
245
+ </summary>
246
+ <div style="margin-top: 1rem; padding: 1rem; background: var(--soft-gray); border-radius: 6px;">
247
+ <div style="margin-bottom: 0.8rem;"><strong>Subject:</strong> {director_cut.subject_description}</div>
248
+ <div style="margin-bottom: 0.8rem;"><strong>Action:</strong> {director_cut.action_description}</div>
249
+ <div style="margin-bottom: 0.8rem;"><strong>Setting:</strong> {director_cut.setting_description}</div>
250
+ <div style="margin-bottom: 0.8rem;"><strong>Style:</strong> {director_cut.cinematic_style}</div>
251
+ <div style="margin-bottom: 0.8rem;"><strong>Shot & Framing:</strong> {director_cut.shot_and_framing}</div>
252
+ <div style="margin-bottom: 0.8rem;"><strong>Camera Movement:</strong> {director_cut.camera_movement}</div>
253
+ <div style="margin-bottom: 0.8rem;"><strong>Lighting & Color:</strong> {director_cut.lighting_and_color}</div>
254
+ </div>
255
+ </details>
256
+ </div>
257
+ """
258
+
259
+ # Explanation section with HTML formatting from DSPy
260
+ html += f"""
261
+ <div class="explanation-section">
262
+ <div class="section-title">πŸ€” Judge's Reasoning</div>
263
+ <div style="line-height: 1.8; color: var(--charcoal); font-size: 1rem;">
264
+ {result.director_ranks.explanation}
265
+ </div>
266
+ </div>
267
+ </div>
268
+ """
269
+
270
+ return html
271
+
272
+ except Exception as e:
273
+ return f"""
274
+ <div class="results-container">
275
+ <div style="color: #e74c3c; padding: 1rem; background: #fdf2f2; border-radius: 8px;">
276
+ <strong>Error formatting results:</strong> {str(e)}
277
+ </div>
278
+ </div>
279
+ """
280
+
281
+ def run_director_bakeoff(video_idea, directors):
282
+ """Run the director bake-off and return formatted results."""
283
+ if not video_idea or not video_idea.strip():
284
+ return "<div class='loading'>Please enter a video idea to get started!</div>"
285
+
286
+ try:
287
+ # Initial loading message with expectations
288
+ yield """
289
+ <div class='loading' style='background: var(--light-blue); padding: 2rem; border-radius: 12px; border-left: 4px solid var(--muted-blue);'>
290
+ <div style='font-size: 1.3rem; font-weight: 600; color: var(--charcoal); margin-bottom: 1rem;'>
291
+ 🎬 Director Bake-Off in Progress...
292
+ </div>
293
+ <div style='font-size: 1rem; color: var(--muted-blue); margin-bottom: 1.5rem; line-height: 1.6;'>
294
+ Please be patient, this may take some minutes...<br>
295
+ We're consulting with legendary directors to bring your vision to life!<br>
296
+ <strong>This process typically takes 30-60 seconds.</strong><br>
297
+ ✨
298
+ </div>
299
+ <div style='background: var(--primary-white); padding: 1rem; border-radius: 8px; border-left: 3px solid var(--accent-gold);'>
300
+ <div style='font-size: 0.9rem; color: var(--charcoal);'>
301
+ <strong>What's happening:</strong><br>
302
+ β€’ Finding the perfect additional director for your concept<br>
303
+ β€’ Generating unique interpretations from each director<br>
304
+ β€’ Ranking all concepts to find the best match<br>
305
+ </div>
306
+ </div>
307
+ </div>
308
+ """
309
+
310
+ # Run the bake-off
311
+ result = run_bake_off(video_idea, directors)
312
+
313
+ # Format and return results
314
+ formatted_html = format_results_html(result)
315
+ yield formatted_html
316
+
317
+ except Exception as e:
318
+ error_html = f"""
319
+ <div class="results-container">
320
+ <div style="color: #e74c3c; padding: 1.5rem; background: #fdf2f2; border-radius: 8px; border-left: 4px solid #e74c3c;">
321
+ <div style="font-weight: 600; margin-bottom: 0.5rem;">Something went wrong!</div>
322
+ <div style="margin-bottom: 1rem;">{str(e)}</div>
323
+ <details>
324
+ <summary style="cursor: pointer; color: #c0392b;">View technical details</summary>
325
+ <pre style="margin-top: 1rem; font-size: 0.85rem; background: #f8f8f8; padding: 1rem; border-radius: 4px; overflow-x: auto;">
326
+ {traceback.format_exc()}
327
+ </pre>
328
+ </details>
329
+ </div>
330
+ </div>
331
+ """
332
+ yield error_html
333
+
334
+ # Create the Gradio interface
335
+ def create_interface():
336
+ with gr.Blocks(css=SWEDISH_CSS, title="Director Bake-Off Studio") as interface:
337
+ # Header
338
+ gr.HTML("""
339
+ <div class="main-header">
340
+ <h1 class="main-title">Director Bake-Off Studio</h1>
341
+ <p class="main-subtitle">Let legendary directors compete to bring your vision to life</p>
342
+ </div>
343
+ """)
344
+
345
+ # How it Works section - moved to top
346
+ gr.HTML("""
347
+ <div style="background: var(--light-blue); padding: 1.5rem; border-radius: 10px; margin-bottom: 2rem; border-left: 4px solid var(--muted-blue);">
348
+ <h3 style="margin-top: 0; color: var(--charcoal);">How it works:</h3>
349
+ <ol style="color: var(--charcoal); line-height: 1.6;">
350
+ <li>Enter your video idea in the text area below</li>
351
+ <li>List your favorite directors (or use our defaults)</li>
352
+ <li>Our AI will suggest an additional director perfect for your vision</li>
353
+ <li>Watch as each director creates their unique interpretation</li>
354
+ <li>See them ranked by how well they match your concept!</li>
355
+ </ol>
356
+ </div>
357
+ """)
358
+
359
+ # Results section - will only show content when there are actual results
360
+ results_html = gr.HTML(
361
+ value="",
362
+ elem_classes=["results-display"],
363
+ visible=False
364
+ )
365
+
366
+ with gr.Row():
367
+ with gr.Column(scale=1):
368
+ # Input section
369
+
370
+ video_idea = gr.Textbox(
371
+ label="πŸŽ₯ Your Video Idea",
372
+ placeholder="Describe your video concept... (e.g., 'A futuristic cityscape with flying cars and neon lights')",
373
+ lines=4,
374
+ elem_classes=["input-field"]
375
+ )
376
+
377
+ directors = gr.Textbox(
378
+ label="🎬 Directors (comma-separated)",
379
+ placeholder="e.g., Quentin Tarantino, Alfred Hitchcock",
380
+ value="Quentin Tarantino, Alfred Hitchcock",
381
+ lines=2,
382
+ elem_classes=["input-field"]
383
+ )
384
+
385
+ submit_btn = gr.Button(
386
+ "πŸš€ Start the Bake-Off!",
387
+ elem_classes=["submit-btn"],
388
+ variant="primary"
389
+ )
390
+
391
+ # Set up the interaction - removed show_progress=True to eliminate progress bar
392
+ def handle_submit(video_idea, directors):
393
+ # Make results visible and update content
394
+ for result in run_director_bakeoff(video_idea, directors):
395
+ yield gr.update(value=result, visible=True)
396
+
397
+ submit_btn.click(
398
+ fn=handle_submit,
399
+ inputs=[video_idea, directors],
400
+ outputs=[results_html]
401
+ )
402
+
403
+ # Footer
404
+ gr.HTML("""
405
+ <div style="text-align: center; padding: 2rem; color: var(--muted-blue); font-size: 0.9rem;">
406
+ <p>Powered by DSPy and the creative genius of legendary directors 🎬</p>
407
+ </div>
408
+ """)
409
+
410
+ return interface
411
+
412
+ if __name__ == "__main__":
413
+ # Create and launch the interface
414
+ interface = create_interface()
415
+ interface.launch(
416
+ server_name="0.0.0.0",
417
+ server_port=7860,
418
+ share=False,
419
+ show_error=True,
420
+ debug=True
421
+ )
requirements.txt ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ pydantic
2
+ python-dotenv
3
+ dspy
4
+ gradio