agentscope-chat / app.py
Akshay66777's picture
AgentScope Gradio chat app — ZeroGPU-ready
9792ea7 verified
Raw
History Blame Contribute Delete
8.79 kB
# -*- 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> &nbsp; "
f"**Model:** `{MODEL_NAME}` &nbsp; "
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)