Merge branch 'main' into tool_schema_temporary-fix

This commit is contained in:
Tao Sun 2025-10-17 12:07:34 +08:00 committed by GitHub
commit 1f092cfccd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
33 changed files with 290 additions and 791 deletions

View file

@ -323,6 +323,17 @@ class ListenChatAgent(ChatAgent):
else:
result = raw_result
mask_flag = False
# Prepare result message with truncation
if isinstance(result, str):
result_msg = result
else:
result_str = repr(result)
MAX_RESULT_LENGTH = 500
if len(result_str) > MAX_RESULT_LENGTH:
result_msg = result_str[:MAX_RESULT_LENGTH] + f"... (truncated, total length: {len(result_str)} chars)"
else:
result_msg = result_str
asyncio.create_task(
task_lock.put_queue(
ActionDeactivateToolkitData(
@ -331,7 +342,7 @@ class ListenChatAgent(ChatAgent):
"process_task_id": self.process_task_id,
"toolkit_name": toolkit_name,
"method_name": func_name,
"message": result if isinstance(result, str) else repr(result),
"message": result_msg,
},
)
)
@ -407,6 +418,17 @@ class ListenChatAgent(ChatAgent):
traceroot_logger.error(f"Async tool execution failed for {func_name}: {e}")
traceback.print_exc()
# Prepare result message with truncation
if isinstance(result, str):
result_msg = result
else:
result_str = repr(result)
MAX_RESULT_LENGTH = 500
if len(result_str) > MAX_RESULT_LENGTH:
result_msg = result_str[:MAX_RESULT_LENGTH] + f"... (truncated, total length: {len(result_str)} chars)"
else:
result_msg = result_str
await task_lock.put_queue(
ActionDeactivateToolkitData(
data={
@ -414,7 +436,7 @@ class ListenChatAgent(ChatAgent):
"process_task_id": self.process_task_id,
"toolkit_name": toolkit_name,
"method_name": func_name,
"message": result if isinstance(result, str) else repr(result),
"message": result_msg,
},
)
)

View file

@ -1,8 +1,10 @@
import asyncio
from functools import wraps
from inspect import iscoroutinefunction
from inspect import iscoroutinefunction, getmembers, ismethod, signature
import json
from typing import Any, Callable
from typing import Any, Callable, Type, TypeVar
import threading
from concurrent.futures import ThreadPoolExecutor
from loguru import logger
from app.service.task import (
@ -14,6 +16,39 @@ from app.utils.toolkit.abstract_toolkit import AbstractToolkit
from app.service.task import process_task
def _safe_put_queue(task_lock, data):
"""Safely put data to the queue, handling both sync and async contexts"""
try:
# Try to get current event loop
loop = asyncio.get_running_loop()
# We're in an async context, create a task
task = asyncio.create_task(task_lock.put_queue(data))
if hasattr(task_lock, "add_background_task"):
task_lock.add_background_task(task)
except RuntimeError:
# No running event loop, we need to handle this differently
try:
# Create a new event loop in a separate thread to avoid conflicts
def run_in_thread():
try:
# Create a new event loop for this thread
new_loop = asyncio.new_event_loop()
asyncio.set_event_loop(new_loop)
try:
new_loop.run_until_complete(task_lock.put_queue(data))
logger.debug(f"[listen_toolkit] Successfully sent data to queue using new event loop")
finally:
new_loop.close()
except Exception as e:
logger.error(f"[listen_toolkit] Failed to send data in thread: {e}")
# Run in a separate thread to avoid blocking
thread = threading.Thread(target=run_in_thread, daemon=True)
thread.start()
except Exception as e:
logger.error(f"[listen_toolkit] Failed to send data to queue: {e}")
def listen_toolkit(
wrap_method: Callable[..., Any] | None = None,
inputs: Callable[..., str] | None = None,
@ -27,6 +62,11 @@ def listen_toolkit(
@wraps(wrap)
async def async_wrapper(*args, **kwargs):
toolkit: AbstractToolkit = args[0]
# Check if api_task_id exists
if not hasattr(toolkit, 'api_task_id'):
logger.warning(f"[listen_toolkit] {toolkit.__class__.__name__} missing api_task_id, calling method directly")
return await func(*args, **kwargs)
task_lock = get_task_lock(toolkit.api_task_id)
if inputs is not None:
@ -40,19 +80,24 @@ def listen_toolkit(
kwargs_str = ", ".join(f"{k}={v!r}" for k, v in kwargs.items())
args_str = f"{args_str}, {kwargs_str}" if args_str else kwargs_str
# Truncate args_str if too long
MAX_ARGS_LENGTH = 500
if len(args_str) > MAX_ARGS_LENGTH:
args_str = args_str[:MAX_ARGS_LENGTH] + f"... (truncated, total length: {len(args_str)} chars)"
toolkit_name = toolkit.toolkit_name()
method_name = func.__name__.replace("_", " ")
await task_lock.put_queue(
ActionActivateToolkitData(
data={
"agent_name": toolkit.agent_name,
"process_task_id": process_task.get(""),
"toolkit_name": toolkit_name,
"method_name": method_name,
"message": args_str,
},
)
activate_data = ActionActivateToolkitData(
data={
"agent_name": toolkit.agent_name,
"process_task_id": process_task.get(""),
"toolkit_name": toolkit_name,
"method_name": method_name,
"message": args_str,
},
)
logger.debug(f"[listen_toolkit] Sending activate data: {activate_data.model_dump()}")
await task_lock.put_queue(activate_data)
error = None
res = None
try:
@ -70,21 +115,27 @@ def listen_toolkit(
res_msg = json.dumps(res, ensure_ascii=False)
except TypeError:
# Handle cases where res contains non-serializable objects (like coroutines)
res_msg = str(res)
res_str = str(res)
# Truncate very long outputs to avoid flooding logs
MAX_LENGTH = 500
if len(res_str) > MAX_LENGTH:
res_msg = res_str[:MAX_LENGTH] + f"... (truncated, total length: {len(res_str)} chars)"
else:
res_msg = res_str
else:
res_msg = str(error)
await task_lock.put_queue(
ActionDeactivateToolkitData(
data={
"agent_name": toolkit.agent_name,
"process_task_id": process_task.get(""),
"toolkit_name": toolkit_name,
"method_name": method_name,
"message": res_msg,
},
)
deactivate_data = ActionDeactivateToolkitData(
data={
"agent_name": toolkit.agent_name,
"process_task_id": process_task.get(""),
"toolkit_name": toolkit_name,
"method_name": method_name,
"message": res_msg,
},
)
logger.debug(f"[listen_toolkit] Sending deactivate data: {deactivate_data.model_dump()}")
await task_lock.put_queue(deactivate_data)
if error is not None:
raise error
return res
@ -96,6 +147,11 @@ def listen_toolkit(
@wraps(wrap)
def sync_wrapper(*args, **kwargs):
toolkit: AbstractToolkit = args[0]
# Check if api_task_id exists
if not hasattr(toolkit, 'api_task_id'):
logger.warning(f"[listen_toolkit] {toolkit.__class__.__name__} missing api_task_id, calling method directly")
return func(*args, **kwargs)
task_lock = get_task_lock(toolkit.api_task_id)
if inputs is not None:
@ -109,23 +165,24 @@ def listen_toolkit(
kwargs_str = ", ".join(f"{k}={v!r}" for k, v in kwargs.items())
args_str = f"{args_str}, {kwargs_str}" if args_str else kwargs_str
# Truncate args_str if too long
MAX_ARGS_LENGTH = 500
if len(args_str) > MAX_ARGS_LENGTH:
args_str = args_str[:MAX_ARGS_LENGTH] + f"... (truncated, total length: {len(args_str)} chars)"
toolkit_name = toolkit.toolkit_name()
method_name = func.__name__.replace("_", " ")
task = asyncio.create_task(
task_lock.put_queue(
ActionActivateToolkitData(
data={
"agent_name": toolkit.agent_name,
"process_task_id": process_task.get(""),
"toolkit_name": toolkit_name,
"method_name": method_name,
"message": args_str,
},
)
)
activate_data = ActionActivateToolkitData(
data={
"agent_name": toolkit.agent_name,
"process_task_id": process_task.get(""),
"toolkit_name": toolkit_name,
"method_name": method_name,
"message": args_str,
},
)
if hasattr(task_lock, "add_background_task"):
task_lock.add_background_task(task)
logger.debug(f"[listen_toolkit sync] Sending activate data: {activate_data.model_dump()}")
_safe_put_queue(task_lock, activate_data)
error = None
res = None
try:
@ -150,25 +207,26 @@ def listen_toolkit(
res_msg = json.dumps(res, ensure_ascii=False)
except TypeError:
# Handle cases where res contains non-serializable objects (like coroutines)
res_msg = str(res)
res_str = str(res)
# Truncate very long outputs to avoid flooding logs
MAX_LENGTH = 500
if len(res_str) > MAX_LENGTH:
res_msg = res_str[:MAX_LENGTH] + f"... (truncated, total length: {len(res_str)} chars)"
else:
res_msg = res_str
else:
res_msg = str(error)
task = asyncio.create_task(
task_lock.put_queue(
ActionDeactivateToolkitData(
data={
"agent_name": toolkit.agent_name,
"process_task_id": process_task.get(""),
"toolkit_name": toolkit_name,
"method_name": method_name,
"message": res_msg,
},
)
)
deactivate_data = ActionDeactivateToolkitData(
data={
"agent_name": toolkit.agent_name,
"process_task_id": process_task.get(""),
"toolkit_name": toolkit_name,
"method_name": method_name,
"message": res_msg,
},
)
if hasattr(task_lock, "add_background_task"):
task_lock.add_background_task(task)
_safe_put_queue(task_lock, deactivate_data)
if error is not None:
raise error
return res
@ -176,3 +234,81 @@ def listen_toolkit(
return sync_wrapper
return decorator
T = TypeVar('T')
# Methods that should not be wrapped by auto_listen_toolkit
# These are utility/helper methods that don't perform actual tool operations
EXCLUDED_METHODS = {
'get_tools', # Tool enumeration
'get_can_use_tools', # Tool filtering
'toolkit_name', # Metadata getter
'run_mcp_server', # MCP server initialization
'model_dump', # Pydantic model serialization
'model_dump_json', # Pydantic model serialization
'dict', # Pydantic legacy dict method
'json', # Pydantic legacy json method
'copy', # Object copying
'update', # Object update
}
def auto_listen_toolkit(base_toolkit_class: Type[T]) -> Callable[[Type[T]], Type[T]]:
"""
Class decorator that automatically wraps all public methods from the base toolkit
with the @listen_toolkit decorator.
Excluded methods (not wrapped):
- get_tools, get_can_use_tools: Tool enumeration/filtering
- toolkit_name: Metadata getter
- run_mcp_server: MCP server initialization
- Pydantic serialization methods: model_dump, model_dump_json, dict, json
- Object utility methods: copy, update
These methods are typically called during initialization or for metadata,
and should not trigger activate/deactivate events.
Usage:
@auto_listen_toolkit(BaseNoteTakingToolkit)
class NoteTakingToolkit(BaseNoteTakingToolkit, AbstractToolkit):
agent_name: str = Agents.document_agent
"""
def class_decorator(cls: Type[T]) -> Type[T]:
base_methods = {}
for name in dir(base_toolkit_class):
# Skip private methods and excluded helper methods
if not name.startswith('_') and name not in EXCLUDED_METHODS:
attr = getattr(base_toolkit_class, name)
if callable(attr):
base_methods[name] = attr
for method_name, base_method in base_methods.items():
if method_name in cls.__dict__:
continue
sig = signature(base_method)
def create_wrapper(method_name: str, base_method: Callable) -> Callable:
if iscoroutinefunction(base_method):
async def async_method_wrapper(self, *args, **kwargs):
return await getattr(super(cls, self), method_name)(*args, **kwargs)
async_method_wrapper.__name__ = method_name
async_method_wrapper.__signature__ = sig
return async_method_wrapper
else:
def sync_method_wrapper(self, *args, **kwargs):
return getattr(super(cls, self), method_name)(*args, **kwargs)
sync_method_wrapper.__name__ = method_name
sync_method_wrapper.__signature__ = sig
return sync_method_wrapper
wrapper = create_wrapper(method_name, base_method)
decorated_method = listen_toolkit(base_method)(wrapper)
setattr(cls, method_name, decorated_method)
return cls
return class_decorator

View file

@ -4,10 +4,11 @@ from camel.toolkits import AudioAnalysisToolkit as BaseAudioAnalysisToolkit
from app.component.environment import env
from app.service.task import Agents
from app.utils.listen.toolkit_listen import listen_toolkit
from app.utils.listen.toolkit_listen import auto_listen_toolkit
from app.utils.toolkit.abstract_toolkit import AbstractToolkit
@auto_listen_toolkit(BaseAudioAnalysisToolkit)
class AudioAnalysisToolkit(BaseAudioAnalysisToolkit, AbstractToolkit):
agent_name: str = Agents.multi_modal_agent
@ -23,14 +24,3 @@ class AudioAnalysisToolkit(BaseAudioAnalysisToolkit, AbstractToolkit):
cache_dir = env("file_save_path", os.path.expanduser("~/.eigent/tmp/"))
super().__init__(cache_dir, transcribe_model, audio_reasoning_model, timeout)
self.api_task_id = api_task_id
@listen_toolkit(
BaseAudioAnalysisToolkit.audio2text,
lambda _, audio_path, question: f"transcribe audio from {audio_path} and ask question: {question}",
)
def ask_question_about_audio(self, audio_path: str, question: str) -> str:
return super().ask_question_about_audio(audio_path, question)
@listen_toolkit(BaseAudioAnalysisToolkit.audio2text)
def audio2text(self, audio_path: str) -> str:
return super().audio2text(audio_path)

View file

@ -1,10 +1,11 @@
from typing import List, Literal
from camel.toolkits import CodeExecutionToolkit as BaseCodeExecutionToolkit, FunctionTool
from app.service.task import Agents
from app.utils.listen.toolkit_listen import listen_toolkit
from app.utils.listen.toolkit_listen import auto_listen_toolkit
from app.utils.toolkit.abstract_toolkit import AbstractToolkit
@auto_listen_toolkit(BaseCodeExecutionToolkit)
class CodeExecutionToolkit(BaseCodeExecutionToolkit, AbstractToolkit):
agent_name: str = Agents.developer_agent
@ -21,18 +22,6 @@ class CodeExecutionToolkit(BaseCodeExecutionToolkit, AbstractToolkit):
self.api_task_id = api_task_id
super().__init__(sandbox, verbose, unsafe_mode, import_white_list, require_confirm, timeout)
@listen_toolkit(
BaseCodeExecutionToolkit.execute_code,
)
def execute_code(self, code: str, code_type: str = "python") -> str:
return super().execute_code(code, code_type)
@listen_toolkit(
BaseCodeExecutionToolkit.execute_command,
)
def execute_command(self, command: str) -> str | tuple[str, str]:
return super().execute_command(command)
def get_tools(self) -> List[FunctionTool]:
return [
FunctionTool(self.execute_code),

View file

@ -1,10 +1,11 @@
from camel.toolkits import Crawl4AIToolkit as BaseCrawl4AIToolkit
from app.service.task import Agents
from app.utils.listen.toolkit_listen import listen_toolkit
from app.utils.listen.toolkit_listen import auto_listen_toolkit
from app.utils.toolkit.abstract_toolkit import AbstractToolkit
@auto_listen_toolkit(BaseCrawl4AIToolkit)
class Crawl4AIToolkit(BaseCrawl4AIToolkit, AbstractToolkit):
agent_name: str = Agents.search_agent
@ -12,18 +13,5 @@ class Crawl4AIToolkit(BaseCrawl4AIToolkit, AbstractToolkit):
self.api_task_id = api_task_id
super().__init__(timeout)
# async def _get_client(self):
# r"""Get or create the AsyncWebCrawler client."""
# if self._client is None:
# from crawl4ai import AsyncWebCrawler
# self._client = AsyncWebCrawler(use_managed_browser=True)
# await self._client.__aenter__()
# return self._client
@listen_toolkit(BaseCrawl4AIToolkit.scrape)
async def scrape(self, url: str) -> str:
return await super().scrape(url)
def toolkit_name(self) -> str:
return "Crawl Toolkit"

View file

@ -3,10 +3,11 @@ from camel.toolkits import ExcelToolkit as BaseExcelToolkit
from app.component.environment import env
from app.service.task import Agents
from app.utils.listen.toolkit_listen import listen_toolkit
from app.utils.listen.toolkit_listen import auto_listen_toolkit
from app.utils.toolkit.abstract_toolkit import AbstractToolkit
@auto_listen_toolkit(BaseExcelToolkit)
class ExcelToolkit(BaseExcelToolkit, AbstractToolkit):
agent_name: str = Agents.document_agent
@ -20,7 +21,3 @@ class ExcelToolkit(BaseExcelToolkit, AbstractToolkit):
if working_directory is None:
working_directory = env("file_save_path", os.path.expanduser("~/Downloads"))
super().__init__(timeout=timeout, working_directory=working_directory)
@listen_toolkit(BaseExcelToolkit.extract_excel_content)
def extract_excel_content(self, document_path: str) -> str:
return super().extract_excel_content(document_path)

View file

@ -5,10 +5,11 @@ from camel.toolkits import FileToolkit as BaseFileToolkit
from app.component.environment import env
from app.service.task import process_task
from app.service.task import ActionWriteFileData, Agents, get_task_lock
from app.utils.listen.toolkit_listen import listen_toolkit
from app.utils.listen.toolkit_listen import auto_listen_toolkit, listen_toolkit
from app.utils.toolkit.abstract_toolkit import AbstractToolkit
@auto_listen_toolkit(BaseFileToolkit)
class FileToolkit(BaseFileToolkit, AbstractToolkit):
agent_name: str = Agents.document_agent
@ -54,15 +55,3 @@ class FileToolkit(BaseFileToolkit, AbstractToolkit):
)
)
return res
@listen_toolkit(
BaseFileToolkit.read_file,
)
def read_file(self, file_paths: str | list[str]) -> str | dict[str, str]:
return super().read_file(file_paths)
@listen_toolkit(
BaseFileToolkit.edit_file,
)
def edit_file(self, file_path: str, old_content: str, new_content: str) -> str:
return super().edit_file(file_path, old_content, new_content)

View file

@ -3,10 +3,11 @@ from camel.toolkits import GithubToolkit as BaseGithubToolkit
from camel.toolkits.function_tool import FunctionTool
from app.component.environment import env
from app.service.task import Agents
from app.utils.listen.toolkit_listen import listen_toolkit
from app.utils.listen.toolkit_listen import auto_listen_toolkit
from app.utils.toolkit.abstract_toolkit import AbstractToolkit
@auto_listen_toolkit(BaseGithubToolkit)
class GithubToolkit(BaseGithubToolkit, AbstractToolkit):
agent_name: str = Agents.developer_agent
@ -19,86 +20,6 @@ class GithubToolkit(BaseGithubToolkit, AbstractToolkit):
super().__init__(access_token, timeout)
self.api_task_id = api_task_id
@listen_toolkit(
BaseGithubToolkit.create_pull_request,
lambda _,
repo_name,
file_path,
new_content,
pr_title,
body,
branch_name: f"Create PR in {repo_name} for {file_path} with title '{pr_title}', branch '{branch_name}', content '{new_content}'",
)
def create_pull_request(
self,
repo_name: str,
file_path: str,
new_content: str,
pr_title: str,
body: str,
branch_name: str,
) -> str:
return super().create_pull_request(repo_name, file_path, new_content, pr_title, body, branch_name)
@listen_toolkit(
BaseGithubToolkit.get_issue_list,
lambda _, repo_name, state="all": f"Get issue list from {repo_name} with state '{state}'",
lambda issues: f"Retrieved {len(issues)} issues",
)
def get_issue_list(
self, repo_name: str, state: Literal["open", "closed", "all"] = "all"
) -> list[dict[str, object]]:
return super().get_issue_list(repo_name, state)
@listen_toolkit(
BaseGithubToolkit.get_issue_content,
lambda _, repo_name, issue_number: f"Get content of issue {issue_number} from {repo_name}",
)
def get_issue_content(self, repo_name: str, issue_number: int) -> str:
return super().get_issue_content(repo_name, issue_number)
@listen_toolkit(
BaseGithubToolkit.get_pull_request_list,
lambda _, repo_name, state="all": f"Get pull request list from {repo_name} with state '{state}'",
lambda prs: f"Retrieved {len(prs)} pull requests",
)
def get_pull_request_list(
self, repo_name: str, state: Literal["open", "closed", "all"] = "all"
) -> list[dict[str, object]]:
return super().get_pull_request_list(repo_name, state)
@listen_toolkit(
BaseGithubToolkit.get_pull_request_code,
lambda _, repo_name, pr_number: f"Get code for pull request {pr_number} in {repo_name}",
lambda code: f"Retrieved {len(code)} code files",
)
def get_pull_request_code(self, repo_name: str, pr_number: int) -> list[dict[str, str]]:
return super().get_pull_request_code(repo_name, pr_number)
@listen_toolkit(
BaseGithubToolkit.get_pull_request_comments,
lambda _, repo_name, pr_number: f"Get comments for pull request {pr_number} in {repo_name}",
lambda comments: f"Retrieved {len(comments)} comments",
)
def get_pull_request_comments(self, repo_name: str, pr_number: int) -> list[dict[str, str]]:
return super().get_pull_request_comments(repo_name, pr_number)
@listen_toolkit(
BaseGithubToolkit.get_all_file_paths,
lambda _, repo_name, path="": f"Get all file paths from {repo_name}, path '{path}'",
lambda paths: f"Retrieved {len(paths)} file paths",
)
def get_all_file_paths(self, repo_name: str, path: str = "") -> list[str]:
return super().get_all_file_paths(repo_name, path)
@listen_toolkit(
BaseGithubToolkit.retrieve_file_content,
lambda _, repo_name, file_path: f"Retrieve content of file {file_path} from {repo_name}",
lambda content: f"Retrieved content of length {len(content)}",
)
def retrieve_file_content(self, repo_name: str, file_path: str) -> str:
return super().retrieve_file_content(repo_name, file_path)
@classmethod
def get_can_use_tools(cls, api_task_id: str) -> list[FunctionTool]:
if env("GITHUB_ACCESS_TOKEN"):

View file

@ -1,11 +1,12 @@
from typing import Any, Dict, List
from app.component.environment import env
from app.service.task import Agents
from app.utils.listen.toolkit_listen import listen_toolkit
from app.utils.listen.toolkit_listen import auto_listen_toolkit
from app.utils.toolkit.abstract_toolkit import AbstractToolkit
from camel.toolkits import GoogleCalendarToolkit as BaseGoogleCalendarToolkit
@auto_listen_toolkit(BaseGoogleCalendarToolkit)
class GoogleCalendarToolkit(BaseGoogleCalendarToolkit, AbstractToolkit):
agent_name: str = Agents.social_medium_agent
@ -13,44 +14,6 @@ class GoogleCalendarToolkit(BaseGoogleCalendarToolkit, AbstractToolkit):
self.api_task_id = api_task_id
super().__init__(timeout)
@listen_toolkit(BaseGoogleCalendarToolkit.create_event)
def create_event(
self,
event_title: str,
start_time: str,
end_time: str,
description: str = "",
location: str = "",
attendees_email: List[str] | None = None,
timezone: str = "UTC",
) -> Dict[str, Any]:
return super().create_event(event_title, start_time, end_time, description, location, attendees_email, timezone)
@listen_toolkit(BaseGoogleCalendarToolkit.get_events)
def get_events(self, max_results: int = 10, time_min: str | None = None) -> List[Dict[str, Any]] | Dict[str, Any]:
return super().get_events(max_results, time_min)
@listen_toolkit(BaseGoogleCalendarToolkit.update_event)
def update_event(
self,
event_id: str,
event_title: str | None = None,
start_time: str | None = None,
end_time: str | None = None,
description: str | None = None,
location: str | None = None,
attendees_email: List[str] | None = None,
) -> Dict[str, Any]:
return super().update_event(event_id, event_title, start_time, end_time, description, location, attendees_email)
@listen_toolkit(BaseGoogleCalendarToolkit.delete_event)
def delete_event(self, event_id: str) -> str:
return super().delete_event(event_id)
@listen_toolkit(BaseGoogleCalendarToolkit.get_calendar_details)
def get_calendar_details(self) -> Dict[str, Any]:
return super().get_calendar_details()
@classmethod
def get_can_use_tools(cls, api_task_id: str):
if env("GOOGLE_CLIENT_ID") and env("GOOGLE_CLIENT_SECRET"):

View file

@ -3,12 +3,13 @@ from camel.toolkits.base import BaseToolkit
from loguru import logger
from camel.toolkits.function_tool import FunctionTool
from app.service.task import Action, ActionAskData, ActionNoticeData, get_task_lock
from app.utils.listen.toolkit_listen import listen_toolkit
from app.utils.listen.toolkit_listen import auto_listen_toolkit, listen_toolkit
from app.utils.toolkit.abstract_toolkit import AbstractToolkit
from app.service.task import process_task
# Rewrite HumanToolkit because the system's user interaction was using console, but in electron we cannot use console. Changed to use SSE response to let frontend show dialog for user interaction
@auto_listen_toolkit(BaseToolkit)
class HumanToolkit(BaseToolkit, AbstractToolkit):
r"""A class representing a toolkit for human interaction.
Note:

View file

@ -16,7 +16,7 @@ from loguru import logger
from app.component.environment import env
from app.exception.exception import ProgramException
from app.service.task import Agents
from app.utils.listen.toolkit_listen import listen_toolkit
from app.utils.listen.toolkit_listen import auto_listen_toolkit, listen_toolkit
from app.utils.toolkit.abstract_toolkit import AbstractToolkit
@ -124,6 +124,7 @@ class BrowserSession(BaseHybridBrowserSession):
break
@auto_listen_toolkit(BaseHybridBrowserToolkit)
class HybridBrowserPythonToolkit(BaseHybridBrowserToolkit, AbstractToolkit):
agent_name: str = Agents.search_agent
@ -224,14 +225,6 @@ class HybridBrowserPythonToolkit(BaseHybridBrowserToolkit, AbstractToolkit):
self._agent: PlaywrightLLMAgent | None = None
self._unified_script = self._load_unified_analyzer()
@listen_toolkit(BaseHybridBrowserToolkit.browser_open)
async def browser_open(self) -> Dict[str, str]:
return await super().browser_open()
@listen_toolkit(BaseHybridBrowserToolkit.browser_close)
async def browser_close(self) -> str:
return await super().browser_close()
@listen_toolkit(BaseHybridBrowserToolkit.browser_visit_page, lambda _, url: url)
async def browser_visit_page(self, url: str) -> Dict[str, Any]:
r"""Navigates to a URL.
@ -282,66 +275,6 @@ class HybridBrowserPythonToolkit(BaseHybridBrowserToolkit, AbstractToolkit):
return {"result": nav_result, "snapshot": snapshot, **tab_info}
@listen_toolkit(BaseHybridBrowserToolkit.browser_back)
async def browser_back(self) -> Dict[str, Any]:
return await super().browser_back()
@listen_toolkit(BaseHybridBrowserToolkit.browser_forward)
async def browser_forward(self) -> Dict[str, Any]:
return await super().browser_forward()
@listen_toolkit(BaseHybridBrowserToolkit.browser_click)
async def browser_click(self, *, ref: str) -> Dict[str, Any]:
return await super().browser_click(ref=ref)
@listen_toolkit(BaseHybridBrowserToolkit.browser_type)
async def browser_type(self, *, ref: str, text: str) -> Dict[str, Any]:
return await super().browser_type(ref=ref, text=text)
@listen_toolkit(BaseHybridBrowserToolkit.browser_switch_tab)
async def browser_switch_tab(self, *, tab_id: str) -> Dict[str, Any]:
return await super().browser_switch_tab(tab_id=tab_id)
@listen_toolkit(BaseHybridBrowserToolkit.browser_select)
async def browser_select(self, *, ref: str, value: str) -> Dict[str, str]:
return await super().browser_select(ref=ref, value=value)
@listen_toolkit(BaseHybridBrowserToolkit.browser_scroll)
async def browser_scroll(self, *, direction: str, amount: int) -> Dict[str, str]:
return await super().browser_scroll(direction=direction, amount=amount)
@listen_toolkit(BaseHybridBrowserToolkit.browser_wait_user)
async def browser_wait_user(self, timeout_sec: float | None = None) -> Dict[str, str]:
return await super().browser_wait_user(timeout_sec)
@listen_toolkit(BaseHybridBrowserToolkit.browser_enter)
async def browser_enter(self) -> Dict[str, str]:
return await super().browser_enter()
@listen_toolkit(BaseHybridBrowserToolkit.browser_solve_task)
async def browser_solve_task(self, task_prompt: str, start_url: str, max_steps: int = 15) -> str:
return await super().browser_solve_task(task_prompt, start_url, max_steps)
@listen_toolkit(BaseHybridBrowserToolkit.browser_get_page_snapshot)
async def browser_get_page_snapshot(self) -> str:
return await super().browser_get_page_snapshot()
@listen_toolkit(BaseHybridBrowserToolkit.browser_get_som_screenshot)
async def browser_get_som_screenshot(self):
return await super().browser_get_som_screenshot()
@listen_toolkit(BaseHybridBrowserToolkit.browser_get_page_links)
async def browser_get_page_links(self, *, ref: List[str]) -> Dict[str, Any]:
return await super().browser_get_page_links(ref=ref)
@listen_toolkit(BaseHybridBrowserToolkit.browser_close_tab)
async def browser_close_tab(self, *, tab_id: str) -> Dict[str, Any]:
return await super().browser_close_tab(tab_id=tab_id)
@listen_toolkit(BaseHybridBrowserToolkit.browser_get_tab_info)
async def browser_get_tab_info(self) -> Dict[str, Any]:
return await super().browser_get_tab_info()
@classmethod
def get_can_use_tools(cls, api_task_id: str) -> list[FunctionTool]:
browser = HybridBrowserPythonToolkit(

View file

@ -16,7 +16,7 @@ from camel.toolkits.hybrid_browser_toolkit.ws_wrapper import WebSocketBrowserWra
from app.component.command import bun, uv
from app.component.environment import env
from app.service.task import Agents
from app.utils.listen.toolkit_listen import listen_toolkit
from app.utils.listen.toolkit_listen import auto_listen_toolkit
from app.utils.toolkit.abstract_toolkit import AbstractToolkit
@ -210,6 +210,7 @@ class WebSocketConnectionPool:
websocket_connection_pool = WebSocketConnectionPool()
@auto_listen_toolkit(BaseHybridBrowserToolkit)
class HybridBrowserToolkit(BaseHybridBrowserToolkit, AbstractToolkit):
agent_name: str = Agents.search_agent
@ -240,7 +241,10 @@ class HybridBrowserToolkit(BaseHybridBrowserToolkit, AbstractToolkit):
cdp_keep_current_page: bool = False,
full_visual_mode: bool = False,
) -> None:
logger.info(f"[HybridBrowserToolkit] Initializing with api_task_id: {api_task_id}")
self.api_task_id = api_task_id
logger.debug(f"[HybridBrowserToolkit] api_task_id set to: {self.api_task_id}")
logger.debug(f"[HybridBrowserToolkit] Calling super().__init__ with session_id: {session_id}")
super().__init__(
headless=headless,
user_data_dir=user_data_dir,
@ -264,16 +268,20 @@ class HybridBrowserToolkit(BaseHybridBrowserToolkit, AbstractToolkit):
cdp_keep_current_page=cdp_keep_current_page,
full_visual_mode=full_visual_mode,
)
logger.info(f"[HybridBrowserToolkit] Initialization complete for api_task_id: {self.api_task_id}")
async def _ensure_ws_wrapper(self):
"""Ensure WebSocket wrapper is initialized using connection pool."""
logger.debug(f"[HybridBrowserToolkit] _ensure_ws_wrapper called for api_task_id: {getattr(self, 'api_task_id', 'NOT SET')}")
global websocket_connection_pool
# Get session ID from config or use default
session_id = self._ws_config.get("session_id", "default")
logger.debug(f"[HybridBrowserToolkit] Using session_id: {session_id}")
# Get or create connection from pool
self._ws_wrapper = await websocket_connection_pool.get_connection(session_id, self._ws_config)
logger.info(f"[HybridBrowserToolkit] WebSocket wrapper initialized for session: {session_id}")
# Additional health check
if self._ws_wrapper.websocket is None:
@ -336,74 +344,3 @@ class HybridBrowserToolkit(BaseHybridBrowserToolkit, AbstractToolkit):
if hasattr(self, "_ws_wrapper") and self._ws_wrapper:
session_id = self._ws_config.get("session_id", "default")
logger.debug(f"HybridBrowserToolkit for session {session_id} is being garbage collected")
@listen_toolkit(BaseHybridBrowserToolkit.browser_open)
async def browser_open(self) -> Dict[str, Any]:
return await super().browser_open()
@listen_toolkit(BaseHybridBrowserToolkit.browser_close)
async def browser_close(self) -> str:
return await super().browser_close()
@listen_toolkit(BaseHybridBrowserToolkit.browser_visit_page)
async def browser_visit_page(self, url: str) -> Dict[str, Any]:
logger.debug(f"browser_visit_page called with URL: {url}")
try:
result = await super().browser_visit_page(url)
logger.debug(f"browser_visit_page succeeded for URL: {url}")
return result
except Exception as e:
logger.error(f"browser_visit_page failed for URL {url}: {type(e).__name__}: {e}")
raise
@listen_toolkit(BaseHybridBrowserToolkit.browser_back)
async def browser_back(self) -> Dict[str, Any]:
return await super().browser_back()
@listen_toolkit(BaseHybridBrowserToolkit.browser_forward)
async def browser_forward(self) -> Dict[str, Any]:
return await super().browser_forward()
@listen_toolkit(BaseHybridBrowserToolkit.browser_get_page_snapshot)
async def browser_get_page_snapshot(self) -> str:
return await super().browser_get_page_snapshot()
@listen_toolkit(BaseHybridBrowserToolkit.browser_get_som_screenshot)
async def browser_get_som_screenshot(self, read_image: bool = False, instruction: str | None = None) -> str:
return await super().browser_get_som_screenshot(read_image, instruction)
@listen_toolkit(BaseHybridBrowserToolkit.browser_click)
async def browser_click(self, *, ref: str) -> Dict[str, Any]:
return await super().browser_click(ref=ref)
@listen_toolkit(BaseHybridBrowserToolkit.browser_type)
async def browser_type(self, *, ref: str, text: str) -> Dict[str, Any]:
return await super().browser_type(ref=ref, text=text)
@listen_toolkit(BaseHybridBrowserToolkit.browser_select)
async def browser_select(self, *, ref: str, value: str) -> Dict[str, Any]:
return await super().browser_select(ref=ref, value=value)
@listen_toolkit(BaseHybridBrowserToolkit.browser_scroll)
async def browser_scroll(self, *, direction: str, amount: int = 500) -> Dict[str, Any]:
return await super().browser_scroll(direction=direction, amount=amount)
@listen_toolkit(BaseHybridBrowserToolkit.browser_enter)
async def browser_enter(self) -> Dict[str, Any]:
return await super().browser_enter()
@listen_toolkit(BaseHybridBrowserToolkit.browser_wait_user)
async def browser_wait_user(self, timeout_sec: float | None = None) -> Dict[str, Any]:
return await super().browser_wait_user(timeout_sec)
@listen_toolkit(BaseHybridBrowserToolkit.browser_switch_tab)
async def browser_switch_tab(self, *, tab_id: str) -> Dict[str, Any]:
return await super().browser_switch_tab(tab_id=tab_id)
@listen_toolkit(BaseHybridBrowserToolkit.browser_close_tab)
async def browser_close_tab(self, *, tab_id: str) -> Dict[str, Any]:
return await super().browser_close_tab(tab_id=tab_id)
@listen_toolkit(BaseHybridBrowserToolkit.browser_get_tab_info)
async def browser_get_tab_info(self) -> Dict[str, Any]:
return await super().browser_get_tab_info()

View file

@ -2,10 +2,11 @@ from camel.models import BaseModelBackend
from camel.toolkits import ImageAnalysisToolkit as BaseImageAnalysisToolkit
from app.service.task import Agents
from app.utils.listen.toolkit_listen import listen_toolkit
from app.utils.listen.toolkit_listen import auto_listen_toolkit
from app.utils.toolkit.abstract_toolkit import AbstractToolkit
@auto_listen_toolkit(BaseImageAnalysisToolkit)
class ImageAnalysisToolkit(BaseImageAnalysisToolkit, AbstractToolkit):
agent_name: str = Agents.multi_modal_agent
@ -17,24 +18,3 @@ class ImageAnalysisToolkit(BaseImageAnalysisToolkit, AbstractToolkit):
):
super().__init__(model, timeout)
self.api_task_id = api_task_id
@listen_toolkit(
BaseImageAnalysisToolkit.image_to_text,
lambda _,
image_path,
sys_prompt: f"transcribe image from {image_path} and ask sys_prompt: {sys_prompt}",
)
def image_to_text(self, image_path: str, sys_prompt: str | None = None) -> str:
return super().image_to_text(image_path, sys_prompt)
@listen_toolkit(
BaseImageAnalysisToolkit.ask_question_about_image,
lambda _,
image_path,
question,
sys_prompt: f"transcribe image from {image_path} and ask question: {question} with sys_prompt: {sys_prompt}",
)
def ask_question_about_image(
self, image_path: str, question: str, sys_prompt: str | None = None
) -> str:
return super().ask_question_about_image(image_path, question, sys_prompt)

View file

@ -2,10 +2,11 @@ from camel.toolkits import LinkedInToolkit as BaseLinkedInToolkit
from camel.toolkits.function_tool import FunctionTool
from app.component.environment import env
from app.service.task import Agents
from app.utils.listen.toolkit_listen import listen_toolkit
from app.utils.listen.toolkit_listen import auto_listen_toolkit
from app.utils.toolkit.abstract_toolkit import AbstractToolkit
@auto_listen_toolkit(BaseLinkedInToolkit)
class LinkedInToolkit(BaseLinkedInToolkit, AbstractToolkit):
agent_name: str = Agents.social_medium_agent
@ -13,27 +14,6 @@ class LinkedInToolkit(BaseLinkedInToolkit, AbstractToolkit):
super().__init__(timeout)
self.api_task_id = api_task_id
@listen_toolkit(
BaseLinkedInToolkit.create_post,
lambda _, text: f"create a LinkedIn post with text: {text}",
)
def create_post(self, text: str) -> dict:
return super().create_post(text)
@listen_toolkit(
BaseLinkedInToolkit.delete_post,
lambda _, post_id: f"delete LinkedIn post with id: {post_id}",
)
def delete_post(self, post_id: str) -> str:
return super().delete_post(post_id)
@listen_toolkit(
BaseLinkedInToolkit.get_profile,
lambda _, include_id: f"get LinkedIn profile with include_id: {include_id}",
)
def get_profile(self, include_id: bool = False) -> dict:
return super().get_profile(include_id)
@classmethod
def get_can_use_tools(cls, api_task_id: str) -> list[FunctionTool]:
if env("LINKEDIN_ACCESS_TOKEN"):

View file

@ -2,17 +2,14 @@ from typing import Dict, List
from camel.toolkits import MarkItDownToolkit as BaseMarkItDownToolkit
from app.service.task import Agents
from app.utils.listen.toolkit_listen import listen_toolkit
from app.utils.listen.toolkit_listen import auto_listen_toolkit
from app.utils.toolkit.abstract_toolkit import AbstractToolkit
@auto_listen_toolkit(BaseMarkItDownToolkit)
class MarkItDownToolkit(BaseMarkItDownToolkit, AbstractToolkit):
agent_name: str = Agents.document_agent
def __init__(self, api_task_id: str, timeout: float | None = None):
self.api_task_id = api_task_id
super().__init__(timeout)
@listen_toolkit(BaseMarkItDownToolkit.read_files)
def read_files(self, file_paths: List[str]) -> Dict[str, str]:
return super().read_files(file_paths)

View file

@ -5,10 +5,11 @@ from typing import Optional
from app.component.environment import env
from app.service.task import Agents
from app.utils.listen.toolkit_listen import listen_toolkit
from app.utils.listen.toolkit_listen import auto_listen_toolkit
from app.utils.toolkit.abstract_toolkit import AbstractToolkit
@auto_listen_toolkit(BaseNoteTakingToolkit)
class NoteTakingToolkit(BaseNoteTakingToolkit, AbstractToolkit):
agent_name: str = Agents.document_agent
@ -25,19 +26,3 @@ class NoteTakingToolkit(BaseNoteTakingToolkit, AbstractToolkit):
if working_directory is None:
working_directory = env("file_save_path", os.path.expanduser("~/.eigent/notes")) + "/note.md"
super().__init__(working_directory=working_directory, timeout=timeout)
@listen_toolkit(BaseNoteTakingToolkit.append_note)
def append_note(self, note_name: str, content: str) -> str:
return super().append_note(note_name=note_name, content=content)
@listen_toolkit(BaseNoteTakingToolkit.read_note)
def read_note(self, note_name: Optional[str] = "all_notes") -> str:
return super().read_note(note_name=note_name)
@listen_toolkit(BaseNoteTakingToolkit.create_note)
def create_note(self, note_name: str, content: str, overwrite: bool = False) -> str:
return super().create_note(note_name=note_name, content=content, overwrite=overwrite)
@listen_toolkit(BaseNoteTakingToolkit.list_note)
def list_note(self) -> str:
return super().list_note()

View file

@ -3,10 +3,11 @@ from camel.toolkits import NotionToolkit as BaseNotionToolkit
from camel.toolkits.function_tool import FunctionTool
from app.component.environment import env
from app.service.task import Agents
from app.utils.listen.toolkit_listen import listen_toolkit
from app.utils.listen.toolkit_listen import auto_listen_toolkit
from app.utils.toolkit.abstract_toolkit import AbstractToolkit
@auto_listen_toolkit(BaseNotionToolkit)
class NotionToolkit(BaseNotionToolkit, AbstractToolkit):
agent_name: str = Agents.document_agent
@ -19,29 +20,6 @@ class NotionToolkit(BaseNotionToolkit, AbstractToolkit):
super().__init__(notion_token, timeout)
self.api_task_id = api_task_id
@listen_toolkit(
BaseNotionToolkit.list_all_pages,
lambda _: "list all pages in Notion workspace",
lambda result: f"{len(result)} pages found",
)
def list_all_pages(self) -> List[dict]:
return super().list_all_pages()
@listen_toolkit(
BaseNotionToolkit.list_all_users,
lambda _: "list all users in Notion workspace",
lambda result: f"{len(result)} users found",
)
def list_all_users(self) -> List[dict]:
return super().list_all_users()
@listen_toolkit(
BaseNotionToolkit.get_notion_block_text_content,
lambda _, page_id: f"get text content of page with id: {page_id}",
)
def get_notion_block_text_content(self, block_id: str) -> str:
return super().get_notion_block_text_content(block_id)
@classmethod
def get_can_use_tools(cls, api_task_id: str) -> List[FunctionTool]:
if env("NOTION_TOKEN"):

View file

@ -3,11 +3,12 @@ from camel.toolkits import OpenAIImageToolkit as BaseOpenAIImageToolkit
from app.component.environment import env
from app.service.task import Agents
from app.utils.listen.toolkit_listen import listen_toolkit
from app.utils.listen.toolkit_listen import auto_listen_toolkit, listen_toolkit
from app.utils.toolkit.abstract_toolkit import AbstractToolkit
from typing import Literal, Optional, Union, List
@auto_listen_toolkit(BaseOpenAIImageToolkit)
class OpenAIImageToolkit(BaseOpenAIImageToolkit, AbstractToolkit):
agent_name: str = Agents.multi_modal_agent

View file

@ -4,11 +4,12 @@ from camel.toolkits import PPTXToolkit as BasePPTXToolkit
from app.component.environment import env
from app.service.task import ActionWriteFileData, Agents, get_task_lock
from app.utils.listen.toolkit_listen import listen_toolkit
from app.utils.listen.toolkit_listen import auto_listen_toolkit, listen_toolkit
from app.utils.toolkit.abstract_toolkit import AbstractToolkit
from app.service.task import process_task
@auto_listen_toolkit(BasePPTXToolkit)
class PPTXToolkit(BasePPTXToolkit, AbstractToolkit):
agent_name: str = Agents.document_agent

View file

@ -4,10 +4,11 @@ from camel.toolkits import PyAutoGUIToolkit as BasePyAutoGUIToolkit
from app.component.environment import env
from app.service.task import Agents
from app.utils.listen.toolkit_listen import listen_toolkit
from app.utils.listen.toolkit_listen import auto_listen_toolkit
from app.utils.toolkit.abstract_toolkit import AbstractToolkit
@auto_listen_toolkit(BasePyAutoGUIToolkit)
class PyAutoGUIToolkit(BasePyAutoGUIToolkit, AbstractToolkit):
agent_name: str = Agents.search_agent
@ -21,69 +22,3 @@ class PyAutoGUIToolkit(BasePyAutoGUIToolkit, AbstractToolkit):
screenshots_dir = env("file_save_path", os.path.expanduser("~/Downloads"))
super().__init__(timeout, screenshots_dir)
self.api_task_id = api_task_id
@listen_toolkit(BasePyAutoGUIToolkit.mouse_move, lambda _, x, y: f"mouse move to {x}, {y}")
def mouse_move(self, x: int, y: int) -> str:
return super().mouse_move(x, y)
@listen_toolkit(
BasePyAutoGUIToolkit.mouse_click,
lambda _, button="left", clicks=1, x=None, y=None: f"mouse click {button} {clicks} times at {x}, {y}",
)
def mouse_click(
self,
button: Literal["left", "middle", "right"] = "left",
clicks: int = 1,
x: int | None = None,
y: int | None = None,
) -> str:
return super().mouse_click(button, clicks, x, y)
@listen_toolkit(
BasePyAutoGUIToolkit.keyboard_type,
lambda _, text, interval=0: f"keyboard type {text}, interval {interval}",
)
def keyboard_type(self, text: str, interval: float = 0) -> str:
return super().keyboard_type(text, interval)
@listen_toolkit(BasePyAutoGUIToolkit.take_screenshot)
def take_screenshot(self) -> str:
return super().take_screenshot()
@listen_toolkit(BasePyAutoGUIToolkit.get_mouse_position)
def get_mouse_position(self) -> str:
return super().get_mouse_position()
@listen_toolkit(BasePyAutoGUIToolkit.press_key, lambda _, key: f"press key {key}")
def press_key(self, key: str | list[str]) -> str:
return super().press_key(key)
@listen_toolkit(BasePyAutoGUIToolkit.hotkey, lambda _, keys: f"hotkey {keys}")
def hotkey(self, keys: List[str]) -> str:
return super().hotkey(keys)
@listen_toolkit(
BasePyAutoGUIToolkit.mouse_drag,
lambda _,
start_x,
start_y,
end_x,
end_y,
button="left": f"mouse drag from {start_x}, {start_y} to {end_x}, {end_y} with {button} button",
)
def mouse_drag(
self,
start_x: int,
start_y: int,
end_x: int,
end_y: int,
button: Literal["left", "middle", "right"] = "left",
) -> str:
return super().mouse_drag(start_x, start_y, end_x, end_y, button)
@listen_toolkit(
BasePyAutoGUIToolkit.scroll,
lambda _, scroll_amount, x=None, y=None: f"scroll {scroll_amount} at {x}, {y}",
)
def scroll(self, scroll_amount: int, x: int | None = None, y: int | None = None) -> str:
return super().scroll(scroll_amount, x, y)

View file

@ -3,10 +3,11 @@ from camel.toolkits import RedditToolkit as BaseRedditToolkit
from camel.toolkits.function_tool import FunctionTool
from app.component.environment import env
from app.service.task import Agents
from app.utils.listen.toolkit_listen import listen_toolkit
from app.utils.listen.toolkit_listen import auto_listen_toolkit
from app.utils.toolkit.abstract_toolkit import AbstractToolkit
@auto_listen_toolkit(BaseRedditToolkit)
class RedditToolkit(BaseRedditToolkit, AbstractToolkit):
agent_name: str = Agents.social_medium_agent
@ -20,47 +21,6 @@ class RedditToolkit(BaseRedditToolkit, AbstractToolkit):
super().__init__(retries, delay, timeout)
self.api_task_id = api_task_id
@listen_toolkit(
BaseRedditToolkit.collect_top_posts,
lambda _,
subreddit_name,
post_limit=5,
comment_limit=5: f"collect top posts from subreddit: {subreddit_name} with post limit: {post_limit} and comment limit: {comment_limit}",
lambda result: f"top posts collected: {result}",
)
def collect_top_posts(
self, subreddit_name: str, post_limit: int = 5, comment_limit: int = 5
) -> List[Dict[str, Any]] | str:
return super().collect_top_posts(subreddit_name, post_limit, comment_limit)
@listen_toolkit(
BaseRedditToolkit.perform_sentiment_analysis,
lambda _, data: f"perform sentiment analysis on data number: {len(data)}",
lambda result: f"perform analysis result: {result}",
)
def perform_sentiment_analysis(self, data: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
return super().perform_sentiment_analysis(data)
@listen_toolkit(
BaseRedditToolkit.track_keyword_discussions,
lambda _,
subreddits,
keywords,
post_limit=10,
comment_limit=10,
sentiment_analysis=False: f"track keyword discussions for subreddits: {subreddits}, keywords: {keywords}",
lambda result: f"track keyword discussions result: {result}",
)
def track_keyword_discussions(
self,
subreddits: List[str],
keywords: List[str],
post_limit: int = 10,
comment_limit: int = 10,
sentiment_analysis: bool = False,
) -> List[Dict[str, Any]] | str:
return super().track_keyword_discussions(subreddits, keywords, post_limit, comment_limit, sentiment_analysis)
@classmethod
def get_can_use_tools(cls, api_task_id: str) -> list[FunctionTool]:
if env("REDDIT_CLIENT_ID") and env("REDDIT_CLIENT_SECRET") and env("REDDIT_USER_AGENT"):

View file

@ -3,10 +3,11 @@ from camel.toolkits import ScreenshotToolkit as BaseScreenshotToolkit
from app.component.environment import env
from app.service.task import Agents
from app.utils.listen.toolkit_listen import listen_toolkit
from app.utils.listen.toolkit_listen import auto_listen_toolkit
from app.utils.toolkit.abstract_toolkit import AbstractToolkit
@auto_listen_toolkit(BaseScreenshotToolkit)
class ScreenshotToolkit(BaseScreenshotToolkit, AbstractToolkit):
agent_name: str = Agents.developer_agent
@ -15,13 +16,3 @@ class ScreenshotToolkit(BaseScreenshotToolkit, AbstractToolkit):
if working_directory is None:
working_directory = env("file_save_path", os.path.expanduser("~/Downloads"))
super().__init__(working_directory, timeout)
@listen_toolkit(BaseScreenshotToolkit.take_screenshot_and_read_image)
def take_screenshot_and_read_image(
self, filename: str, save_to_file: bool = True, read_image: bool = True, instruction: str | None = None
) -> str:
return super().take_screenshot_and_read_image(filename, save_to_file, read_image, instruction)
@listen_toolkit(BaseScreenshotToolkit.read_image)
def read_image(self, image_path: str, instruction: str = "") -> str:
return super().read_image(image_path, instruction)

View file

@ -5,10 +5,11 @@ import httpx
from loguru import logger
from app.component.environment import env, env_not_empty
from app.service.task import Agents
from app.utils.listen.toolkit_listen import listen_toolkit
from app.utils.listen.toolkit_listen import auto_listen_toolkit, listen_toolkit
from app.utils.toolkit.abstract_toolkit import AbstractToolkit
@auto_listen_toolkit(BaseSearchToolkit)
class SearchToolkit(BaseSearchToolkit, AbstractToolkit):
agent_name: str = Agents.search_agent

View file

@ -3,10 +3,11 @@ from camel.toolkits.function_tool import FunctionTool
from loguru import logger
from app.component.environment import env
from app.service.task import Agents
from app.utils.listen.toolkit_listen import listen_toolkit
from app.utils.listen.toolkit_listen import auto_listen_toolkit
from app.utils.toolkit.abstract_toolkit import AbstractToolkit
@auto_listen_toolkit(BaseSlackToolkit)
class SlackToolkit(BaseSlackToolkit, AbstractToolkit):
agent_name: str = Agents.social_medium_agent
@ -14,71 +15,6 @@ class SlackToolkit(BaseSlackToolkit, AbstractToolkit):
super().__init__(timeout)
self.api_task_id = api_task_id
@listen_toolkit(
BaseSlackToolkit.create_slack_channel,
lambda _, name, is_private=True: f"create a Slack channel with name: {name} and is_private: {is_private}",
)
def create_slack_channel(self, name: str, is_private: bool | None = True) -> str:
return super().create_slack_channel(name, is_private)
@listen_toolkit(
BaseSlackToolkit.join_slack_channel,
lambda _, channel_id: f"join Slack channel with id: {channel_id}",
)
def join_slack_channel(self, channel_id: str) -> str:
return super().join_slack_channel(channel_id)
@listen_toolkit(
BaseSlackToolkit.leave_slack_channel,
lambda _, channel_id: f"leave Slack channel with id: {channel_id}",
)
def leave_slack_channel(self, channel_id: str) -> str:
return super().leave_slack_channel(channel_id)
@listen_toolkit(
BaseSlackToolkit.get_slack_channel_information,
lambda _: "get Slack channel information",
)
def get_slack_channel_information(self) -> str:
return super().get_slack_channel_information()
@listen_toolkit(
BaseSlackToolkit.get_slack_channel_message,
lambda _, channel_id: f"get Slack channel message for channel id: {channel_id}",
)
def get_slack_channel_message(self, channel_id: str) -> str:
return super().get_slack_channel_message(channel_id)
@listen_toolkit(
BaseSlackToolkit.send_slack_message,
lambda _, message, channel_id, file_path=None, user=None: f"send Slack message: {message} to channel id: {channel_id}, file: {file_path}, user: {user}",
)
def send_slack_message(self, message: str, channel_id: str, file_path: str | None = None, user: str | None = None) -> str:
return super().send_slack_message(message, channel_id, file_path, user)
@listen_toolkit(
BaseSlackToolkit.delete_slack_message,
lambda _,
time_stamp,
channel_id: f"delete Slack message with timestamp: {time_stamp} in channel id: {channel_id}",
)
def delete_slack_message(self, time_stamp: str, channel_id: str) -> str:
return super().delete_slack_message(time_stamp, channel_id)
@listen_toolkit(
BaseSlackToolkit.get_slack_user_list,
lambda _: "get Slack user list",
)
def get_slack_user_list(self) -> str:
return super().get_slack_user_list()
@listen_toolkit(
BaseSlackToolkit.get_slack_user_info,
lambda _, user_id: f"get Slack user info with user id: {user_id}",
)
def get_slack_user_info(self, user_id: str) -> str:
return super().get_slack_user_info(user_id)
@classmethod
def get_can_use_tools(cls, api_task_id: str) -> list[FunctionTool]:
logger.debug(f"slack===={env('SLACK_BOT_TOKEN')}")

View file

@ -4,11 +4,12 @@ from camel.toolkits.terminal_toolkit import TerminalToolkit as BaseTerminalToolk
from camel.toolkits.terminal_toolkit.terminal_toolkit import _to_plain
from app.component.environment import env
from app.service.task import Action, ActionTerminalData, Agents, get_task_lock
from app.utils.listen.toolkit_listen import listen_toolkit
from app.utils.listen.toolkit_listen import auto_listen_toolkit
from app.utils.toolkit.abstract_toolkit import AbstractToolkit
from app.service.task import process_task
@auto_listen_toolkit(BaseTerminalToolkit)
class TerminalToolkit(BaseTerminalToolkit, AbstractToolkit):
agent_name: str = Agents.developer_agent
@ -67,45 +68,3 @@ class TerminalToolkit(BaseTerminalToolkit, AbstractToolkit):
)
if hasattr(task_lock, "add_background_task"):
task_lock.add_background_task(task)
@listen_toolkit(
BaseTerminalToolkit.shell_exec,
lambda _, id, command, block=True: f"id: {id}, command: {command}, block: {block}",
)
def shell_exec(self, id: str, command: str, block: bool = True) -> str:
return super().shell_exec(id=id, command=command, block=block)
@listen_toolkit(
BaseTerminalToolkit.shell_view,
lambda _, id: f"id: {id}",
)
def shell_view(self, id: str) -> str:
return super().shell_view(id)
@listen_toolkit(
BaseTerminalToolkit.shell_wait,
lambda _, id, wait_seconds=None: f"id: {id}, wait_seconds: {wait_seconds}",
)
def shell_wait(self, id: str, wait_seconds: float = 5.0) -> str:
return super().shell_wait(id=id, wait_seconds=wait_seconds)
@listen_toolkit(
BaseTerminalToolkit.shell_write_to_process,
lambda _, id, command: f"id: {id}, command: {command}",
)
def shell_write_to_process(self, id: str, command: str) -> str:
return super().shell_write_to_process(id=id, command=command)
@listen_toolkit(
BaseTerminalToolkit.shell_kill_process,
lambda _, id: f"id: {id}",
)
def shell_kill_process(self, id: str) -> str:
return super().shell_kill_process(id=id)
@listen_toolkit(
BaseTerminalToolkit.shell_ask_user_for_help,
lambda _, id, prompt: f"id: {id}, prompt: {prompt}",
)
def shell_ask_user_for_help(self, id: str, prompt: str) -> str:
return super().shell_ask_user_for_help(id=id, prompt=prompt)

View file

@ -1,40 +1,13 @@
from camel.toolkits import ThinkingToolkit as BaseThinkingToolkit
from app.utils.listen.toolkit_listen import listen_toolkit
from app.utils.listen.toolkit_listen import auto_listen_toolkit
from app.utils.toolkit.abstract_toolkit import AbstractToolkit
@auto_listen_toolkit(BaseThinkingToolkit)
class ThinkingToolkit(BaseThinkingToolkit, AbstractToolkit):
def __init__(self, api_task_id: str, agent_name: str, timeout: float | None = None):
super().__init__(timeout)
self.api_task_id = api_task_id
self.agent_name = agent_name
@listen_toolkit(BaseThinkingToolkit.plan)
def plan(self, plan: str) -> str:
return super().plan(plan)
@listen_toolkit(BaseThinkingToolkit.hypothesize)
def hypothesize(self, hypothesis: str) -> str:
return super().hypothesize(hypothesis)
@listen_toolkit(BaseThinkingToolkit.think)
def think(self, thought: str) -> str:
return super().think(thought)
@listen_toolkit(BaseThinkingToolkit.contemplate)
def contemplate(self, contemplation: str) -> str:
return super().contemplate(contemplation)
@listen_toolkit(BaseThinkingToolkit.critique)
def critique(self, critique: str) -> str:
return super().critique(critique)
@listen_toolkit(BaseThinkingToolkit.synthesize)
def synthesize(self, synthesis: str) -> str:
return super().synthesize(synthesis)
@listen_toolkit(BaseThinkingToolkit.reflect)
def reflect(self, reflection: str) -> str:
return super().reflect(reflection)

View file

@ -9,10 +9,11 @@ from camel.toolkits.twitter_toolkit import (
from app.component.environment import env
from app.service.task import Agents
from app.utils.listen.toolkit_listen import listen_toolkit
from app.utils.listen.toolkit_listen import auto_listen_toolkit, listen_toolkit
from app.utils.toolkit.abstract_toolkit import AbstractToolkit
@auto_listen_toolkit(BaseTwitterToolkit)
class TwitterToolkit(BaseTwitterToolkit, AbstractToolkit):
agent_name: str = Agents.social_medium_agent

View file

@ -4,10 +4,11 @@ from camel.toolkits import VideoAnalysisToolkit as BaseVideoAnalysisToolkit
from app.component.environment import env
from app.service.task import Agents
from app.utils.listen.toolkit_listen import listen_toolkit
from app.utils.listen.toolkit_listen import auto_listen_toolkit
from app.utils.toolkit.abstract_toolkit import AbstractToolkit
@auto_listen_toolkit(BaseVideoAnalysisToolkit)
class VideoAnalysisToolkit(BaseVideoAnalysisToolkit, AbstractToolkit):
agent_name: str = Agents.multi_modal_agent
@ -36,10 +37,3 @@ class VideoAnalysisToolkit(BaseVideoAnalysisToolkit, AbstractToolkit):
cookies_path,
timeout,
)
@listen_toolkit(
BaseVideoAnalysisToolkit.ask_question_about_video,
lambda _, video_path, question: f"transcribe video from {video_path} and ask question: {question}",
)
def ask_question_about_video(self, video_path: str, question: str) -> str:
return super().ask_question_about_video(video_path, question)

View file

@ -5,10 +5,11 @@ from camel.toolkits import VideoDownloaderToolkit as BaseVideoDownloaderToolkit
from app.component.environment import env
from app.service.task import Agents
from app.utils.listen.toolkit_listen import listen_toolkit
from app.utils.listen.toolkit_listen import auto_listen_toolkit
from app.utils.toolkit.abstract_toolkit import AbstractToolkit
@auto_listen_toolkit(BaseVideoDownloaderToolkit)
class VideoDownloaderToolkit(BaseVideoDownloaderToolkit, AbstractToolkit):
agent_name: str = Agents.multi_modal_agent
@ -23,23 +24,3 @@ class VideoDownloaderToolkit(BaseVideoDownloaderToolkit, AbstractToolkit):
working_directory = env("file_save_path", os.path.expanduser("~/Downloads"))
super().__init__(working_directory, cookies_path, timeout)
self.api_task_id = api_task_id
@listen_toolkit(BaseVideoDownloaderToolkit.download_video)
def download_video(self, url: str) -> str:
return super().download_video(url)
@listen_toolkit(
BaseVideoDownloaderToolkit.get_video_bytes,
lambda _, video_path: f"get video bytes from {video_path}",
lambda _: "get video bytes",
)
def get_video_bytes(self, video_path: str) -> bytes:
return super().get_video_bytes(video_path)
@listen_toolkit(
BaseVideoDownloaderToolkit.get_video_screenshots,
lambda _, video_path, amount: f"get video screenshots from {video_path}, amount: {amount}",
lambda results: f"get video screenshots {len(results)}",
)
def get_video_screenshots(self, video_path: str, amount: int) -> List[Image]:
return super().get_video_screenshots(video_path, amount)

View file

@ -3,10 +3,11 @@ from typing import Any, Dict
from camel.toolkits import WebDeployToolkit as BaseWebDeployToolkit
from app.service.task import Agents
from app.utils.listen.toolkit_listen import listen_toolkit
from app.utils.listen.toolkit_listen import auto_listen_toolkit, listen_toolkit
from app.utils.toolkit.abstract_toolkit import AbstractToolkit
@auto_listen_toolkit(BaseWebDeployToolkit)
class WebDeployToolkit(BaseWebDeployToolkit, AbstractToolkit):
agent_name: str = Agents.developer_agent
@ -43,11 +44,3 @@ class WebDeployToolkit(BaseWebDeployToolkit, AbstractToolkit):
) -> Dict[str, Any]:
subdirectory = str(uuid.uuid4())
return super().deploy_folder(folder_path, port, domain, subdirectory)
@listen_toolkit(BaseWebDeployToolkit.stop_server)
def stop_server(self, port: int) -> Dict[str, Any]:
return super().stop_server(port)
@listen_toolkit(BaseWebDeployToolkit.list_running_servers)
def list_running_servers(self) -> Dict[str, Any]:
return super().list_running_servers()

View file

@ -3,10 +3,11 @@ from camel.toolkits import WhatsAppToolkit as BaseWhatsAppToolkit
from camel.toolkits.function_tool import FunctionTool
from app.component.environment import env
from app.service.task import Agents
from app.utils.listen.toolkit_listen import listen_toolkit
from app.utils.listen.toolkit_listen import auto_listen_toolkit
from app.utils.toolkit.abstract_toolkit import AbstractToolkit
@auto_listen_toolkit(BaseWhatsAppToolkit)
class WhatsAppToolkit(BaseWhatsAppToolkit, AbstractToolkit):
agent_name: str = Agents.social_medium_agent
@ -14,30 +15,6 @@ class WhatsAppToolkit(BaseWhatsAppToolkit, AbstractToolkit):
super().__init__(timeout)
self.api_task_id = api_task_id
@listen_toolkit(
BaseWhatsAppToolkit.send_message,
lambda _, to, message: f"send message to {to}: {message}",
lambda result: f"message sent result: {result}",
)
def send_message(self, to: str, message: str) -> Dict[str, Any] | str:
return super().send_message(to, message)
@listen_toolkit(
BaseWhatsAppToolkit.get_message_templates,
lambda _: "get message templates",
lambda result: f"message templates: {result}",
)
def get_message_templates(self) -> List[Dict[str, Any]] | str:
return super().get_message_templates()
@listen_toolkit(
BaseWhatsAppToolkit.get_business_profile,
lambda _: "get business profile",
lambda result: f"business profile: {result}",
)
def get_business_profile(self) -> Dict[str, Any] | str:
return super().get_business_profile()
@classmethod
def get_can_use_tools(cls, api_task_id: str) -> list[FunctionTool]:
if env("WHATSAPP_ACCESS_TOKEN") and env("WHATSAPP_PHONE_NUMBER_ID"):

View file

@ -609,6 +609,13 @@ function registerIpcHandlers() {
return { success: false, error: 'File does not exist' };
}
// Check if it's a directory
const stats = await fsp.stat(filePath);
if (stats.isDirectory()) {
log.error('Path is a directory, not a file:', filePath);
return { success: false, error: 'EISDIR: illegal operation on a directory, read' };
}
// Read file content
const fileContent = await fsp.readFile(filePath);
log.info('File read successfully:', filePath);

View file

@ -727,7 +727,7 @@ const chatStore = create<ChatStore>()(
if (taskIndex !== -1) {
const { toolkit_name, method_name } = agentMessages.data;
if (toolkit_name && method_name) {
if (toolkit_name && method_name && assigneeAgentIndex !== -1) {
const task = taskAssigning[assigneeAgentIndex].tasks.find((task: TaskInfo) => task.id === agentMessages.data.process_task_id);
const message = filterMessage(agentMessages)
@ -739,7 +739,7 @@ const chatStore = create<ChatStore>()(
message: message.data.message as string,
toolkitStatus: "running" as AgentStatus,
}
if (assigneeAgentIndex !== -1 && task) {
if (task) {
task.toolkits ??= []
task.toolkits.push({ ...toolkit });
task.status = "running";
@ -887,9 +887,12 @@ const chatStore = create<ChatStore>()(
taskId as string
);
if (!type && import.meta.env.VITE_USE_LOCAL_PROXY !== 'true' && res.length > 0) {
// Filter out directories, only upload actual files
const files = res.filter((file: any) => !file.isFolder);
// Upload files sequentially to avoid overwhelming the server
const uploadResults = await Promise.allSettled(
res.map(async (file: any) => {
files.map(async (file: any) => {
try {
// Read file content using Electron API
const result = await window.ipcRenderer.invoke('read-file', file.path);