newtrone / agent.py
nulltron's picture
Upload 62 files
c2df2b9 verified
Raw
History Blame Contribute Delete
5.68 kB
from pathlib import Path
from typing import Any, Dict, List, Optional
from langchain_openai import ChatOpenAI
from langchain_core.messages import AIMessage, HumanMessage, SystemMessage
from langchain_core.tools import tool
from langgraph.prebuilt import create_react_agent
def _make_file_tools(workspace: Path):
workspace = workspace.resolve()
@tool
def read_file(path: str) -> str:
"""Read the contents of a file from the workspace."""
full = (workspace / path).resolve()
if not str(full).startswith(str(workspace)):
return "Error: path outside workspace."
if not full.exists():
return f"Error: file not found: {path}"
return full.read_text(encoding="utf-8", errors="replace")
@tool
def write_file(path: str, content: str) -> str:
"""Write content to a file in the workspace (creates parent dirs)."""
full = (workspace / path).resolve()
if not str(full).startswith(str(workspace)):
return "Error: path outside workspace."
full.parent.mkdir(parents=True, exist_ok=True)
full.write_text(content, encoding="utf-8")
return f"Written: {path}"
@tool
def list_files(path: str = ".") -> str:
"""List files and directories under a path in the workspace."""
full = (workspace / path).resolve()
if not str(full).startswith(str(workspace)):
return "Error: path outside workspace."
if not full.exists():
return f"Error: path not found: {path}"
items = []
for p in sorted(full.iterdir()):
tag = "[DIR]" if p.is_dir() else "[FILE]"
items.append(f"{tag} {p.relative_to(workspace)}")
return "\n".join(items) if items else "(empty)"
@tool
def delete_file(path: str) -> str:
"""Delete a file or empty directory in the workspace."""
full = (workspace / path).resolve()
if not str(full).startswith(str(workspace)):
return "Error: path outside workspace."
if not full.exists():
return f"Error: not found: {path}"
if full.is_file():
full.unlink()
return f"Deleted file: {path}"
if full.is_dir():
full.rmdir()
return f"Deleted empty directory: {path}"
return f"Error: {path} is neither a file nor empty directory."
return [read_file, write_file, list_files, delete_file]
def _build_system_message(workspace: Path) -> SystemMessage:
return SystemMessage(
content=(
f"Your workspace is at {workspace}.\n"
"You have file tools to read, write, list, and delete files here.\n"
"You have built-in web search capabilities through your model.\n\n"
"=== LEAN WORKSPACE RULES ===\n"
"1. KEEP ONLY HIGH-VALUE FILES: Retain only final artifacts the user would want\n"
" (reports, generated scripts, datasets, or files the user explicitly asked for).\n"
"2. AUTO-DELETE JUNK: Before finishing your response, use 'delete_file' to remove\n"
" all intermediate scratch files, .tmp files, raw HTML dumps, logs, or any file\n"
" that is NOT a final deliverable.\n"
"3. OVERWRITE — NEVER DUPLICATE: If you need to update a file, overwrite it in place\n"
" using 'write_file' with the same path. Never create versioned copies (e.g.,\n"
" report_v1.txt, report_v2.txt) unless the user explicitly asks for a new version.\n\n"
"When the user asks about their files or folder contents, use the file tools."
)
)
async def run_agent(
api_key: str,
model: str,
provider: str,
workspace: Path,
user_message: str,
chat_history: List[Dict[str, str]],
) -> Dict[str, Any]:
tools = _make_file_tools(workspace)
if provider == "gemini":
from langchain_google_genai import ChatGoogleGenerativeAI
llm = ChatGoogleGenerativeAI(
model=model or "gemini-2.0-flash",
google_api_key=api_key,
temperature=0.7,
)
else:
llm = ChatOpenAI(
model=model or "perplexity/llama-3-sonar-large-32k-online",
openai_api_key=api_key,
base_url="https://openrouter.ai/api/v1",
default_headers={
"HTTP-Referer": "https://huggingface.co/spaces/react-agent",
"X-Title": "React Agent",
},
temperature=0.7,
)
agent = create_react_agent(model=llm, tools=tools)
messages = [_build_system_message(workspace)]
for msg in chat_history:
role = msg.get("role", "")
content = msg.get("content", "")
if role == "user":
messages.append(HumanMessage(content=content))
elif role == "assistant":
messages.append(AIMessage(content=content))
messages.append(HumanMessage(content=user_message))
result = await agent.ainvoke({"messages": messages})
agent_response = ""
for msg in reversed(result.get("messages", [])):
if isinstance(msg, AIMessage) and msg.content:
agent_response = msg.content
break
updated_chat_history = []
for msg in result.get("messages", []):
if isinstance(msg, HumanMessage):
updated_chat_history.append({"role": "user", "content": msg.content})
elif isinstance(msg, AIMessage):
updated_chat_history.append({"role": "assistant", "content": msg.content})
return {
"agent_response": agent_response,
"updated_chat_history": updated_chat_history,
}