Spaces:
Sleeping
Sleeping
Commit
·
032080e
1
Parent(s):
25a899a
vid gen
Browse files- agent_setup.py +1 -1
- app.py +94 -10
- requirements.txt +2 -1
- story_generator.py +91 -0
- utils.py +48 -0
agent_setup.py
CHANGED
|
@@ -22,7 +22,7 @@ def initialize_adk(vision_model, processor, retriever):
|
|
| 22 |
|
| 23 |
agent = Agent(
|
| 24 |
name="AuraMindGlowAgent",
|
| 25 |
-
model="gemini-2.
|
| 26 |
description="A farming assistant that can diagnose plant health and suggest remedies.",
|
| 27 |
instruction="You are a friendly farming assistant. Your goal is to help users identify plant health issues and find solutions. Use your tools to diagnose the plant from an image and then find a remedy.",
|
| 28 |
tools=[diagnosis_tool, remedy_tool]
|
|
|
|
| 22 |
|
| 23 |
agent = Agent(
|
| 24 |
name="AuraMindGlowAgent",
|
| 25 |
+
model="gemini-2.0-flash",
|
| 26 |
description="A farming assistant that can diagnose plant health and suggest remedies.",
|
| 27 |
instruction="You are a friendly farming assistant. Your goal is to help users identify plant health issues and find solutions. Use your tools to diagnose the plant from an image and then find a remedy.",
|
| 28 |
tools=[diagnosis_tool, remedy_tool]
|
app.py
CHANGED
|
@@ -2,7 +2,7 @@
|
|
| 2 |
# Aura Mind Glow - Main Application (Refactored)
|
| 3 |
# ==============================================================================
|
| 4 |
"""
|
| 5 |
-
This script launches the Aura Mind Glow application.
|
| 6 |
"""
|
| 7 |
|
| 8 |
# --- Step 0: Essential Imports ---
|
|
@@ -22,6 +22,8 @@ from vision_model import load_vision_model
|
|
| 22 |
from knowledge_base import get_retriever
|
| 23 |
from agent_setup import initialize_adk
|
| 24 |
from google.genai import types
|
|
|
|
|
|
|
| 25 |
|
| 26 |
print("✅ All libraries imported successfully.")
|
| 27 |
|
|
@@ -32,19 +34,35 @@ print("Performing initial setup...")
|
|
| 32 |
VISION_MODEL, PROCESSOR = load_vision_model()
|
| 33 |
RETRIEVER = get_retriever()
|
| 34 |
|
| 35 |
-
# Initialize ADK components
|
| 36 |
adk_components = initialize_adk(VISION_MODEL, PROCESSOR, RETRIEVER)
|
| 37 |
ADK_RUNNER = adk_components["runner"] if adk_components else None
|
| 38 |
DIAGNOSIS_TOOL = adk_components["diagnosis_tool"] if adk_components else None
|
| 39 |
REMEDY_TOOL = adk_components["remedy_tool"] if adk_components else None
|
| 40 |
SESSION_SERVICE = adk_components["session_service"] if adk_components else None
|
| 41 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 42 |
|
| 43 |
# --- Step 3: Define Gradio UIs ---
|
| 44 |
|
| 45 |
def create_field_mode_ui():
|
| 46 |
"""Creates the Gradio UI for the offline Field Mode."""
|
| 47 |
-
|
| 48 |
def get_diagnosis_and_remedy(uploaded_image: Image.Image) -> str:
|
| 49 |
if uploaded_image is None:
|
| 50 |
return "Please upload an image of a maize plant first."
|
|
@@ -104,9 +122,9 @@ def create_field_mode_ui():
|
|
| 104 |
css=css
|
| 105 |
)
|
| 106 |
|
| 107 |
-
|
| 108 |
def create_connected_mode_ui():
|
| 109 |
"""Creates the Gradio UI for the online Connected Mode."""
|
|
|
|
| 110 |
with gr.Blocks(theme=gr.themes.Soft(primary_hue="green", secondary_hue="lime")) as demo:
|
| 111 |
gr.Markdown("# 🌽 Aura Mind Glow: Connected Mode 🤖")
|
| 112 |
gr.Markdown("I am an AI farming assistant. Upload an image and ask for a diagnosis and remedy.")
|
|
@@ -170,6 +188,51 @@ def create_connected_mode_ui():
|
|
| 170 |
|
| 171 |
return demo
|
| 172 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 173 |
|
| 174 |
# --- Step 4: App Launcher ---
|
| 175 |
|
|
@@ -184,11 +247,32 @@ def check_internet_connection(host="8.8.8.8", port=53, timeout=3):
|
|
| 184 |
|
| 185 |
|
| 186 |
if __name__ == "__main__":
|
| 187 |
-
|
| 188 |
-
|
| 189 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 190 |
else:
|
| 191 |
-
|
| 192 |
-
ui = create_field_mode_ui()
|
| 193 |
|
| 194 |
-
ui.launch(share=True, debug=True)
|
|
|
|
| 2 |
# Aura Mind Glow - Main Application (Refactored)
|
| 3 |
# ==============================================================================
|
| 4 |
"""
|
| 5 |
+
This script launches the Aura Mind Glow application, now with multiple modes.
|
| 6 |
"""
|
| 7 |
|
| 8 |
# --- Step 0: Essential Imports ---
|
|
|
|
| 22 |
from knowledge_base import get_retriever
|
| 23 |
from agent_setup import initialize_adk
|
| 24 |
from google.genai import types
|
| 25 |
+
from story_generator import create_story_prompt_from_pdf, generate_video_from_prompt
|
| 26 |
+
from langchain_huggingface import HuggingFaceEndpoint
|
| 27 |
|
| 28 |
print("✅ All libraries imported successfully.")
|
| 29 |
|
|
|
|
| 34 |
VISION_MODEL, PROCESSOR = load_vision_model()
|
| 35 |
RETRIEVER = get_retriever()
|
| 36 |
|
| 37 |
+
# Initialize ADK components for Connected Mode
|
| 38 |
adk_components = initialize_adk(VISION_MODEL, PROCESSOR, RETRIEVER)
|
| 39 |
ADK_RUNNER = adk_components["runner"] if adk_components else None
|
| 40 |
DIAGNOSIS_TOOL = adk_components["diagnosis_tool"] if adk_components else None
|
| 41 |
REMEDY_TOOL = adk_components["remedy_tool"] if adk_components else None
|
| 42 |
SESSION_SERVICE = adk_components["session_service"] if adk_components else None
|
| 43 |
|
| 44 |
+
# Initialize a separate LLM for the Story Generator
|
| 45 |
+
STORY_LLM = None
|
| 46 |
+
if os.environ.get("HF_TOKEN"):
|
| 47 |
+
try:
|
| 48 |
+
STORY_LLM = HuggingFaceEndpoint(
|
| 49 |
+
repo_id="HuggingFaceH4/zephyr-7b-beta",
|
| 50 |
+
huggingfacehub_api_token=os.environ.get("HF_TOKEN"),
|
| 51 |
+
max_new_tokens=150,
|
| 52 |
+
temperature=0.4,
|
| 53 |
+
)
|
| 54 |
+
print("✅ Story Generator LLM initialized successfully.")
|
| 55 |
+
except Exception as e:
|
| 56 |
+
print(f"❌ Could not initialize Story Generator LLM: {e}")
|
| 57 |
+
else:
|
| 58 |
+
print("❌ HF_TOKEN not found. Story Generator Mode will be disabled.")
|
| 59 |
+
|
| 60 |
|
| 61 |
# --- Step 3: Define Gradio UIs ---
|
| 62 |
|
| 63 |
def create_field_mode_ui():
|
| 64 |
"""Creates the Gradio UI for the offline Field Mode."""
|
| 65 |
+
# ... (This function remains unchanged) ...
|
| 66 |
def get_diagnosis_and_remedy(uploaded_image: Image.Image) -> str:
|
| 67 |
if uploaded_image is None:
|
| 68 |
return "Please upload an image of a maize plant first."
|
|
|
|
| 122 |
css=css
|
| 123 |
)
|
| 124 |
|
|
|
|
| 125 |
def create_connected_mode_ui():
|
| 126 |
"""Creates the Gradio UI for the online Connected Mode."""
|
| 127 |
+
# ... (This function remains unchanged) ...
|
| 128 |
with gr.Blocks(theme=gr.themes.Soft(primary_hue="green", secondary_hue="lime")) as demo:
|
| 129 |
gr.Markdown("# 🌽 Aura Mind Glow: Connected Mode 🤖")
|
| 130 |
gr.Markdown("I am an AI farming assistant. Upload an image and ask for a diagnosis and remedy.")
|
|
|
|
| 188 |
|
| 189 |
return demo
|
| 190 |
|
| 191 |
+
def create_story_mode_ui():
|
| 192 |
+
"""Creates the Gradio UI for the Farmer's Story Mode."""
|
| 193 |
+
with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="yellow")) as demo:
|
| 194 |
+
gr.Markdown("# 🌽 Aura Mind Glow: Farmer's Story Mode 🎬")
|
| 195 |
+
gr.Markdown("Create a short video story from your farm documents. Upload a PDF, describe the mood, and let the AI create a visual story.")
|
| 196 |
+
|
| 197 |
+
with gr.Row():
|
| 198 |
+
with gr.Column(scale=1):
|
| 199 |
+
pdf_input = gr.File(label="Upload Farm PDF", file_types=[".pdf"])
|
| 200 |
+
image_input = gr.Image(type="filepath", label="Optional: Upload a Starting Image")
|
| 201 |
+
user_prompt_input = gr.Textbox(label="Describe the video's tone or theme", placeholder="e.g., hopeful, a look back at a tough season, etc.")
|
| 202 |
+
submit_btn = gr.Button("Generate Video Story")
|
| 203 |
+
with gr.Column(scale=2):
|
| 204 |
+
video_output = gr.Video(label="Generated Video Story")
|
| 205 |
+
status_output = gr.Textbox(label="Status", interactive=False, lines=3)
|
| 206 |
+
|
| 207 |
+
def story_generation_process(pdf, image, user_prompt):
|
| 208 |
+
if pdf is None:
|
| 209 |
+
yield None, "Please upload a PDF document to begin."
|
| 210 |
+
return
|
| 211 |
+
|
| 212 |
+
yield None, "Step 1: Reading PDF and generating creative prompt..."
|
| 213 |
+
|
| 214 |
+
creative_prompt = create_story_prompt_from_pdf(pdf.name, user_prompt, STORY_LLM)
|
| 215 |
+
|
| 216 |
+
if "Error" in creative_prompt:
|
| 217 |
+
yield None, creative_prompt
|
| 218 |
+
return
|
| 219 |
+
|
| 220 |
+
yield None, f"Step 2: Generating video with prompt: '{creative_prompt[:100]}...' (This may take several minutes)"
|
| 221 |
+
|
| 222 |
+
video_path = generate_video_from_prompt(creative_prompt, image)
|
| 223 |
+
|
| 224 |
+
if "Error" in video_path:
|
| 225 |
+
yield None, video_path
|
| 226 |
+
return
|
| 227 |
+
|
| 228 |
+
yield video_path, "Video generation complete!"
|
| 229 |
+
|
| 230 |
+
submit_btn.click(
|
| 231 |
+
story_generation_process,
|
| 232 |
+
inputs=[pdf_input, image_input, user_prompt_input],
|
| 233 |
+
outputs=[video_output, status_output]
|
| 234 |
+
)
|
| 235 |
+
return demo
|
| 236 |
|
| 237 |
# --- Step 4: App Launcher ---
|
| 238 |
|
|
|
|
| 247 |
|
| 248 |
|
| 249 |
if __name__ == "__main__":
|
| 250 |
+
field_mode_ui = create_field_mode_ui()
|
| 251 |
+
interface_list = [field_mode_ui]
|
| 252 |
+
tab_titles = ["Field Mode (Offline)"]
|
| 253 |
+
|
| 254 |
+
# Conditionally add modes that require an internet connection
|
| 255 |
+
if check_internet_connection():
|
| 256 |
+
if ADK_RUNNER:
|
| 257 |
+
connected_mode_ui = create_connected_mode_ui()
|
| 258 |
+
interface_list.append(connected_mode_ui)
|
| 259 |
+
tab_titles.append("Connected Mode")
|
| 260 |
+
else:
|
| 261 |
+
print("⚠️ Connected Mode disabled: ADK components not initialized.")
|
| 262 |
+
|
| 263 |
+
if STORY_LLM:
|
| 264 |
+
story_mode_ui = create_story_mode_ui()
|
| 265 |
+
interface_list.append(story_mode_ui)
|
| 266 |
+
tab_titles.append("Farmer's Story Mode")
|
| 267 |
+
else:
|
| 268 |
+
print("⚠️ Farmer's Story Mode disabled: Story LLM not initialized.")
|
| 269 |
+
else:
|
| 270 |
+
print("❌ No internet connection. Launching in Offline Mode only.")
|
| 271 |
+
|
| 272 |
+
# Launch the appropriate UI
|
| 273 |
+
if len(interface_list) > 1:
|
| 274 |
+
ui = gr.TabbedInterface(interface_list, tab_titles)
|
| 275 |
else:
|
| 276 |
+
ui = field_mode_ui
|
|
|
|
| 277 |
|
| 278 |
+
ui.launch(share=True, debug=True)
|
requirements.txt
CHANGED
|
@@ -16,4 +16,5 @@ pymupdf
|
|
| 16 |
duckduckgo-search
|
| 17 |
langgraph
|
| 18 |
google-genai
|
| 19 |
-
google-adk
|
|
|
|
|
|
| 16 |
duckduckgo-search
|
| 17 |
langgraph
|
| 18 |
google-genai
|
| 19 |
+
google-adk
|
| 20 |
+
pypdf
|
story_generator.py
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
import time
|
| 3 |
+
import os
|
| 4 |
+
from google import genai
|
| 5 |
+
from google.genai import types
|
| 6 |
+
from langchain_community.document_loaders import PyPDFLoader
|
| 7 |
+
from langchain_huggingface import HuggingFaceEndpoint
|
| 8 |
+
from PIL import Image
|
| 9 |
+
from utils import retry_with_exponential_backoff
|
| 10 |
+
|
| 11 |
+
def create_story_prompt_from_pdf(pdf_path: str, user_prompt: str, llm: HuggingFaceEndpoint) -> str:
|
| 12 |
+
"""
|
| 13 |
+
Reads a PDF, summarizes its content, and creates a creative prompt for video generation.
|
| 14 |
+
"""
|
| 15 |
+
try:
|
| 16 |
+
# 1. Load and read the PDF
|
| 17 |
+
loader = PyPDFLoader(pdf_path)
|
| 18 |
+
pages = loader.load_and_split()
|
| 19 |
+
# Limit to first 3 pages for brevity and to manage token count
|
| 20 |
+
pdf_content = " ".join(page.page_content for page in pages[:3])
|
| 21 |
+
|
| 22 |
+
# 2. Use an LLM to generate a creative prompt
|
| 23 |
+
system_prompt = """You are a creative assistant for a farmer. Your task is to read the summary of a document and a user's desired tone, and then write a short, visually descriptive prompt for a video generation model (like Google Veo). The prompt should tell a story about a farmer dealing with this paperwork, capturing the user's desired tone. Describe the scene, camera shots, and the farmer's actions.
|
| 24 |
+
|
| 25 |
+
Example:
|
| 26 |
+
- Document Summary: "Invoice for tractor parts, total $2,500. Delivery next week."
|
| 27 |
+
- User Tone: "A feeling of progress and investment in the future."
|
| 28 |
+
- Generated Prompt: "Close up on a farmer's weathered hands circling a date on a calendar in a rustic office. The camera pulls back to reveal invoices on the desk. The farmer looks out the window at the fields, a determined smile on their face. Golden morning light fills the room. Cinematic, hopeful, 4k."
|
| 29 |
+
"""
|
| 30 |
+
|
| 31 |
+
human_prompt = f"""
|
| 32 |
+
Document Summary: "{pdf_content[:1500]}"
|
| 33 |
+
User Tone: "{user_prompt}"
|
| 34 |
+
|
| 35 |
+
Generate a creative video prompt based on the summary and tone.
|
| 36 |
+
"""
|
| 37 |
+
|
| 38 |
+
# The llm object from HuggingFaceEndpoint expects a string prompt
|
| 39 |
+
creative_prompt = llm.invoke(human_prompt, config={"system_prompt": system_prompt})
|
| 40 |
+
|
| 41 |
+
print(f"Generated creative prompt: {creative_prompt}")
|
| 42 |
+
return creative_prompt
|
| 43 |
+
|
| 44 |
+
except Exception as e:
|
| 45 |
+
print(f"Error creating story from PDF: {e}")
|
| 46 |
+
return f"Error processing PDF: {e}"
|
| 47 |
+
|
| 48 |
+
@retry_with_exponential_backoff
|
| 49 |
+
def generate_video_from_prompt(prompt: str, image_path: str = None) -> str:
|
| 50 |
+
"""
|
| 51 |
+
Generates a video using the Veo API from a text prompt and an optional starting image.
|
| 52 |
+
Returns the path to the saved video file.
|
| 53 |
+
"""
|
| 54 |
+
# This function is now wrapped with the retry decorator.
|
| 55 |
+
# The try/except block is still useful for catching non-retriable errors.
|
| 56 |
+
try:
|
| 57 |
+
client = genai.Client()
|
| 58 |
+
|
| 59 |
+
if image_path:
|
| 60 |
+
print(f"Generating video with initial image: {image_path}")
|
| 61 |
+
img = Image.open(image_path)
|
| 62 |
+
operation = client.models.generate_videos(
|
| 63 |
+
model="veo-3.0-generate-preview",
|
| 64 |
+
prompt=prompt,
|
| 65 |
+
image=img,
|
| 66 |
+
)
|
| 67 |
+
else:
|
| 68 |
+
print("Generating video from text prompt only.")
|
| 69 |
+
operation = client.models.generate_videos(
|
| 70 |
+
model="veo-3.0-generate-preview",
|
| 71 |
+
prompt=prompt,
|
| 72 |
+
)
|
| 73 |
+
|
| 74 |
+
print("Video generation started. Polling for completion...")
|
| 75 |
+
while not operation.done:
|
| 76 |
+
print("Waiting for video generation to complete...")
|
| 77 |
+
time.sleep(10)
|
| 78 |
+
operation = client.operations.get(operation)
|
| 79 |
+
|
| 80 |
+
generated_video = operation.response.generated_videos[0]
|
| 81 |
+
|
| 82 |
+
video_file_name = "generated_story.mp4"
|
| 83 |
+
client.files.download(file=generated_video.video)
|
| 84 |
+
generated_video.video.save(video_file_name)
|
| 85 |
+
|
| 86 |
+
print(f"Generated video saved to {video_file_name}")
|
| 87 |
+
return video_file_name
|
| 88 |
+
|
| 89 |
+
except Exception as e:
|
| 90 |
+
print(f"Error generating video: {e}")
|
| 91 |
+
return f"Error during video generation: {e}"
|
utils.py
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# utils.py
|
| 2 |
+
import time
|
| 3 |
+
import random
|
| 4 |
+
from functools import wraps
|
| 5 |
+
from google.api_core import exceptions
|
| 6 |
+
import gradio as gr
|
| 7 |
+
|
| 8 |
+
def retry_with_exponential_backoff(
|
| 9 |
+
func,
|
| 10 |
+
initial_delay: float = 2,
|
| 11 |
+
exponential_base: float = 2,
|
| 12 |
+
jitter: bool = True,
|
| 13 |
+
max_retries: int = 5,
|
| 14 |
+
):
|
| 15 |
+
"""
|
| 16 |
+
A decorator to retry a function with exponential backoff for API calls.
|
| 17 |
+
It specifically catches google.api_core.exceptions.ResourceExhausted.
|
| 18 |
+
"""
|
| 19 |
+
|
| 20 |
+
@wraps(func)
|
| 21 |
+
def wrapper(*args, **kwargs):
|
| 22 |
+
num_retries = 0
|
| 23 |
+
delay = initial_delay
|
| 24 |
+
|
| 25 |
+
while True:
|
| 26 |
+
try:
|
| 27 |
+
return func(*args, **kwargs)
|
| 28 |
+
except exceptions.ResourceExhausted as e:
|
| 29 |
+
num_retries += 1
|
| 30 |
+
if num_retries > max_retries:
|
| 31 |
+
# Using gr.Error to raise a user-facing error in the Gradio UI
|
| 32 |
+
raise gr.Error(
|
| 33 |
+
f"Maximum number of retries ({max_retries}) exceeded. The API is still busy. Please try again later."
|
| 34 |
+
) from e
|
| 35 |
+
|
| 36 |
+
if jitter:
|
| 37 |
+
delay *= exponential_base * (1 + random.random())
|
| 38 |
+
else:
|
| 39 |
+
delay *= exponential_base
|
| 40 |
+
|
| 41 |
+
# It's helpful to print the delay to the console for debugging
|
| 42 |
+
print(f"Rate limit exceeded. Retrying in {delay:.2f} seconds...")
|
| 43 |
+
time.sleep(delay)
|
| 44 |
+
except Exception as e:
|
| 45 |
+
# Raise other exceptions immediately
|
| 46 |
+
raise gr.Error(f"An unexpected error occurred: {e}")
|
| 47 |
+
|
| 48 |
+
return wrapper
|