Spaces:
Paused
Paused
| # -*- coding: utf-8 -*- | |
| """AgentScope chat — Gradio app for Hugging Face Spaces (ZeroGPU). | |
| This is a single-file Gradio app that runs an AgentScope ``Agent`` | |
| in-process. No FastAPI, no React build — just Gradio + AgentScope. | |
| That makes it eligible for HF Spaces' free ``zero-a10g`` hardware | |
| (which requires the Gradio SDK). | |
| Configuration is via HF Spaces **Secrets** (or local env vars): | |
| OPENAI_API_KEY — required, your OpenAI (or compatible) key | |
| OPENAI_BASE_URL — optional, for OpenAI-compatible endpoints | |
| MODEL_NAME — optional, defaults to "gpt-4o-mini" | |
| SYSTEM_PROMPT — optional, defaults to a helpful-assistant prompt | |
| AGENT_NAME — optional, defaults to "AgentScope" | |
| WORKDIR — optional, defaults to /tmp/agentscope-workdir | |
| Usage on HF Spaces: | |
| 1. Create a Space with SDK = Gradio | |
| 2. Set hardware = zero-a10g (free for Gradio Spaces) | |
| 3. Add OPENAI_API_KEY as a Secret (and optionally OPENAI_BASE_URL, | |
| MODEL_NAME, SYSTEM_PROMPT) | |
| 4. Push this app.py + requirements.txt + README.md | |
| 5. The Space builds and runs on port 7860 automatically. | |
| """ | |
| from __future__ import annotations | |
| import asyncio | |
| import os | |
| import sys | |
| import traceback | |
| from pathlib import Path | |
| from typing import Any | |
| import gradio as gr | |
| # AgentScope — installed from the local source via requirements.txt | |
| from agentscope.agent import Agent | |
| from agentscope.credential import OpenAICredential | |
| from agentscope.message import Msg, TextBlock | |
| from agentscope.model import OpenAIChatModel | |
| from agentscope.tool import ( | |
| Toolkit, | |
| Bash, | |
| Read, | |
| Write, | |
| Edit, | |
| Glob, | |
| Grep, | |
| ) | |
| # --------------------------------------------------------------------------- | |
| # Configuration (HF Secrets / env vars) | |
| # --------------------------------------------------------------------------- | |
| OPENAI_API_KEY = os.environ.get("OPENAI_API_KEY", "") | |
| OPENAI_BASE_URL = os.environ.get("OPENAI_BASE_URL", "") or None | |
| MODEL_NAME = os.environ.get("MODEL_NAME", "gpt-4o-mini") | |
| SYSTEM_PROMPT = os.environ.get( | |
| "SYSTEM_PROMPT", | |
| ( | |
| "You're AgentScope, a helpful AI assistant with access to a " | |
| "workspace directory. You can use the bash, read, write, edit, " | |
| "glob, and grep tools to help the user with coding, file " | |
| "operations, and research tasks. Always explain what you're " | |
| "doing before calling tools." | |
| ), | |
| ) | |
| AGENT_NAME = os.environ.get("AGENT_NAME", "AgentScope") | |
| WORKDIR = os.environ.get("WORKDIR", "/tmp/agentscope-workdir") | |
| # Ensure the workdir exists so the Bash tool has a CWD | |
| Path(WORKDIR).mkdir(parents=True, exist_ok=True) | |
| # --------------------------------------------------------------------------- | |
| # Build the agent once at module load (HF Spaces reuses the process) | |
| # --------------------------------------------------------------------------- | |
| def _build_agent() -> Agent | None: | |
| """Build the AgentScope agent. Returns None if no API key is set.""" | |
| if not OPENAI_API_KEY: | |
| return None | |
| credential = OpenAICredential( | |
| api_key=OPENAI_API_KEY, | |
| base_url=OPENAI_BASE_URL, | |
| ) | |
| model = OpenAIChatModel( | |
| credential=credential, | |
| model=MODEL_NAME, | |
| stream=True, | |
| context_size=128_000, | |
| parameters=OpenAIChatModel.Parameters( | |
| temperature=0.7, | |
| ), | |
| ) | |
| # Built-in agent tools operating on WORKDIR. LocalBackend is the | |
| # default backend; we pass cwd=WORKDIR to Bash so commands run there. | |
| toolkit = Toolkit( | |
| tools=[ | |
| Bash(cwd=WORKDIR), | |
| Read(), | |
| Write(), | |
| Edit(), | |
| Glob(), | |
| Grep(), | |
| ], | |
| ) | |
| return Agent( | |
| name=AGENT_NAME, | |
| system_prompt=SYSTEM_PROMPT, | |
| model=model, | |
| toolkit=toolkit, | |
| ) | |
| _agent: Agent | None = None | |
| try: | |
| _agent = _build_agent() | |
| except Exception as exc: # pragma: no cover | |
| print(f"[app] Failed to build agent: {exc}", file=sys.stderr) | |
| traceback.print_exc(file=sys.stderr) | |
| # --------------------------------------------------------------------------- | |
| # Chat handler — bridges Gradio's chat interface with AgentScope | |
| # --------------------------------------------------------------------------- | |
| def _extract_text(msg: Msg) -> str: | |
| """Pull visible text out of an AgentScope Msg (skips tool blocks).""" | |
| parts: list[str] = [] | |
| for block in msg.content: | |
| if hasattr(block, "text") and block.type == "text": | |
| parts.append(block.text) | |
| return "\n".join(parts) if parts else "" | |
| async def _reply(message: str, history: list) -> str: | |
| """Run the agent on a single user turn and return the assistant text.""" | |
| if _agent is None: | |
| return ( | |
| "⚠️ No `OPENAI_API_KEY` set. Add it as a Secret in the Space " | |
| "settings → Restart the Space → try again." | |
| ) | |
| user_msg = Msg( | |
| name="user", | |
| content=[TextBlock(text=message)], | |
| role="user", | |
| ) | |
| # Agent.reply is async — runs the full ReAct loop and returns the | |
| # final assistant message (tool calls happen internally). | |
| final_msg = await _agent.reply(inputs=user_msg) | |
| return _extract_text(final_msg) or "(no response)" | |
| def chat(message: str, history: list[dict[str, str]]) -> Any: | |
| """Gradio chat handler (sync wrapper around the async reply).""" | |
| if not message.strip(): | |
| return "", history | |
| try: | |
| reply_text = asyncio.run(_reply(message, history)) | |
| except RuntimeError: | |
| # Event loop already running (shouldn't happen in Gradio sync | |
| # handler, but fall back gracefully if it does). | |
| loop = asyncio.new_event_loop() | |
| try: | |
| reply_text = loop.run_until_complete(_reply(message, history)) | |
| finally: | |
| loop.close() | |
| except Exception as exc: | |
| reply_text = f"❌ Error: {exc}\n```\n{traceback.format_exc()}\n```" | |
| history.append({"role": "user", "content": message}) | |
| history.append({"role": "assistant", "content": reply_text}) | |
| return "", history | |
| # --------------------------------------------------------------------------- | |
| # Gradio UI | |
| # --------------------------------------------------------------------------- | |
| CSS = """ | |
| .gradio-container { max-width: 900px !important; margin: auto; } | |
| .footer { text-align: center; color: #888; font-size: 12px; margin-top: 16px; } | |
| .status-ok { color: #16a34a; font-weight: 600; } | |
| .status-bad { color: #dc2626; font-weight: 600; } | |
| """ | |
| with gr.Blocks( | |
| title="AgentScope Chat", | |
| theme=gr.themes.Soft(primary_hue="indigo"), | |
| css=CSS, | |
| ) as demo: | |
| gr.Markdown( | |
| "# 🤖 AgentScope Chat\n" | |
| "Multi-agent platform with built-in tools " | |
| "(bash, read, write, edit, glob, grep)." | |
| ) | |
| if _agent is None: | |
| gr.Markdown( | |
| "### ⚠️ Setup required\n" | |
| "No `OPENAI_API_KEY` found. To enable chat:\n" | |
| "1. Open this Space's **Settings** tab\n" | |
| "2. Scroll to **Variables and secrets** → **New secret**\n" | |
| "3. Add `OPENAI_API_KEY` with your key " | |
| "(optionally `OPENAI_BASE_URL`, `MODEL_NAME`, `SYSTEM_PROMPT`)\n" | |
| "4. Click **Restart Space**\n\n" | |
| "Supports OpenAI and any OpenAI-compatible endpoint " | |
| "(set `OPENAI_BASE_URL` to your provider's `/v1` URL)." | |
| ) | |
| else: | |
| gr.Markdown( | |
| f"**Status:** <span class='status-ok'>● Connected</span> " | |
| f"**Model:** `{MODEL_NAME}` " | |
| f"**Endpoint:** `{OPENAI_BASE_URL or 'https://api.openai.com/v1'}`" | |
| ) | |
| gr.ChatInterface( | |
| fn=chat, | |
| type="messages", | |
| chatbot=gr.Chatbot(height=520, type="messages"), | |
| textbox=gr.Textbox( | |
| placeholder="Ask me anything — I can run bash, edit files, " | |
| "search code, and more...", | |
| container=False, | |
| scale=7, | |
| ), | |
| submit_btn="Send", | |
| stop_btn="Stop", | |
| retry_btn="Retry", | |
| undo_btn="Undo", | |
| clear_btn="Clear", | |
| examples=[ | |
| "What tools do you have?", | |
| "Create a hello.py file with a print statement, then run it.", | |
| "Find all .py files in /tmp and count lines in each.", | |
| "Explain how MCP (Model Context Protocol) works.", | |
| ], | |
| ) | |
| gr.Markdown( | |
| "<div class='footer'>" | |
| "Built with " | |
| "<a href='https://github.com/agentscope-ai/agentscope' " | |
| "target='_blank'>AgentScope</a> · " | |
| "Running on Hugging Face Spaces (ZeroGPU)" | |
| "</div>" | |
| ) | |
| # HF Spaces runs `python app.py` and expects the app on port 7860 | |
| if __name__ == "__main__": | |
| demo.launch(server_name="0.0.0.0", server_port=7860, show_error=True) | |