|
|
""" |
|
|
Gradio Interface for LangGraph ReAct Agent |
|
|
A production-ready web interface with real-time streaming output |
|
|
Styled similar to gradio_ui.py with sidebar layout |
|
|
""" |
|
|
|
|
|
import os |
|
|
import re |
|
|
import sys |
|
|
import time |
|
|
from typing import Generator, Optional |
|
|
from dataclasses import dataclass |
|
|
|
|
|
import gradio as gr |
|
|
from gradio.themes.utils import fonts |
|
|
from dotenv import load_dotenv |
|
|
from langchain_openai import ChatOpenAI |
|
|
from langchain_core.messages import HumanMessage, AIMessage |
|
|
from langchain_anthropic import ChatAnthropic |
|
|
|
|
|
|
|
|
sys.path.append(os.path.dirname(__file__)) |
|
|
from agent_v3 import CodeAgent |
|
|
from core.types import AgentConfig |
|
|
|
|
|
|
|
|
from managers import PythonExecutor |
|
|
|
|
|
|
|
|
load_dotenv("./.env") |
|
|
|
|
|
|
|
|
class GradioAgentUI: |
|
|
""" |
|
|
Gradio interface for interacting with the LangGraph ReAct Agent. |
|
|
Styled similar to the smolagents GradioUI with sidebar layout. |
|
|
""" |
|
|
|
|
|
def __init__(self, model=None, config=None): |
|
|
"""Initialize the Gradio Agent UI.""" |
|
|
|
|
|
|
|
|
if model is None: |
|
|
api_key = os.environ.get("OPENROUTER_API_KEY") |
|
|
if not api_key: |
|
|
raise ValueError( |
|
|
"OPENROUTER_API_KEY environment variable is not set. " |
|
|
"Please set it or provide a model instance." |
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
model = ChatAnthropic( |
|
|
model='claude-sonnet-4-5-20250929', |
|
|
temperature=0.7, |
|
|
api_key='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' |
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if config is None: |
|
|
config = AgentConfig( |
|
|
max_steps=15, |
|
|
max_conversation_length=30, |
|
|
retry_attempts=3, |
|
|
timeout_seconds=1200, |
|
|
verbose=True |
|
|
) |
|
|
|
|
|
self.model = model |
|
|
self.config = config |
|
|
self.name = "AlphaGenome Agent Interface" |
|
|
self.description = "AlphaGenome Agent is automatically created by [Paper2Agent](https://github.com/jmiao24/Paper2Agent) to use [AlphaGenome](https://github.com/google-deepmind/alphagenome) and interpret DNA variants." |
|
|
|
|
|
def get_step_footnote(self, step_num: int, duration: float) -> str: |
|
|
"""Create a footnote for a step with timing information.""" |
|
|
return f'<span style="color: #888; font-size: 0.9em;">Step {step_num} | Duration: {duration:.2f}s</span>' |
|
|
|
|
|
def format_code_block(self, code: str) -> str: |
|
|
"""Format code as a Python code block.""" |
|
|
code = code.strip() |
|
|
if not code.startswith("```"): |
|
|
code = f"```python\n{code}\n```" |
|
|
return code |
|
|
|
|
|
def extract_content_parts(self, message_content: str) -> dict: |
|
|
"""Extract different parts from the message content.""" |
|
|
parts = { |
|
|
'thinking': '', |
|
|
'plan': None, |
|
|
'code': None, |
|
|
'solution': None, |
|
|
'error': None, |
|
|
'observation': None |
|
|
} |
|
|
|
|
|
|
|
|
thinking_pattern = r'^(.*?)(?=<(?:execute|solution|error|observation)|$)' |
|
|
thinking_match = re.match(thinking_pattern, message_content, re.DOTALL) |
|
|
if thinking_match: |
|
|
thinking = thinking_match.group(1).strip() |
|
|
|
|
|
plan_pattern = r'\d+\.\s*\[[^\]]*\]\s*[^\n]+(?:\n\d+\.\s*\[[^\]]*\]\s*[^\n]+)*' |
|
|
thinking = re.sub(plan_pattern, '', thinking).strip() |
|
|
|
|
|
thinking = re.sub(r'(^|\n)(Thinking:|Plan:)\s*', '\n', thinking).strip() |
|
|
|
|
|
thinking = re.sub(r'Thinking:\s*', '', thinking).strip() |
|
|
if thinking: |
|
|
parts['thinking'] = thinking |
|
|
|
|
|
|
|
|
plan_pattern = r'\d+\.\s*\[[^\]]*\]\s*[^\n]+(?:\n\d+\.\s*\[[^\]]*\]\s*[^\n]+)*' |
|
|
plan_match = re.search(plan_pattern, message_content) |
|
|
if plan_match: |
|
|
parts['plan'] = plan_match.group(0) |
|
|
|
|
|
|
|
|
code_match = re.search(r'<execute>(.*?)</execute>', message_content, re.DOTALL) |
|
|
if code_match: |
|
|
parts['code'] = code_match.group(1).strip() |
|
|
|
|
|
|
|
|
solution_match = re.search(r'<solution>(.*?)</solution>', message_content, re.DOTALL) |
|
|
if solution_match: |
|
|
parts['solution'] = solution_match.group(1).strip() |
|
|
|
|
|
|
|
|
error_match = re.search(r'<error>(.*?)</error>', message_content, re.DOTALL) |
|
|
if error_match: |
|
|
parts['error'] = error_match.group(1).strip() |
|
|
|
|
|
|
|
|
obs_match = re.search(r'<observation>(.*?)</observation>', message_content, re.DOTALL) |
|
|
if obs_match: |
|
|
parts['observation'] = obs_match.group(1).strip() |
|
|
|
|
|
return parts |
|
|
|
|
|
def truncate_output(self, text: str, max_lines: int = 20) -> str: |
|
|
"""Truncate output to specified number of lines.""" |
|
|
lines = text.split('\n') |
|
|
if len(lines) > max_lines: |
|
|
truncated = '\n'.join(lines[:max_lines]) |
|
|
truncated += f"\n... (truncated {len(lines) - max_lines} lines)" |
|
|
return truncated |
|
|
return text |
|
|
|
|
|
def stream_agent_response(self, agent: CodeAgent, query: str) -> Generator: |
|
|
"""Stream agent responses as Gradio ChatMessages with structured blocks.""" |
|
|
|
|
|
|
|
|
tools_dict = agent.get_all_tool_functions() |
|
|
agent.python_executor.send_functions(tools_dict) |
|
|
agent.python_executor.send_variables({}) |
|
|
|
|
|
|
|
|
input_state = { |
|
|
"messages": [HumanMessage(content=query)], |
|
|
"step_count": 0, |
|
|
"error_count": 0, |
|
|
"start_time": time.time(), |
|
|
"current_plan": None |
|
|
} |
|
|
|
|
|
|
|
|
displayed_reasoning = set() |
|
|
previous_plan = None |
|
|
step_timings = {} |
|
|
|
|
|
try: |
|
|
|
|
|
for state in agent.workflow_engine.graph.stream(input_state, stream_mode="values"): |
|
|
step_count = state.get("step_count", 0) |
|
|
error_count = state.get("error_count", 0) |
|
|
current_plan = state.get("current_plan") |
|
|
|
|
|
|
|
|
if step_count not in step_timings: |
|
|
step_timings[step_count] = time.time() |
|
|
|
|
|
message = state["messages"][-1] |
|
|
|
|
|
if isinstance(message, AIMessage): |
|
|
content = message.content |
|
|
parts = self.extract_content_parts(content) |
|
|
|
|
|
|
|
|
if step_count > 0: |
|
|
step_header = f"## Step {step_count}\n" |
|
|
yield gr.ChatMessage( |
|
|
role="assistant", |
|
|
content=step_header, |
|
|
metadata={"status": "done"} |
|
|
) |
|
|
|
|
|
|
|
|
if parts['thinking'] and len(parts['thinking']) > 20: |
|
|
thinking_text = parts['thinking'] |
|
|
|
|
|
thinking_text = re.sub(r'(Thinking:|Plan:)\s*', '', thinking_text).strip() |
|
|
|
|
|
thinking_text = re.sub(r'\n\s*\n\s*\n+', '\n\n', thinking_text).strip() |
|
|
|
|
|
thinking_text = '\n'.join(line.strip() for line in thinking_text.split('\n') if line.strip()) |
|
|
|
|
|
content_hash = hash(thinking_text) |
|
|
if content_hash not in displayed_reasoning and thinking_text: |
|
|
thinking_block = f"""<div style="background-color: #f8f9fa; border-left: 4px solid #6b7280; padding: 12px; margin: 10px 0; border-radius: 4px;"> |
|
|
<div style="font-weight: bold; color: #374151; margin-bottom: 8px;">🤔 Thinking</div> |
|
|
<div style="margin: 0; white-space: pre-line; font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; color: #000000; background-color: #ffffff; padding: 10px; border-radius: 4px; font-size: 14px; line-height: 1.6;">{thinking_text}</div> |
|
|
</div>""" |
|
|
yield gr.ChatMessage( |
|
|
role="assistant", |
|
|
content=thinking_block, |
|
|
metadata={"status": "done"} |
|
|
) |
|
|
displayed_reasoning.add(content_hash) |
|
|
|
|
|
|
|
|
if current_plan and current_plan != previous_plan: |
|
|
formatted_plan = current_plan.replace('[ ]', '☐').replace('[✓]', '✅').replace('[✗]', '❌') |
|
|
|
|
|
plan_block = f"""<div style="background-color: #f8f9fa; border-left: 4px solid #000000; padding: 12px; margin: 10px 0; border-radius: 4px;"> |
|
|
<div style="font-weight: bold; color: #000000; margin-bottom: 8px;">📋 Current Plan</div> |
|
|
<pre style="margin: 0; white-space: pre-wrap; font-family: 'Consolas', 'Monaco', 'Courier New', monospace; color: #000000; background-color: #ffffff; padding: 10px; border-radius: 4px; font-size: 14px; line-height: 1.6;">{formatted_plan}</pre> |
|
|
</div>""" |
|
|
yield gr.ChatMessage( |
|
|
role="assistant", |
|
|
content=plan_block, |
|
|
metadata={"status": "done"} |
|
|
) |
|
|
previous_plan = current_plan |
|
|
|
|
|
|
|
|
if parts['code']: |
|
|
|
|
|
code_block = f"""<div style="background-color: #f7fafc; border-left: 4px solid #48bb78; padding: 12px; margin: 10px 0; border-radius: 4px;"> |
|
|
<div style="font-weight: bold; color: #22543d; margin-bottom: 8px;">⚡ Executing Code</div> |
|
|
</div> |
|
|
|
|
|
```python |
|
|
{parts['code']} |
|
|
```""" |
|
|
yield gr.ChatMessage( |
|
|
role="assistant", |
|
|
content=code_block, |
|
|
metadata={"status": "done"} |
|
|
) |
|
|
|
|
|
|
|
|
if parts['observation']: |
|
|
truncated = self.truncate_output(parts['observation']) |
|
|
|
|
|
result_block = f"""<div style="background-color: #fef5e7; border-left: 4px solid #f6ad55; padding: 12px; margin: 10px 0; border-radius: 4px;"> |
|
|
<div style="font-weight: bold; color: #744210; margin-bottom: 8px;">📊 Execution Result</div> |
|
|
</div> |
|
|
|
|
|
``` |
|
|
{truncated} |
|
|
```""" |
|
|
yield gr.ChatMessage( |
|
|
role="assistant", |
|
|
content=result_block, |
|
|
metadata={"status": "done"} |
|
|
) |
|
|
|
|
|
|
|
|
if parts['solution']: |
|
|
solution_block = f"""<div style="background-color: #d1fae5; border-left: 4px solid #10b981; padding: 16px; margin: 10px 0; border-radius: 6px;"> |
|
|
<div style="font-weight: bold; color: #065f46; margin-bottom: 12px; font-size: 16px;">✅ Final Solution</div> |
|
|
<div class="solution-content" style="color: #1f2937 !important; background-color: #ffffff; padding: 16px; border-radius: 4px; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; font-size: 15px; line-height: 1.7; border: 1px solid #e5e7eb;"> |
|
|
|
|
|
{parts['solution']} |
|
|
|
|
|
</div> |
|
|
<style> |
|
|
.solution-content, .solution-content * {{ |
|
|
color: #1f2937 !important; |
|
|
background-color: transparent !important; |
|
|
}} |
|
|
.solution-content code, .solution-content pre {{ |
|
|
background-color: #f3f4f6 !important; |
|
|
color: #1f2937 !important; |
|
|
padding: 2px 6px; |
|
|
border-radius: 3px; |
|
|
font-family: 'Monaco', 'Menlo', 'Courier New', monospace; |
|
|
}} |
|
|
.solution-content pre {{ |
|
|
padding: 12px; |
|
|
overflow-x: auto; |
|
|
}} |
|
|
.solution-content table {{ |
|
|
border-collapse: collapse; |
|
|
width: 100%; |
|
|
margin: 10px 0; |
|
|
background-color: #ffffff !important; |
|
|
}} |
|
|
.solution-content td, .solution-content th {{ |
|
|
border: 1px solid #d1d5db; |
|
|
padding: 8px; |
|
|
color: #1f2937 !important; |
|
|
background-color: #ffffff !important; |
|
|
}} |
|
|
.solution-content th {{ |
|
|
background-color: #f3f4f6 !important; |
|
|
font-weight: 600; |
|
|
}} |
|
|
.solution-content p, .solution-content div, .solution-content span {{ |
|
|
background-color: transparent !important; |
|
|
}} |
|
|
</style> |
|
|
</div>""" |
|
|
yield gr.ChatMessage( |
|
|
role="assistant", |
|
|
content=solution_block, |
|
|
metadata={"status": "done"} |
|
|
) |
|
|
|
|
|
|
|
|
if parts['error']: |
|
|
error_block = f"""<div style="background-color: #fed7d7; border-left: 4px solid #fc8181; padding: 12px; margin: 10px 0; border-radius: 4px;"> |
|
|
<div style="font-weight: bold; color: #742a2a; margin-bottom: 8px;">⚠️ Error</div> |
|
|
<div style="color: #742a2a;">{parts['error']}</div> |
|
|
</div>""" |
|
|
yield gr.ChatMessage( |
|
|
role="assistant", |
|
|
content=error_block, |
|
|
metadata={"status": "done"} |
|
|
) |
|
|
|
|
|
|
|
|
if parts['observation'] and step_count in step_timings: |
|
|
duration = time.time() - step_timings[step_count] |
|
|
footnote = f'<div style="color: #718096; font-size: 0.875em; margin-top: 8px;">Step {step_count} | Duration: {duration:.2f}s</div>' |
|
|
yield gr.ChatMessage( |
|
|
role="assistant", |
|
|
content=footnote, |
|
|
metadata={"status": "done"} |
|
|
) |
|
|
|
|
|
|
|
|
if step_count > 0: |
|
|
yield gr.ChatMessage( |
|
|
role="assistant", |
|
|
content='<hr style="border: none; border-top: 1px solid #e2e8f0; margin: 20px 0;">', |
|
|
metadata={"status": "done"} |
|
|
) |
|
|
|
|
|
except Exception as e: |
|
|
error_block = f"""<div style="background-color: #fed7d7; border-left: 4px solid #fc8181; padding: 12px; margin: 10px 0; border-radius: 4px;"> |
|
|
<div style="font-weight: bold; color: #742a2a; margin-bottom: 8px;">💥 Critical Error</div> |
|
|
<div style="color: #742a2a;">Error during agent execution: {str(e)}</div> |
|
|
</div>""" |
|
|
yield gr.ChatMessage( |
|
|
role="assistant", |
|
|
content=error_block, |
|
|
metadata={"status": "done"} |
|
|
) |
|
|
|
|
|
def interact_with_agent(self, prompt: str, api_key: str, messages: list, session_state: dict) -> Generator: |
|
|
"""Handle interaction with the agent.""" |
|
|
|
|
|
|
|
|
original_prompt = prompt |
|
|
|
|
|
|
|
|
if api_key and api_key.strip(): |
|
|
prompt = f"{prompt} My API key is: {api_key.strip()}." |
|
|
|
|
|
|
|
|
if "agent" not in session_state: |
|
|
session_state["agent"] = CodeAgent( |
|
|
model=self.model, |
|
|
config=self.config, |
|
|
use_tool_manager=True, |
|
|
use_tool_selection=False |
|
|
) |
|
|
|
|
|
|
|
|
mcp_config_path = "./mcp_config.yaml" |
|
|
if os.path.exists(mcp_config_path): |
|
|
try: |
|
|
session_state["agent"].add_mcp(mcp_config_path) |
|
|
print(f"✅ Loaded MCP tools from {mcp_config_path}") |
|
|
except Exception as e: |
|
|
print(f"⚠️ Could not load MCP tools: {e}") |
|
|
|
|
|
agent = session_state["agent"] |
|
|
|
|
|
try: |
|
|
|
|
|
messages.append(gr.ChatMessage(role="user", content=original_prompt, metadata={"status": "done"})) |
|
|
yield messages |
|
|
|
|
|
|
|
|
messages.append(gr.ChatMessage(role="assistant", content="🤔 Processing...", metadata={"status": "pending"})) |
|
|
yield messages |
|
|
|
|
|
|
|
|
messages.pop() |
|
|
|
|
|
|
|
|
for msg in self.stream_agent_response(agent, prompt): |
|
|
messages.append(msg) |
|
|
yield messages |
|
|
|
|
|
except Exception as e: |
|
|
messages.append( |
|
|
gr.ChatMessage( |
|
|
role="assistant", |
|
|
content=f"Error: {str(e)}", |
|
|
metadata={"title": "💥 Error", "status": "done"} |
|
|
) |
|
|
) |
|
|
yield messages |
|
|
|
|
|
def create_app(self): |
|
|
"""Create the Gradio app with sidebar layout.""" |
|
|
|
|
|
|
|
|
modern_theme = gr.themes.Monochrome( |
|
|
font=fonts.GoogleFont("Inter"), |
|
|
font_mono=fonts.GoogleFont("JetBrains Mono") |
|
|
) |
|
|
|
|
|
with gr.Blocks(theme=modern_theme, fill_height=True, title=self.name, css=""" |
|
|
/* Hide Gradio footer */ |
|
|
.footer {display: none !important;} |
|
|
footer {display: none !important;} |
|
|
.gradio-footer {display: none !important;} |
|
|
#footer {display: none !important;} |
|
|
[class*="footer"] {display: none !important;} |
|
|
[id*="footer"] {display: none !important;} |
|
|
.block.svelte-1scc9gv {display: none !important;} |
|
|
.built-with-gradio {display: none !important;} |
|
|
.gradio-container footer {display: none !important;} |
|
|
""") as demo: |
|
|
|
|
|
demo.load(js=""" |
|
|
() => { |
|
|
document.body.classList.remove('dark'); |
|
|
document.querySelector('gradio-app').classList.remove('dark'); |
|
|
const url = new URL(window.location); |
|
|
url.searchParams.set('__theme', 'light'); |
|
|
window.history.replaceState({}, '', url); |
|
|
|
|
|
// Hide footer elements |
|
|
setTimeout(() => { |
|
|
const footers = document.querySelectorAll('footer, .footer, .gradio-footer, #footer, [class*="footer"], [id*="footer"], .built-with-gradio'); |
|
|
footers.forEach(footer => footer.style.display = 'none'); |
|
|
|
|
|
// Also hide any Gradio logo/branding |
|
|
const brandingElements = document.querySelectorAll('a[href*="gradio"], .gradio-logo, [alt*="gradio"]'); |
|
|
brandingElements.forEach(el => el.style.display = 'none'); |
|
|
}, 100); |
|
|
} |
|
|
""") |
|
|
|
|
|
session_state = gr.State({}) |
|
|
stored_messages = gr.State([]) |
|
|
|
|
|
with gr.Row(): |
|
|
|
|
|
with gr.Column(scale=1): |
|
|
gr.Markdown(f"# {self.name}") |
|
|
gr.Markdown(f"> {self.description}") |
|
|
|
|
|
gr.Markdown("---") |
|
|
|
|
|
|
|
|
with gr.Group(): |
|
|
gr.Markdown("**AlphaGenome API Key**") |
|
|
api_key_input = gr.Textbox( |
|
|
label="API Key", |
|
|
type="password", |
|
|
placeholder="Enter your AlphaGenome API key", |
|
|
value="AIzaSyD1USDNy9WqfIROICB3FWI1wJHmkO2z21U" |
|
|
) |
|
|
|
|
|
|
|
|
with gr.Group(): |
|
|
gr.Markdown("**Your Request**") |
|
|
text_input = gr.Textbox( |
|
|
lines=4, |
|
|
label="Query", |
|
|
placeholder="Enter your query here and press Enter or click Submit", |
|
|
value="""Use AlphaGenome MCP to analyze heart gene expression data to identify the causal gene |
|
|
for the variant chr11:116837649:T>G, associated with Hypoalphalipoproteinemia.""" |
|
|
) |
|
|
submit_btn = gr.Button("🚀 Submit", variant="primary", size="lg") |
|
|
|
|
|
|
|
|
with gr.Accordion("⚙️ Configuration", open=False): |
|
|
max_steps_input = gr.Slider( |
|
|
label="Max Steps", |
|
|
minimum=5, |
|
|
maximum=50, |
|
|
value=self.config.max_steps, |
|
|
step=1 |
|
|
) |
|
|
|
|
|
temperature_input = gr.Slider( |
|
|
label="Temperature", |
|
|
minimum=0.0, |
|
|
maximum=1.0, |
|
|
value=0.7, |
|
|
step=0.1 |
|
|
) |
|
|
|
|
|
apply_config_btn = gr.Button("Apply Configuration", size="sm") |
|
|
|
|
|
|
|
|
with gr.Accordion("📚 Example Queries", open=False): |
|
|
gr.Examples( |
|
|
examples=[ |
|
|
["What's the quantile score of chr3:197081044:TACTC>T on splice junction in Artery (tibial)?"], |
|
|
["What are the raw and quantile scores of the variant chr3:120280774:G>T for chromatin accessibility in the GM12878 cell line?"], |
|
|
], |
|
|
inputs=text_input, |
|
|
) |
|
|
|
|
|
gr.Markdown("---") |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
with gr.Column(scale=3): |
|
|
chatbot = gr.Chatbot( |
|
|
label="Agent Conversation", |
|
|
type="messages", |
|
|
height=700, |
|
|
show_copy_button=True, |
|
|
avatar_images=( |
|
|
None, |
|
|
"images.png" |
|
|
), |
|
|
latex_delimiters=[ |
|
|
{"left": r"$$", "right": r"$$", "display": True}, |
|
|
{"left": r"$", "right": r"$", "display": False}, |
|
|
], |
|
|
render_markdown=True, |
|
|
sanitize_html=False, |
|
|
) |
|
|
|
|
|
with gr.Row(): |
|
|
clear_btn = gr.Button("🗑️ Clear", size="sm") |
|
|
|
|
|
|
|
|
stop_btn = gr.Button("⏹️ Stop", size="sm", variant="stop") |
|
|
|
|
|
|
|
|
def update_config(max_steps, temperature, session_state): |
|
|
"""Update agent configuration.""" |
|
|
if "agent" in session_state: |
|
|
agent = session_state["agent"] |
|
|
agent.config.max_steps = max_steps |
|
|
if hasattr(agent.model, 'temperature'): |
|
|
agent.model.temperature = temperature |
|
|
return "Configuration updated!" |
|
|
|
|
|
def clear_chat(): |
|
|
"""Clear the chat.""" |
|
|
return [], [] |
|
|
|
|
|
|
|
|
text_input.submit( |
|
|
lambda x: ("", gr.Button(interactive=False)), |
|
|
[text_input], |
|
|
[text_input, submit_btn] |
|
|
).then( |
|
|
self.interact_with_agent, |
|
|
[stored_messages, api_key_input, chatbot, session_state], |
|
|
[chatbot] |
|
|
).then( |
|
|
lambda: gr.Button(interactive=True), |
|
|
None, |
|
|
[submit_btn] |
|
|
) |
|
|
|
|
|
submit_btn.click( |
|
|
lambda x: (x, "", gr.Button(interactive=False)), |
|
|
[text_input], |
|
|
[stored_messages, text_input, submit_btn] |
|
|
).then( |
|
|
self.interact_with_agent, |
|
|
[stored_messages, api_key_input, chatbot, session_state], |
|
|
[chatbot] |
|
|
).then( |
|
|
lambda: gr.Button(interactive=True), |
|
|
None, |
|
|
[submit_btn] |
|
|
) |
|
|
|
|
|
apply_config_btn.click( |
|
|
update_config, |
|
|
[max_steps_input, temperature_input, session_state], |
|
|
None |
|
|
) |
|
|
|
|
|
clear_btn.click( |
|
|
clear_chat, |
|
|
None, |
|
|
[chatbot, stored_messages] |
|
|
) |
|
|
|
|
|
return demo |
|
|
|
|
|
def launch(self, share: bool = False, **kwargs): |
|
|
"""Launch the Gradio app.""" |
|
|
app = self.create_app() |
|
|
|
|
|
kwargs.setdefault('server_name', '0.0.0.0') |
|
|
kwargs.setdefault('server_port', 7860) |
|
|
kwargs.setdefault('show_error', True) |
|
|
|
|
|
app.queue(max_size=10).launch( |
|
|
share=share, |
|
|
show_api=False, |
|
|
favicon_path=None, |
|
|
**kwargs |
|
|
) |
|
|
|
|
|
|
|
|
if __name__ == "__main__": |
|
|
|
|
|
if not os.environ.get("OPENROUTER_API_KEY"): |
|
|
print("\n⚠️ Error: OPENROUTER_API_KEY environment variable is not set!") |
|
|
print("\nPlease set it using one of these methods:") |
|
|
print("1. Export it in your shell:") |
|
|
print(" export OPENROUTER_API_KEY='your-api-key-here'") |
|
|
print("\n2. Create a .env file in the current directory with:") |
|
|
print(" OPENROUTER_API_KEY=your-api-key-here") |
|
|
print("\n3. Or provide your own model instance when initializing GradioAgentUI") |
|
|
sys.exit(1) |
|
|
|
|
|
try: |
|
|
|
|
|
ui = GradioAgentUI() |
|
|
|
|
|
|
|
|
mcp_config_path = "./mcp_config.yaml" |
|
|
if os.path.exists(mcp_config_path): |
|
|
print(f"Found MCP config at {mcp_config_path}") |
|
|
|
|
|
ui.launch(share=False, quiet=False) |
|
|
except Exception as e: |
|
|
print(f"\n❌ Error starting Gradio interface: {e}") |
|
|
sys.exit(1) |