async mcp implementation in agentloop - buggy
This commit is contained in:
parent
2a37ae3aa4
commit
0a329139ca
@ -22,7 +22,12 @@ import sys
|
||||
from pathlib import Path
|
||||
|
||||
import requests
|
||||
import httpx
|
||||
from dotenv import load_dotenv
|
||||
from backend.agent.mcp_server_adapter import MCPToolRAGAdapter
|
||||
|
||||
# ── mcp server initialization ────────────────────────────────────────────────────────────────
|
||||
adapter = MCPToolRAGAdapter()
|
||||
|
||||
load_dotenv()
|
||||
|
||||
@ -44,173 +49,216 @@ MAX_HISTORY_CHARS = 80_000
|
||||
# Each tool is a plain Python function decorated with @register_tool.
|
||||
# The decorator adds the function to TOOL_REGISTRY so the dispatcher
|
||||
# can call it by name at runtime.
|
||||
#
|
||||
#TOOL_REGISTRY: dict[str, callable] = {}
|
||||
#
|
||||
#
|
||||
#def register_tool(func):
|
||||
# """Decorator – adds a function to the global tool registry."""
|
||||
# TOOL_REGISTRY[func.__name__] = func
|
||||
# return func
|
||||
#
|
||||
#
|
||||
#@register_tool
|
||||
#def read_file(path: str) -> str:
|
||||
# """Read a .py or .txt file from the workspace and return its contents."""
|
||||
# target = (WORKSPACE / path).resolve()
|
||||
# if not str(target).startswith(str(WORKSPACE.resolve())):
|
||||
# return "ERROR: path is outside the workspace."
|
||||
# if not target.exists():
|
||||
# return f"ERROR: file '{path}' not found."
|
||||
# if target.suffix not in (".py", ".txt"):
|
||||
# return f"ERROR: can only read .py and .txt files, got '{target.suffix}'."
|
||||
# return target.read_text()
|
||||
#
|
||||
#
|
||||
#@register_tool
|
||||
#def write_file(path: str, content: str) -> str:
|
||||
# """Write content to a .py or .txt file in the workspace."""
|
||||
# target = (WORKSPACE / path).resolve()
|
||||
# if not str(target).startswith(str(WORKSPACE.resolve())):
|
||||
# return "ERROR: path is outside the workspace."
|
||||
# if target.suffix not in (".py", ".txt"):
|
||||
# return f"ERROR: can only write .py and .txt files, got '{target.suffix}'."
|
||||
# target.parent.mkdir(parents=True, exist_ok=True)
|
||||
# target.write_text(content)
|
||||
# return f"OK: wrote {len(content)} chars to {path}."
|
||||
#
|
||||
#
|
||||
#@register_tool
|
||||
#def list_files(file_glob: str = "*") -> str:
|
||||
# """List files in the workspace matching the glob pattern."""
|
||||
# found = sorted(WORKSPACE.glob(file_glob))
|
||||
# found = [f.relative_to(WORKSPACE) for f in found if f.is_file()]
|
||||
# if not found:
|
||||
# return f"No files matching '{file_glob}' in workspace."
|
||||
# return "\n".join(str(f) for f in found)
|
||||
#
|
||||
#
|
||||
#@register_tool
|
||||
#def grep_search(pattern: str, file_glob: str = "*.py") -> str:
|
||||
# """Search for a pattern in workspace files and return matching lines with line numbers."""
|
||||
# matches = []
|
||||
# for filepath in sorted(WORKSPACE.glob(file_glob)):
|
||||
# if filepath.suffix not in (".py", ".txt"):
|
||||
# continue
|
||||
# try:
|
||||
# lines = filepath.read_text().splitlines()
|
||||
# except Exception:
|
||||
# continue
|
||||
# for i, line in enumerate(lines, 1):
|
||||
# if pattern in line:
|
||||
# rel = filepath.relative_to(WORKSPACE)
|
||||
# matches.append(f"{rel}:{i}: {line}")
|
||||
# if not matches:
|
||||
# return f"No matches for '{pattern}' in {file_glob}."
|
||||
# return "\n".join(matches)
|
||||
#
|
||||
#
|
||||
#@register_tool
|
||||
#def run_python(path: str) -> str:
|
||||
# """Execute a Python file in the workspace and return stdout and stderr."""
|
||||
# target = (WORKSPACE / path).resolve()
|
||||
# if not str(target).startswith(str(WORKSPACE.resolve())):
|
||||
# return "ERROR: path is outside the workspace."
|
||||
# if not target.exists():
|
||||
# return f"ERROR: file '{path}' not found."
|
||||
# result = subprocess.run(
|
||||
# [sys.executable, str(target)],
|
||||
# capture_output=True, text=True, timeout=30,
|
||||
# cwd=str(WORKSPACE),
|
||||
# )
|
||||
# output = ""
|
||||
# if result.stdout:
|
||||
# output += f"STDOUT:\n{result.stdout}"
|
||||
# if result.stderr:
|
||||
# output += f"STDERR:\n{result.stderr}"
|
||||
# output += f"\nExit code: {result.returncode}"
|
||||
# return output.strip()
|
||||
#
|
||||
#
|
||||
#@register_tool
|
||||
#def validate_python(path: str) -> str:
|
||||
# """Check whether a Python file has valid syntax using ast.parse."""
|
||||
# target = (WORKSPACE / path).resolve()
|
||||
# if not str(target).startswith(str(WORKSPACE.resolve())):
|
||||
# return "ERROR: path is outside the workspace."
|
||||
# if not target.exists():
|
||||
# return f"ERROR: file '{path}' not found."
|
||||
# source = target.read_text()
|
||||
# try:
|
||||
# ast.parse(source)
|
||||
# return "OK: syntax is valid."
|
||||
# except SyntaxError as e:
|
||||
# return f"SYNTAX ERROR: {e}"
|
||||
#
|
||||
#
|
||||
#@register_tool
|
||||
#def done(summary: str) -> str:
|
||||
# """Signal that the agent has finished its task."""
|
||||
# return f"DONE: {summary}"
|
||||
#
|
||||
#
|
||||
## ═════════════════════════════════════════════════════════════════════════════
|
||||
## PART B – TOOL DISPATCHER
|
||||
## ═════════════════════════════════════════════════════════════════════════════
|
||||
#
|
||||
#def build_tool_description() -> str:
|
||||
# """Auto-generate tool descriptions from function signatures and docstrings."""
|
||||
# lines = []
|
||||
# for name, func in TOOL_REGISTRY.items():
|
||||
# sig = inspect.signature(func)
|
||||
# params = []
|
||||
# for pname, param in sig.parameters.items():
|
||||
# if param.default is inspect.Parameter.empty:
|
||||
# params.append(f'"{pname}": "<value>"')
|
||||
# else:
|
||||
# params.append(f'"{pname}": "<optional, default={param.default!r}>"')
|
||||
# param_str = ", ".join(params)
|
||||
# doc = (func.__doc__ or "").strip().split("\n")[0]
|
||||
# lines.append(f" - {name}({{{param_str}}}): {doc}")
|
||||
# return "\n".join(lines)
|
||||
#
|
||||
#
|
||||
#def dispatch_tool(tool_name: str, arguments: dict) -> str:
|
||||
# """Call a tool by name with the given arguments."""
|
||||
# if tool_name not in TOOL_REGISTRY:
|
||||
# return f"ERROR: unknown tool '{tool_name}'. Available: {list(TOOL_REGISTRY.keys())}"
|
||||
# func = TOOL_REGISTRY[tool_name]
|
||||
# try:
|
||||
# return func(**arguments)
|
||||
# except TypeError as e:
|
||||
# return f"ERROR calling {tool_name}: {e}"
|
||||
# except Exception as e:
|
||||
# return f"ERROR in {tool_name}: {type(e).__name__}: {e}"
|
||||
#
|
||||
|
||||
TOOL_REGISTRY: dict[str, callable] = {}
|
||||
async def get_tools_for_prompt(query: str) -> str:
|
||||
"""Get relevant tools from the MCP servers based on the query."""
|
||||
|
||||
relevant_tools = adapter.get_relevant_tools(query, top_k=3)
|
||||
|
||||
def register_tool(func):
|
||||
"""Decorator – adds a function to the global tool registry."""
|
||||
TOOL_REGISTRY[func.__name__] = func
|
||||
return func
|
||||
descriptions = []
|
||||
for tool in relevant_tools:
|
||||
params = tool.inputSchema.get("properties", {})
|
||||
if params:
|
||||
param_lines = []
|
||||
for pname, pinfo in params.items():
|
||||
ptype = pinfo.get("type", "any")
|
||||
pdesc = pinfo.get("description", "")
|
||||
param_lines.append(f" - {pname} ({ptype}): {pdesc}")
|
||||
param_str = "\n".join(param_lines)
|
||||
else:
|
||||
param_str = " (none)"
|
||||
descriptions.append(
|
||||
f"- {tool.name}: {tool.description}\n"
|
||||
f"Parameters:\n{param_str}"
|
||||
)
|
||||
|
||||
return "\n".join(descriptions)
|
||||
|
||||
@register_tool
|
||||
def read_file(path: str) -> str:
|
||||
"""Read a .py or .txt file from the workspace and return its contents."""
|
||||
target = (WORKSPACE / path).resolve()
|
||||
if not str(target).startswith(str(WORKSPACE.resolve())):
|
||||
return "ERROR: path is outside the workspace."
|
||||
if not target.exists():
|
||||
return f"ERROR: file '{path}' not found."
|
||||
if target.suffix not in (".py", ".txt"):
|
||||
return f"ERROR: can only read .py and .txt files, got '{target.suffix}'."
|
||||
return target.read_text()
|
||||
async def dispatch_tool(tool_name: str, arguments: dict) -> str:
|
||||
"""Call a tool by name with the given arguments using the MCP adapter."""
|
||||
|
||||
|
||||
@register_tool
|
||||
def write_file(path: str, content: str) -> str:
|
||||
"""Write content to a .py or .txt file in the workspace."""
|
||||
target = (WORKSPACE / path).resolve()
|
||||
if not str(target).startswith(str(WORKSPACE.resolve())):
|
||||
return "ERROR: path is outside the workspace."
|
||||
if target.suffix not in (".py", ".txt"):
|
||||
return f"ERROR: can only write .py and .txt files, got '{target.suffix}'."
|
||||
target.parent.mkdir(parents=True, exist_ok=True)
|
||||
target.write_text(content)
|
||||
return f"OK: wrote {len(content)} chars to {path}."
|
||||
|
||||
|
||||
@register_tool
|
||||
def list_files(file_glob: str = "*") -> str:
|
||||
"""List files in the workspace matching the glob pattern."""
|
||||
found = sorted(WORKSPACE.glob(file_glob))
|
||||
found = [f.relative_to(WORKSPACE) for f in found if f.is_file()]
|
||||
if not found:
|
||||
return f"No files matching '{file_glob}' in workspace."
|
||||
return "\n".join(str(f) for f in found)
|
||||
|
||||
|
||||
@register_tool
|
||||
def grep_search(pattern: str, file_glob: str = "*.py") -> str:
|
||||
"""Search for a pattern in workspace files and return matching lines with line numbers."""
|
||||
matches = []
|
||||
for filepath in sorted(WORKSPACE.glob(file_glob)):
|
||||
if filepath.suffix not in (".py", ".txt"):
|
||||
continue
|
||||
try:
|
||||
lines = filepath.read_text().splitlines()
|
||||
except Exception:
|
||||
continue
|
||||
for i, line in enumerate(lines, 1):
|
||||
if pattern in line:
|
||||
rel = filepath.relative_to(WORKSPACE)
|
||||
matches.append(f"{rel}:{i}: {line}")
|
||||
if not matches:
|
||||
return f"No matches for '{pattern}' in {file_glob}."
|
||||
return "\n".join(matches)
|
||||
|
||||
|
||||
@register_tool
|
||||
def run_python(path: str) -> str:
|
||||
"""Execute a Python file in the workspace and return stdout and stderr."""
|
||||
target = (WORKSPACE / path).resolve()
|
||||
if not str(target).startswith(str(WORKSPACE.resolve())):
|
||||
return "ERROR: path is outside the workspace."
|
||||
if not target.exists():
|
||||
return f"ERROR: file '{path}' not found."
|
||||
result = subprocess.run(
|
||||
[sys.executable, str(target)],
|
||||
capture_output=True, text=True, timeout=30,
|
||||
cwd=str(WORKSPACE),
|
||||
)
|
||||
output = ""
|
||||
if result.stdout:
|
||||
output += f"STDOUT:\n{result.stdout}"
|
||||
if result.stderr:
|
||||
output += f"STDERR:\n{result.stderr}"
|
||||
output += f"\nExit code: {result.returncode}"
|
||||
return output.strip()
|
||||
|
||||
|
||||
@register_tool
|
||||
def validate_python(path: str) -> str:
|
||||
"""Check whether a Python file has valid syntax using ast.parse."""
|
||||
target = (WORKSPACE / path).resolve()
|
||||
if not str(target).startswith(str(WORKSPACE.resolve())):
|
||||
return "ERROR: path is outside the workspace."
|
||||
if not target.exists():
|
||||
return f"ERROR: file '{path}' not found."
|
||||
source = target.read_text()
|
||||
if tool_name == "done":
|
||||
# Handle the "done" tool locally since it's not an MCP tool
|
||||
summary = arguments.get("summary", "Task completed.")
|
||||
return f"DONE: {summary}"
|
||||
|
||||
try:
|
||||
ast.parse(source)
|
||||
return "OK: syntax is valid."
|
||||
except SyntaxError as e:
|
||||
return f"SYNTAX ERROR: {e}"
|
||||
result = await adapter.call_tool(tool_name, arguments)
|
||||
|
||||
|
||||
@register_tool
|
||||
def done(summary: str) -> str:
|
||||
"""Signal that the agent has finished its task."""
|
||||
return f"DONE: {summary}"
|
||||
|
||||
|
||||
# ═════════════════════════════════════════════════════════════════════════════
|
||||
# PART B – TOOL DISPATCHER
|
||||
# ═════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
def build_tool_description() -> str:
|
||||
"""Auto-generate tool descriptions from function signatures and docstrings."""
|
||||
lines = []
|
||||
for name, func in TOOL_REGISTRY.items():
|
||||
sig = inspect.signature(func)
|
||||
params = []
|
||||
for pname, param in sig.parameters.items():
|
||||
if param.default is inspect.Parameter.empty:
|
||||
params.append(f'"{pname}": "<value>"')
|
||||
else:
|
||||
params.append(f'"{pname}": "<optional, default={param.default!r}>"')
|
||||
param_str = ", ".join(params)
|
||||
doc = (func.__doc__ or "").strip().split("\n")[0]
|
||||
lines.append(f" - {name}({{{param_str}}}): {doc}")
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def dispatch_tool(tool_name: str, arguments: dict) -> str:
|
||||
"""Call a tool by name with the given arguments."""
|
||||
if tool_name not in TOOL_REGISTRY:
|
||||
return f"ERROR: unknown tool '{tool_name}'. Available: {list(TOOL_REGISTRY.keys())}"
|
||||
func = TOOL_REGISTRY[tool_name]
|
||||
try:
|
||||
return func(**arguments)
|
||||
except TypeError as e:
|
||||
return f"ERROR calling {tool_name}: {e}"
|
||||
if result.isError:
|
||||
texts = [block.text for block in result.content if block.type == "text"]
|
||||
return f"Tool error: {' '.join(texts)}"
|
||||
|
||||
texts = [block.text for block in result.content if block.type == "text"]
|
||||
return "\n".join(texts)
|
||||
|
||||
except Exception as e:
|
||||
return f"ERROR in {tool_name}: {type(e).__name__}: {e}"
|
||||
|
||||
return f"Error calling tool '{tool_name}': {e}"
|
||||
|
||||
# ═════════════════════════════════════════════════════════════════════════════
|
||||
# PART C – SYSTEM PROMPT
|
||||
# SYSTEM PROMPT
|
||||
# ═════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
SYSTEM_PROMPT = f"""\
|
||||
You are a coding agent that helps users with Python programming tasks.
|
||||
You work inside a workspace directory and have access to tools.
|
||||
You solve tasks by interacting with a workspace through a
|
||||
dynamic set of tools via the Model Context Protocol (MCP).
|
||||
|
||||
<capabilities>
|
||||
You can:
|
||||
- Read .py and .txt files from the workspace
|
||||
- Write .py and .txt files to the workspace
|
||||
- List files in the workspace
|
||||
- Search for patterns in files using grep
|
||||
- Execute Python files and see their output
|
||||
- Validate Python syntax using ast.parse
|
||||
- Signal completion when the task is done
|
||||
<capabilities>
|
||||
You have access to a workspace where you can manage files,
|
||||
analyze code, and execute Python scripts.
|
||||
Your available tools are provided dynamically based on your current needs.
|
||||
</capabilities>
|
||||
|
||||
<tools>
|
||||
{build_tool_description()}
|
||||
tool will be provided dynamically based on the agent's current context and needs.
|
||||
</tools>
|
||||
|
||||
|
||||
<workflow>
|
||||
For every user request, follow this workflow:
|
||||
1. PLAN: Think about what steps are needed. List them in "thought".
|
||||
@ -252,7 +300,7 @@ Example:
|
||||
|
||||
|
||||
# ═════════════════════════════════════════════════════════════════════════════
|
||||
# PART D – CODING AGENT CLASS
|
||||
# CODING AGENT CLASS
|
||||
# ═════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
def truncate_result(result: str) -> str:
|
||||
@ -335,8 +383,9 @@ class CodingAgent:
|
||||
self.api_key = os.getenv("API_KEY")
|
||||
self.model = os.getenv("MODEL")
|
||||
|
||||
def _call_api(self, messages: list) -> str:
|
||||
async def _call_api(self, messages: list, relevant_tools: list = None) -> str:
|
||||
"""Make a raw API call and return the response content string."""
|
||||
|
||||
headers = {"Content-Type": "application/json"}
|
||||
if self.api_key and self.api_key != "EMPTY":
|
||||
headers["Authorization"] = f"Bearer {self.api_key}"
|
||||
@ -344,12 +393,19 @@ class CodingAgent:
|
||||
payload = {
|
||||
"model": self.model,
|
||||
"messages": messages,
|
||||
"tools": relevant_tools,
|
||||
"temperature": 0.2,
|
||||
"max_tokens": 4096,
|
||||
"stream": False,
|
||||
}
|
||||
|
||||
response = requests.post(self.api_url, headers=headers, json=payload, timeout=60)
|
||||
|
||||
async with httpx.AsyncClient(timeout=30) as client:
|
||||
response = await client.post(
|
||||
self.api_url,
|
||||
headers=headers,
|
||||
json=payload,
|
||||
timeout=60)
|
||||
#response = requests.post(self.api_url, headers=headers, json=payload, timeout=60)
|
||||
|
||||
if response.status_code != 200:
|
||||
raise Exception(f"API Error {response.status_code}: {response.text}")
|
||||
@ -371,7 +427,7 @@ class CodingAgent:
|
||||
self.is_done = False
|
||||
self.iteration = 0
|
||||
|
||||
def propose_next_action(self) -> dict:
|
||||
async def propose_next_action(self) -> dict:
|
||||
"""Ask the LLM what to do next.
|
||||
|
||||
Returns the parsed action dict without executing anything.
|
||||
@ -387,12 +443,14 @@ class CodingAgent:
|
||||
if self.iteration >= MAX_ITERATIONS:
|
||||
return {"thought": "Max iterations reached.", "tool": "done",
|
||||
"arguments": {"summary": "Stopped: max iterations reached."}}
|
||||
|
||||
|
||||
current_context = self.messages[-1]["content"] if self.messages else ""
|
||||
self.iteration += 1
|
||||
self.messages = trim_messages(self.messages)
|
||||
relevant_tools = await get_tools_for_prompt(current_context)
|
||||
|
||||
try:
|
||||
raw = self._call_api(self.messages)
|
||||
raw = await self._call_api(self.messages, relevant_tools)
|
||||
raw = _strip_code_fences(raw)
|
||||
action = json.loads(raw)
|
||||
except json.JSONDecodeError:
|
||||
@ -413,7 +471,7 @@ class CodingAgent:
|
||||
self.pending_action = {"raw": raw, "action": action}
|
||||
return action
|
||||
|
||||
def approve(self) -> dict:
|
||||
async def approve(self) -> dict:
|
||||
"""Execute the pending action and return the result.
|
||||
|
||||
Returns:
|
||||
@ -442,7 +500,7 @@ class CodingAgent:
|
||||
}
|
||||
|
||||
# Execute the tool
|
||||
result = dispatch_tool(tool_name, arguments)
|
||||
result = await dispatch_tool(tool_name, arguments)
|
||||
result = truncate_result(result)
|
||||
|
||||
# Build feedback – nudge agent to replan on errors
|
||||
@ -506,3 +564,5 @@ class CodingAgent:
|
||||
),
|
||||
})
|
||||
self.pending_action = None
|
||||
|
||||
|
||||
|
||||
@ -13,6 +13,7 @@ class MCPToolRAGAdapter:
|
||||
def __init__ (self, config_path: str = "mcp_server_config.json"):
|
||||
self.config_path = config_path
|
||||
self.tools = []
|
||||
self.toolnames = []
|
||||
self.embedder = SentenceTransformer('all-MiniLM-L6-v2') # for embedding tool descriptions
|
||||
self.sessions = {}
|
||||
self.exit_stack = {}
|
||||
@ -64,6 +65,7 @@ class MCPToolRAGAdapter:
|
||||
"definition": tool,
|
||||
"search_text": f"{tool['name']}: {tool.get('description', '')}",
|
||||
})
|
||||
self.tool_names.append(tool["name"])
|
||||
|
||||
# embeddings for all tools in this session
|
||||
if self.tool_registry:
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
"command": "python",
|
||||
"args": ["servers/mcp_server_file_search.py"]
|
||||
},
|
||||
|
||||
"WebSearchServer": {
|
||||
"command": "python",
|
||||
"args": ["servers/mcp_server_web_search.py"],
|
||||
@ -9,6 +10,7 @@
|
||||
"DDGS_API_KEY": "your_ddgs_api_key_here"
|
||||
}
|
||||
},
|
||||
|
||||
"CodeExecutionServer": {
|
||||
"command": "python",
|
||||
"args": ["servers/mcp_server_code_execution.py"]
|
||||
|
||||
@ -2,8 +2,18 @@ import streamlit as st
|
||||
from backend.managers.chat_manager import ChatManager
|
||||
from backend.managers.system_prompter import SystemPrompter
|
||||
|
||||
import asyncio
|
||||
|
||||
|
||||
# ── Agent Mode helpers ────────────────────────────────────────────────────────
|
||||
def _run_async(coro):
|
||||
"""Hilfsfunktion um async Code in sync Streamlit auszuführen"""
|
||||
try:
|
||||
loop = asyncio.get_running_loop()
|
||||
except RuntimeError:
|
||||
loop = asyncio.new_event_loop()
|
||||
asyncio.set_event_loop(loop)
|
||||
return loop.run_until_complete(coro)
|
||||
|
||||
def _start_agent(task: str):
|
||||
"""Initialise a fresh CodingAgent, start the task,
|
||||
@ -11,7 +21,7 @@ def _start_agent(task: str):
|
||||
from backend.agent.coding_agent import CodingAgent
|
||||
agent = CodingAgent()
|
||||
agent.start_task(task)
|
||||
action = agent.propose_next_action()
|
||||
action = _run_async(agent.propose_next_action())
|
||||
st.session_state.coding_agent = agent
|
||||
st.session_state.agent_pending_action = action
|
||||
st.session_state.agent_status = "waiting_approval"
|
||||
@ -24,7 +34,7 @@ def _approve_action():
|
||||
agent = st.session_state.coding_agent
|
||||
pending = st.session_state.agent_pending_action
|
||||
|
||||
result = agent.approve()
|
||||
result = _run_async(agent.approve())
|
||||
|
||||
st.session_state.agent_log.append({
|
||||
"thought": pending.get("thought", ""),
|
||||
@ -37,7 +47,7 @@ def _approve_action():
|
||||
st.session_state.agent_status = "done"
|
||||
st.session_state.agent_pending_action = None
|
||||
else:
|
||||
next_action = agent.propose_next_action()
|
||||
next_action = _run_async(agent.propose_next_action())
|
||||
st.session_state.agent_pending_action = next_action
|
||||
st.session_state.agent_status = "waiting_approval"
|
||||
|
||||
@ -46,7 +56,7 @@ def _reject_action(feedback: str):
|
||||
"""Reject the pending action with optional feedback, then replan."""
|
||||
agent = st.session_state.coding_agent
|
||||
agent.reject(feedback or "Please try a different approach.")
|
||||
next_action = agent.propose_next_action()
|
||||
next_action = _run_async(agent.propose_next_action())
|
||||
st.session_state.agent_pending_action = next_action
|
||||
st.session_state.agent_status = "waiting_approval"
|
||||
|
||||
@ -55,7 +65,7 @@ def _followup_agent(question: str):
|
||||
"""Inject a follow-up question into the finished agent and resume the loop."""
|
||||
agent = st.session_state.coding_agent
|
||||
agent.follow_up(question)
|
||||
action = agent.propose_next_action()
|
||||
action = _run_async(agent.propose_next_action())
|
||||
st.session_state.agent_pending_action = action
|
||||
st.session_state.agent_status = "waiting_approval"
|
||||
|
||||
@ -103,6 +113,8 @@ def render_agent_mode():
|
||||
placeholder="e.g. Write a function that sorts a list and saves it to sorted.py",
|
||||
)
|
||||
if st.button("Start Agent", type="primary", use_container_width=True):
|
||||
#loop = asyncio.new_event_loop()
|
||||
#asyncio.set_event_loop(loop)
|
||||
if task.strip():
|
||||
with st.spinner("Agent is thinking..."):
|
||||
_start_agent(task.strip())
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user