Agent-4 / agent.py
Erog0291's picture
Rename Agent.py to agent.py
e29f99a verified
Raw History Blame Contribute Delete
6.85 kB
"""
STEW β€” Core Reasoning & Execution Engine
Uses OpenRouter (OpenAI-compatible) with multi-model support,
tool calling, and per-user memory.
"""
import json
from openai import OpenAI
from config import Config
from memory import ConversationMemory
from tools import TOOL_DEFINITIONS, execute_tool
# ── System prompt from STEW architecture document ─────────────────────────────
SYSTEM_PROMPT = """You are STEW β€” a next-generation autonomous intelligence system.
You are NOT a simple chatbot. You are a reasoning, planning, execution, and
automation system designed to help humans build software, research information,
automate workflows, analyse data, and create intelligent systems safely and ethically.
CORE MINDSET:
- Think deeply before acting
- Break problems into structured steps
- Make intelligent decisions based on evidence
- Adapt dynamically to changing tasks
- Solve problems proactively
- Operate transparently and ethically
RESPONSE EXECUTION MODEL (internal):
1. Analyse the request
2. Plan the approach
3. Execute step by step
4. Reflect on quality
5. Improve if needed
CAPABILITIES:
- Write production-grade Python, FastAPI, React, Next.js systems
- Design software architectures and databases
- Automate workflows and manage multi-step projects
- Search the web for current information
- Interact with Notion, Slack, Gmail, Google Drive & Calendar
- Coordinate multiple tools in sequence to complete complex tasks
SAFETY & ETHICS:
- Always operate transparently
- Respect privacy and security boundaries
- Require authorization for sensitive actions
- Never perform illegal or deceptive actions
- Protect user data at all times
PERSONALITY:
Intelligent β€’ Calm β€’ Analytical β€’ Strategic β€’ Confident β€’ Proactive
When using tools, explain what you're doing before and after each tool call.
Format code in markdown code blocks. Be concise but thorough."""
class STEWAgent:
def __init__(self):
self.client = OpenAI(
api_key=Config.OPENROUTER_API_KEY,
base_url=Config.OPENROUTER_BASE_URL,
default_headers={
"HTTP-Referer": Config.OPENROUTER_SITE_URL,
"X-Title": Config.OPENROUTER_APP_NAME,
},
)
self.memory = ConversationMemory()
# ── Public chat interface ──────────────────────────────────────────────────
def chat(
self,
message: str,
history: list | None = None,
model_key: str = Config.DEFAULT_MODEL,
user_id: str = "default",
stream: bool = False,
) -> str:
"""
Send a message to STEW and get a response.
Handles tool calling in an agentic loop (up to 5 rounds).
"""
model_id = Config.MODELS.get(model_key, Config.MODELS[Config.DEFAULT_MODEL])
# Build messages array
messages = [{"role": "system", "content": SYSTEM_PROMPT}]
# Inject memory context if available
ctx = self.memory.get_context_string(user_id)
if ctx:
messages.append({"role": "system", "content": ctx})
# Add stored history
messages.extend(self.memory.get_messages(user_id))
# Add Gradio-style history if passed directly (web UI)
if history:
for human, assistant in history:
if human:
messages.append({"role": "user", "content": human})
if assistant:
messages.append({"role": "assistant", "content": assistant})
# Add current message
messages.append({"role": "user", "content": message})
# ── Agentic tool-calling loop ─────────────────────────────────────────
final_reply = ""
for _round in range(5): # max 5 tool rounds
response = self.client.chat.completions.create(
model=model_id,
messages=messages,
tools=TOOL_DEFINITIONS,
tool_choice="auto",
max_tokens=Config.MAX_RESPONSE_TOKENS,
)
choice = response.choices[0]
msg = choice.message
finish = choice.finish_reason
# Append assistant turn to messages
messages.append(msg.model_dump(exclude_none=True))
if finish == "tool_calls" and msg.tool_calls:
# Execute each tool call
for tc in msg.tool_calls:
result = execute_tool(tc.function.name, tc.function.arguments)
messages.append({
"role": "tool",
"tool_call_id": tc.id,
"content": result,
})
# Loop again to let the model respond with tool results
continue
# Normal text response
final_reply = msg.content or ""
break
# Store in memory
self.memory.add(user_id, message, final_reply)
return final_reply
# ── Streaming variant (for Telegram or future use) ────────────────────────
def chat_stream(
self,
message: str,
model_key: str = Config.DEFAULT_MODEL,
user_id: str = "default",
):
"""Yields text chunks as they arrive (no tool calls in stream mode)."""
model_id = Config.MODELS.get(model_key, Config.MODELS[Config.DEFAULT_MODEL])
messages = [{"role": "system", "content": SYSTEM_PROMPT}]
messages.extend(self.memory.get_messages(user_id))
messages.append({"role": "user", "content": message})
full_reply = ""
stream = self.client.chat.completions.create(
model=model_id,
messages=messages,
max_tokens=Config.MAX_RESPONSE_TOKENS,
stream=True,
)
for chunk in stream:
delta = chunk.choices[0].delta.content or ""
full_reply += delta
yield delta
self.memory.add(user_id, message, full_reply)
# ── Utility ───────────────────────────────────────────────────────────────
def clear_memory(self, user_id: str) -> str:
self.memory.clear(user_id)
return "πŸ—‘οΈ Memory cleared."
def memory_summary(self, user_id: str) -> str:
s = self.memory.summary(user_id)
return f"πŸ“Š Memory: {s['turns']} turns stored, {s['facts']} known facts."