Merge pull request #190 from Fosowl/dev

Add stop button, reasoning view, add anthropic provider
This commit is contained in:
Martin
2025-05-21 16:40:08 +02:00
committed by GitHub
13 changed files with 174 additions and 25 deletions
+14 -1
View File
@@ -128,6 +128,12 @@ async def is_active():
logger.info("Is active endpoint called") logger.info("Is active endpoint called")
return {"is_active": interaction.is_active} return {"is_active": interaction.is_active}
@api.get("/stop")
async def stop():
logger.info("Stop endpoint called")
interaction.current_agent.request_stop()
return JSONResponse(status_code=200, content={"status": "stopped"})
@api.get("/latest_answer") @api.get("/latest_answer")
async def get_latest_answer(): async def get_latest_answer():
global query_resp_history global query_resp_history
@@ -138,6 +144,7 @@ async def get_latest_answer():
query_resp = { query_resp = {
"done": "false", "done": "false",
"answer": interaction.current_agent.last_answer, "answer": interaction.current_agent.last_answer,
"reasoning": interaction.current_agent.last_reasoning,
"agent_name": interaction.current_agent.agent_name if interaction.current_agent else "None", "agent_name": interaction.current_agent.agent_name if interaction.current_agent else "None",
"success": interaction.current_agent.success, "success": interaction.current_agent.success,
"blocks": {f'{i}': block.jsonify() for i, block in enumerate(interaction.get_last_blocks_result())} if interaction.current_agent else {}, "blocks": {f'{i}': block.jsonify() for i, block in enumerate(interaction.get_last_blocks_result())} if interaction.current_agent else {},
@@ -145,6 +152,7 @@ async def get_latest_answer():
"uid": uid "uid": uid
} }
interaction.current_agent.last_answer = "" interaction.current_agent.last_answer = ""
interaction.current_agent.last_reasoning = ""
query_resp_history.append(query_resp) query_resp_history.append(query_resp)
return JSONResponse(status_code=200, content=query_resp) return JSONResponse(status_code=200, content=query_resp)
if query_resp_history: if query_resp_history:
@@ -158,6 +166,7 @@ async def think_wrapper(interaction, query):
success = await interaction.think() success = await interaction.think()
if not success: if not success:
interaction.last_answer = "Error: No answer from agent" interaction.last_answer = "Error: No answer from agent"
interaction.last_reasoning = "Error: No reasoning from agent"
interaction.last_success = False interaction.last_success = False
else: else:
interaction.last_success = True interaction.last_success = True
@@ -166,7 +175,8 @@ async def think_wrapper(interaction, query):
return success return success
except Exception as e: except Exception as e:
logger.error(f"Error in think_wrapper: {str(e)}") logger.error(f"Error in think_wrapper: {str(e)}")
interaction.last_answer = f"Error: {str(e)}" interaction.last_answer = f""
interaction.last_reasoning = f"Error: {str(e)}"
interaction.last_success = False interaction.last_success = False
raise e raise e
@@ -177,6 +187,7 @@ async def process_query(request: QueryRequest):
query_resp = QueryResponse( query_resp = QueryResponse(
done="false", done="false",
answer="", answer="",
reasoning="",
agent_name="Unknown", agent_name="Unknown",
success="false", success="false",
blocks={}, blocks={},
@@ -194,6 +205,7 @@ async def process_query(request: QueryRequest):
if not success: if not success:
query_resp.answer = interaction.last_answer query_resp.answer = interaction.last_answer
query_resp.reasoning = interaction.last_reasoning
return JSONResponse(status_code=400, content=query_resp.jsonify()) return JSONResponse(status_code=400, content=query_resp.jsonify())
if interaction.current_agent: if interaction.current_agent:
@@ -208,6 +220,7 @@ async def process_query(request: QueryRequest):
logger.info(f"Blocks: {blocks_json}") logger.info(f"Blocks: {blocks_json}")
query_resp.done = "true" query_resp.done = "true"
query_resp.answer = interaction.last_answer query_resp.answer = interaction.last_answer
query_resp.reasoning = interaction.last_reasoning
query_resp.agent_name = interaction.current_agent.agent_name query_resp.agent_name = interaction.current_agent.agent_name
query_resp.success = str(interaction.last_success) query_resp.success = str(interaction.last_success)
query_resp.blocks = blocks_json query_resp.blocks = blocks_json
+50
View File
@@ -94,6 +94,7 @@ function App() {
{ {
type: 'agent', type: 'agent',
content: data.answer, content: data.answer,
reasoning: data.reasoning,
agentName: data.agent_name, agentName: data.agent_name,
status: data.status, status: data.status,
uid: data.uid, uid: data.uid,
@@ -121,6 +122,19 @@ function App() {
})); }));
}; };
const handleStop = async (e) => {
e.preventDefault();
checkHealth();
setIsLoading(false);
setError(null);
try {
const res = await axios.get('http://127.0.0.1:8000/stop');
setStatus("Requesting stop...");
} catch (err) {
console.error('Error stopping the agent:', err);
}
}
const handleSubmit = async (e) => { const handleSubmit = async (e) => {
e.preventDefault(); e.preventDefault();
checkHealth(); checkHealth();
@@ -213,6 +227,9 @@ function App() {
<button type="submit" disabled={isLoading}> <button type="submit" disabled={isLoading}>
Send Send
</button> </button>
<button onClick={handleStop}>
Stop
</button>
</form> </form>
</div> </div>
@@ -231,6 +248,12 @@ function App() {
> >
Browser View Browser View
</button> </button>
<button
className={currentView === 'thinking' ? 'active' : ''}
onClick={() => setCurrentView('thinking')}
>
Reasoning view
</button>
</div> </div>
<div className="content"> <div className="content">
{error && <p className="error">{error}</p>} {error && <p className="error">{error}</p>}
@@ -256,6 +279,33 @@ function App() {
</div> </div>
)} )}
</div> </div>
) : currentView == 'thinking' ? (
<div className="thinking">
<div className="messages">
{messages.length === 0 ? (
<p className="placeholder">No thinking yet.</p>
) : (
messages.map((msg, index) => (
<div
key={index}
className={`message ${
msg.type === 'user'
? 'user-message'
: msg.type === 'agent'
? 'agent-message'
: 'error-message'
}`}
>
{msg.type === 'agent' && (
<span className="agent-name">{msg.agentName}</span>
)}
<ReactMarkdown>{msg.reasoning}</ReactMarkdown>
</div>
))
)}
<div ref={messagesEndRef} />
</div>
</div>
) : ( ) : (
<div className="screenshot"> <div className="screenshot">
<img <img
+13
View File
@@ -44,7 +44,9 @@ class Agent():
self.blocks_result = [] self.blocks_result = []
self.success = True self.success = True
self.last_answer = "" self.last_answer = ""
self.last_reasoning = ""
self.status_message = "Haven't started yet" self.status_message = "Haven't started yet"
self.stop = False
self.verbose = verbose self.verbose = verbose
self.executor = ThreadPoolExecutor(max_workers=1) self.executor = ThreadPoolExecutor(max_workers=1)
@@ -64,6 +66,10 @@ class Agent():
def get_last_answer(self) -> str: def get_last_answer(self) -> str:
return self.last_answer return self.last_answer
@property
def get_last_reasoning(self) -> str:
return self.last_reasoning
@property @property
def get_blocks(self) -> list: def get_blocks(self) -> list:
return self.blocks_result return self.blocks_result
@@ -114,6 +120,13 @@ class Agent():
except Exception as e: except Exception as e:
raise e raise e
def request_stop(self) -> None:
"""
Request the agent to stop.
"""
self.stop = True
self.status_message = "Stopped"
@abstractmethod @abstractmethod
def process(self, prompt, speech_module) -> str: def process(self, prompt, speech_module) -> str:
""" """
+5 -2
View File
@@ -181,6 +181,7 @@ class BrowserAgent(Agent):
animate_thinking("Thinking...", color="status") animate_thinking("Thinking...", color="status")
self.memory.push('user', prompt) self.memory.push('user', prompt)
answer, reasoning = await self.llm_request() answer, reasoning = await self.llm_request()
self.last_reasoning = reasoning
if show_reasoning: if show_reasoning:
pretty_print(reasoning, color="failure") pretty_print(reasoning, color="failure")
pretty_print(answer, color="output") pretty_print(answer, color="output")
@@ -349,11 +350,13 @@ class BrowserAgent(Agent):
self.show_search_results(search_result) self.show_search_results(search_result)
prompt = self.make_newsearch_prompt(user_prompt, search_result) prompt = self.make_newsearch_prompt(user_prompt, search_result)
unvisited = [None] unvisited = [None]
while not complete and len(unvisited) > 0: while not complete and len(unvisited) > 0 and not self.stop:
self.memory.clear() self.memory.clear()
unvisited = self.select_unvisited(search_result) unvisited = self.select_unvisited(search_result)
answer, reasoning = await self.llm_decide(prompt, show_reasoning = False) answer, reasoning = await self.llm_decide(prompt, show_reasoning = False)
if self.stop:
pretty_print(f"Requested stop.", color="failure")
break
if self.last_answer == answer: if self.last_answer == answer:
prompt = self.stuck_prompt(user_prompt, unvisited) prompt = self.stuck_prompt(user_prompt, unvisited)
continue continue
+3 -1
View File
@@ -51,10 +51,12 @@ class CoderAgent(Agent):
self.memory.push('user', prompt) self.memory.push('user', prompt)
clarify_trigger = "REQUEST_CLARIFICATION" clarify_trigger = "REQUEST_CLARIFICATION"
while attempt < max_attempts: while attempt < max_attempts and not self.stop:
print("Stopped?", self.stop)
animate_thinking("Thinking...", color="status") animate_thinking("Thinking...", color="status")
await self.wait_message(speech_module) await self.wait_message(speech_module)
answer, reasoning = await self.llm_request() answer, reasoning = await self.llm_request()
self.last_reasoning = reasoning
if clarify_trigger in answer: if clarify_trigger in answer:
self.last_answer = answer self.last_answer = answer
await asyncio.sleep(0) await asyncio.sleep(0)
+2 -1
View File
@@ -28,10 +28,11 @@ class FileAgent(Agent):
exec_success = False exec_success = False
prompt += f"\nYou must work in directory: {self.work_dir}" prompt += f"\nYou must work in directory: {self.work_dir}"
self.memory.push('user', prompt) self.memory.push('user', prompt)
while exec_success is False: while exec_success is False and not self.stop:
await self.wait_message(speech_module) await self.wait_message(speech_module)
animate_thinking("Thinking...", color="status") animate_thinking("Thinking...", color="status")
answer, reasoning = await self.llm_request() answer, reasoning = await self.llm_request()
self.last_reasoning = reasoning
exec_success, _ = self.execute_modules(answer) exec_success, _ = self.execute_modules(answer)
answer = self.remove_blocks(answer) answer = self.remove_blocks(answer)
self.last_answer = answer self.last_answer = answer
+20 -8
View File
@@ -83,11 +83,15 @@ class PlannerAgent(Agent):
self.logger.warning(f"Agent {task['agent']} does not exist.") self.logger.warning(f"Agent {task['agent']} does not exist.")
pretty_print(f"Agent {task['agent']} does not exist.", color="warning") pretty_print(f"Agent {task['agent']} does not exist.", color="warning")
return [] return []
agent = { try:
'agent': task['agent'], agent = {
'id': task['id'], 'agent': task['agent'],
'task': task['task'] 'id': task['id'],
} 'task': task['task']
}
except:
self.logger.warning("Missing field in json plan.")
return []
self.logger.info(f"Created agent {task['agent']} with task: {task['task']}") self.logger.info(f"Created agent {task['agent']} with task: {task['task']}")
if 'need' in task: if 'need' in task:
self.logger.info(f"Agent {task['agent']} was given info:\n {task['need']}") self.logger.info(f"Agent {task['agent']} was given info:\n {task['need']}")
@@ -156,6 +160,7 @@ class PlannerAgent(Agent):
return [] return []
agents_tasks = self.parse_agent_tasks(answer) agents_tasks = self.parse_agent_tasks(answer)
if agents_tasks == []: if agents_tasks == []:
self.show_plan(agents_tasks, answer)
prompt = f"Failed to parse the tasks. Please write down your task followed by a json plan within ```json. Do not ask for clarification.\n" prompt = f"Failed to parse the tasks. Please write down your task followed by a json plan within ```json. Do not ask for clarification.\n"
pretty_print("Failed to make plan. Retrying...", color="warning") pretty_print("Failed to make plan. Retrying...", color="warning")
continue continue
@@ -178,7 +183,11 @@ class PlannerAgent(Agent):
last_agent_work = agents_work_result[id] last_agent_work = agents_work_result[id]
tool_success_str = "success" if success else "failure" tool_success_str = "success" if success else "failure"
pretty_print(f"Agent {id} work {tool_success_str}.", color="success" if success else "failure") pretty_print(f"Agent {id} work {tool_success_str}.", color="success" if success else "failure")
if int(id) == len(agents_tasks): try:
id_int = int(id)
except Exception as e:
return agents_tasks
if id_int == len(agents_tasks):
next_task = "No task follow, this was the last step. If it failed add a task to recover." next_task = "No task follow, this was the last step. If it failed add a task to recover."
else: else:
next_task = f"Next task is: {agents_tasks[int(id)][0]}." next_task = f"Next task is: {agents_tasks[int(id)][0]}."
@@ -221,8 +230,9 @@ class PlannerAgent(Agent):
agent_prompt = self.make_prompt(task['task'], required_infos) agent_prompt = self.make_prompt(task['task'], required_infos)
pretty_print(f"Agent {task['agent']} started working...", color="status") pretty_print(f"Agent {task['agent']} started working...", color="status")
self.logger.info(f"Agent {task['agent']} started working on {task['task']}.") self.logger.info(f"Agent {task['agent']} started working on {task['task']}.")
answer, _ = await self.agents[task['agent'].lower()].process(agent_prompt, None) answer, reasoning = await self.agents[task['agent'].lower()].process(agent_prompt, None)
self.last_answer = answer self.last_answer = answer
self.last_reasoning = reasoning
self.blocks_result = self.agents[task['agent'].lower()].blocks_result self.blocks_result = self.agents[task['agent'].lower()].blocks_result
agent_answer = self.agents[task['agent'].lower()].raw_answer_blocks(answer) agent_answer = self.agents[task['agent'].lower()].raw_answer_blocks(answer)
success = self.agents[task['agent'].lower()].get_success success = self.agents[task['agent'].lower()].get_success
@@ -257,7 +267,7 @@ class PlannerAgent(Agent):
return "Failed to parse the tasks.", "" return "Failed to parse the tasks.", ""
i = 0 i = 0
steps = len(agents_tasks) steps = len(agents_tasks)
while i < steps: while i < steps and not self.stop:
task_name, task = agents_tasks[i][0], agents_tasks[i][1] task_name, task = agents_tasks[i][0], agents_tasks[i][1]
self.status_message = "Starting agents..." self.status_message = "Starting agents..."
pretty_print(f"I will {task_name}.", color="info") pretty_print(f"I will {task_name}.", color="info")
@@ -271,6 +281,8 @@ class PlannerAgent(Agent):
answer, success = await self.start_agent_process(task, required_infos) answer, success = await self.start_agent_process(task, required_infos)
except Exception as e: except Exception as e:
raise e raise e
if self.stop:
pretty_print(f"Requested stop.", color="failure")
agents_work_result[task['id']] = answer agents_work_result[task['id']] = answer
agents_tasks = await self.update_plan(goal, agents_tasks, agents_work_result, task['id'], success) agents_tasks = await self.update_plan(goal, agents_tasks, agents_work_result, task['id'], success)
steps = len(agents_tasks) steps = len(agents_tasks)
+14 -5
View File
@@ -206,7 +206,11 @@ class Browser:
def setup_tabs(self): def setup_tabs(self):
self.tabs = self.driver.window_handles self.tabs = self.driver.window_handles
self.driver.get("https://www.google.com") try:
self.driver.get("https://www.google.com")
except Exception as e:
self.logger.log(f"Failed to setup initial tab:" + str(e))
pass
self.screenshot() self.screenshot()
def switch_control_tab(self): def switch_control_tab(self):
@@ -215,7 +219,11 @@ class Browser:
def load_anticatpcha_manually(self): def load_anticatpcha_manually(self):
pretty_print("You might want to install the AntiCaptcha extension for captchas.", color="warning") pretty_print("You might want to install the AntiCaptcha extension for captchas.", color="warning")
self.driver.get(self.anticaptcha) try:
self.driver.get(self.anticaptcha)
except Exception as e:
self.logger.log(f"Failed to setup initial tab:" + str(e))
pass
def human_move(element): def human_move(element):
actions = ActionChains(driver) actions = ActionChains(driver)
@@ -685,15 +693,16 @@ class Browser:
input_elements = self.driver.execute_script(script) input_elements = self.driver.execute_script(script)
if __name__ == "__main__": if __name__ == "__main__":
driver = create_driver(headless=False, stealth_mode=True) driver = create_driver(headless=False, stealth_mode=True, crx_path="../crx/nopecha.crx")
browser = Browser(driver, anticaptcha_manual_install=False) browser = Browser(driver, anticaptcha_manual_install=True)
input("press enter to continue") input("press enter to continue")
print("AntiCaptcha / Form Test") print("AntiCaptcha / Form Test")
browser.go_to("https://www.google.com/recaptcha/api2/demo")
time.sleep(50)
browser.go_to("https://bot.sannysoft.com") browser.go_to("https://bot.sannysoft.com")
time.sleep(5) time.sleep(5)
#txt = browser.get_text() #txt = browser.get_text()
#browser.go_to("https://www.google.com/recaptcha/api2/demo")
browser.go_to("https://home.openweathermap.org/users/sign_up") browser.go_to("https://home.openweathermap.org/users/sign_up")
inputs_visible = browser.get_form_inputs() inputs_visible = browser.get_form_inputs()
print("inputs:", inputs_visible) print("inputs:", inputs_visible)
+2 -1
View File
@@ -22,6 +22,7 @@ class Interaction:
self.current_agent = None self.current_agent = None
self.last_query = None self.last_query = None
self.last_answer = None self.last_answer = None
self.last_reasoning = None
self.agents = agents self.agents = agents
self.tts_enabled = tts_enabled self.tts_enabled = tts_enabled
self.stt_enabled = stt_enabled self.stt_enabled = stt_enabled
@@ -158,7 +159,7 @@ class Interaction:
tmp = self.last_answer tmp = self.last_answer
self.current_agent = agent self.current_agent = agent
self.is_generating = True self.is_generating = True
self.last_answer, _ = await agent.process(self.last_query, self.speech) self.last_answer, self.last_reasoning = await agent.process(self.last_query, self.speech)
self.is_generating = False self.is_generating = False
if push_last_agent_memory: if push_last_agent_memory:
self.current_agent.memory.push('user', self.last_query) self.current_agent.memory.push('user', self.last_query)
+35 -2
View File
@@ -32,11 +32,12 @@ class Provider:
"deepseek": self.deepseek_fn, "deepseek": self.deepseek_fn,
"together": self.together_fn, "together": self.together_fn,
"dsk_deepseek": self.dsk_deepseek, "dsk_deepseek": self.dsk_deepseek,
"test": self.test_fn "test": self.test_fn,
"anthropic": self.anthropic_fn
} }
self.logger = Logger("provider.log") self.logger = Logger("provider.log")
self.api_key = None self.api_key = None
self.unsafe_providers = ["openai", "deepseek", "dsk_deepseek", "together", "google"] self.unsafe_providers = ["openai", "deepseek", "dsk_deepseek", "together", "google", "anthropic"]
if self.provider_name not in self.available_providers: if self.provider_name not in self.available_providers:
raise ValueError(f"Unknown provider: {provider_name}") raise ValueError(f"Unknown provider: {provider_name}")
if self.provider_name in self.unsafe_providers and self.is_local == False: if self.provider_name in self.unsafe_providers and self.is_local == False:
@@ -57,6 +58,38 @@ class Provider:
exit(1) exit(1)
return api_key return api_key
def anthropic_fn(self, history, verbose=False):
"""
Use Anthropic to generate text.
"""
from anthropic import Anthropic
client = Anthropic(api_key=self.api_key)
system_message = None
messages = []
for message in history:
clean_message = {'role': message['role'], 'content': message['content']}
if message['role'] == 'system':
system_message = message['content']
else:
messages.append(clean_message)
try:
response = client.messages.create(
model=self.model,
max_tokens=1024,
messages=messages,
system=system_message
)
if response is None:
raise Exception("Anthropic response is empty.")
thought = response.content[0].text
if verbose:
print(thought)
return thought
except Exception as e:
raise Exception(f"Anthropic API error: {str(e)}") from e
def respond(self, history, verbose=True): def respond(self, history, verbose=True):
""" """
Use the choosen provider to generate text. Use the choosen provider to generate text.
+2
View File
@@ -19,6 +19,7 @@ class QueryRequest(BaseModel):
class QueryResponse(BaseModel): class QueryResponse(BaseModel):
done: str done: str
answer: str answer: str
reasoning: str
agent_name: str agent_name: str
success: str success: str
blocks: dict blocks: dict
@@ -32,6 +33,7 @@ class QueryResponse(BaseModel):
return { return {
"done": self.done, "done": self.done,
"answer": self.answer, "answer": self.answer,
"reasoning": self.reasoning,
"agent_name": self.agent_name, "agent_name": self.agent_name,
"success": self.success, "success": self.success,
"blocks": self.blocks, "blocks": self.blocks,
+4 -3
View File
@@ -8,7 +8,7 @@ if __name__ == "__main__": # if running as a script for individual testing
sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))) sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
from sources.tools.tools import Tools from sources.tools.tools import Tools
from sources.tools.safety import is_unsafe from sources.tools.safety import is_any_unsafe
class BashInterpreter(Tools): class BashInterpreter(Tools):
""" """
@@ -43,9 +43,9 @@ class BashInterpreter(Tools):
for command in commands: for command in commands:
command = f"cd {self.work_dir} && {command}" command = f"cd {self.work_dir} && {command}"
command = command.replace('\n', '') command = command.replace('\n', '')
if self.safe_mode and is_unsafe(commands): if self.safe_mode and is_any_unsafe(commands):
print(f"Unsafe command rejected: {command}") print(f"Unsafe command rejected: {command}")
return "Unsafe command detected, execution aborted." return "\nUnsafe command: {command}. Execution aborted. This is beyond allowed capabilities report to user."
if self.language_bash_attempt(command) and self.allow_language_exec_bash == False: if self.language_bash_attempt(command) and self.allow_language_exec_bash == False:
continue continue
try: try:
@@ -100,6 +100,7 @@ class BashInterpreter(Tools):
r"not permitted", r"not permitted",
r"not installed", r"not installed",
r"not found", r"not found",
r"aborted",
r"no such", r"no such",
r"too many", r"too many",
r"too few", r"too few",
+9
View File
@@ -66,6 +66,15 @@ unsafe_commands_windows = [
"bootcfg" "bootcfg"
] ]
def is_any_unsafe(cmds):
"""
check if any bash command is unsafe.
"""
for cmd in cmds:
if is_unsafe(cmd):
return True
return False
def is_unsafe(cmd): def is_unsafe(cmd):
""" """
check if a bash command is unsafe. check if a bash command is unsafe.