From 1f0c5de27f5d2c3fa61d7c8352361543861c70d0 Mon Sep 17 00:00:00 2001 From: ZiTao-Li <135263265+ZiTao-Li@users.noreply.github.com> Date: Thu, 6 Nov 2025 23:42:52 -0800 Subject: [PATCH] Fix alias pre-commit errors (#28) --- .pre-commit-config.yaml | 1 + alias/src/alias/__init__.py | 6 + alias/src/alias/agent/__init__.py | 10 + .../alias/agent/agents/_alias_agent_base.py | 23 +- .../agent/agents/_deep_research_agent.py | 239 +++++++++--------- .../prompt_decompose_subtask.md | 4 +- .../prompt_deeper_expansion.md | 4 +- .../prompt_deepresearch_summary_report.md | 12 +- .../prompt_inprocess_report.md | 2 +- .../built_in_prompt/prompt_reflect_failure.md | 8 +- .../prompt_worker_additional_sys_prompt.md | 8 +- .../built_in_prompt/promptmodule.py | 86 ++++--- .../agent/agents/_dragent_utils/utils.py | 11 +- alias/src/alias/agent/agents/_meta_planner.py | 14 +- .../_planning_tools/_roadmap_manager.py | 2 +- .../agents/_planning_tools/_worker_manager.py | 4 +- alias/src/alias/agent/mock/__init__.py | 2 +- .../alias/agent/mock/mock_message_models.py | 11 +- .../alias/agent/mock/mock_session_service.py | 1 - alias/src/alias/agent/run.py | 40 +-- alias/src/alias/agent/tools/alias_toolkit.py | 37 +-- .../agent/tools/improved_tools/__init__.py | 4 +- .../tools/improved_tools/file_operations.py | 90 ++++--- .../improved_tools/multimodal_to_text.py | 7 +- alias/src/alias/agent/tools/sandbox_util.py | 21 +- .../agent/tools/toolkit_hooks/__init__.py | 3 +- .../toolkit_hooks/long_text_post_hook.py | 56 ++-- .../toolkit_hooks/read_file_post_hook.py | 5 +- alias/src/alias/cli.py | 96 ++++--- alias/src/alias/runtime/__init__.py | 6 +- .../alias/runtime/alias_sandbox/__init__.py | 3 +- 31 files changed, 430 insertions(+), 386 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index dfd45a5..ea2c6e1 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -66,6 +66,7 @@ repos: | \.html$ ) args: [ + "--init-hook=import sys; sys.path.insert(0, 'alias/src')", --disable=W0511, --disable=W0718, --disable=W0122, diff --git a/alias/src/alias/__init__.py b/alias/src/alias/__init__.py index fb96762..fa90e77 100644 --- a/alias/src/alias/__init__.py +++ b/alias/src/alias/__init__.py @@ -3,3 +3,9 @@ __version__ = "0.0.1" +__all__ = ["agent", "runtime", "__version__"] + +# Import submodules to make them accessible via alias.agent, alias.runtime +# Import at the end to avoid circular import issues +from . import agent # noqa: E402, F401 +from . import runtime # noqa: E402, F401 diff --git a/alias/src/alias/agent/__init__.py b/alias/src/alias/agent/__init__.py index e69de29..ae9466d 100644 --- a/alias/src/alias/agent/__init__.py +++ b/alias/src/alias/agent/__init__.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +"""Agent module for Alias""" + +__all__ = ["agents", "tools", "mock", "utils"] + +# Import submodules to make them accessible via alias.agent.agents, etc. +from . import agents # noqa: E402, F401 +from . import tools # noqa: E402, F401 +from . import mock # noqa: E402, F401 +from . import utils # noqa: E402, F401 diff --git a/alias/src/alias/agent/agents/_alias_agent_base.py b/alias/src/alias/agent/agents/_alias_agent_base.py index 26f479c..48ae7ae 100644 --- a/alias/src/alias/agent/agents/_alias_agent_base.py +++ b/alias/src/alias/agent/agents/_alias_agent_base.py @@ -1,11 +1,12 @@ # -*- coding: utf-8 -*- -from typing import Optional, Any, Type, Callable import asyncio -import time -from pydantic import BaseModel -from loguru import logger -import traceback import json +import time +import traceback +from typing import Any, Optional, Type + +from loguru import logger +from pydantic import BaseModel from agentscope.agent import ReActAgent from agentscope.model import ChatModelBase @@ -49,6 +50,7 @@ class AliasAgentBase(ReActAgent): async def _reasoning(self): """Override _reasoning to add retry logic.""" + # Call the parent class's _reasoning method directly to # avoid double hook execution # We need to call the underlying implementation without hooks @@ -57,10 +59,10 @@ class AliasAgentBase(ReActAgent): # metaclass processing # Access the method from the class that defines it # (before metaclass wrapping) - original_method = ReActAgent.__dict__['_reasoning'] + original_method = ReActAgent.__dict__["_reasoning"] # Check if this is the wrapped version by looking for # the wrapper attributes - if hasattr(original_method, '__wrapped__'): + if hasattr(original_method, "__wrapped__"): # This is the wrapped version, get the original original_method = original_method.__wrapped__ return await original_method(self) @@ -68,17 +70,17 @@ class AliasAgentBase(ReActAgent): for i in range(MODEL_MAX_RETRIES - 1): try: return await call_parent_reasoning() - except Exception as e: + except Exception: logger.warning( f"Reasoning fail at attempt {i + 1}. " f"Max attempts {MODEL_MAX_RETRIES}\n" - f"{traceback.format_exc()}" + f"{traceback.format_exc()}", ) memory_msgs = await self.memory.get_memory() mem_len = len(memory_msgs) # ensure the last message has no tool_use before next attempt if mem_len > 0 and memory_msgs[-1].has_content_blocks( - "tool_use" + "tool_use", ): await self.memory.delete(index=mem_len - 1) time.sleep(2) @@ -241,7 +243,6 @@ class AliasAgentBase(ReActAgent): # Skip non-serializable values pass - # Skip the printing of the finish function call if ( tool_call["name"] != self.finish_function_name diff --git a/alias/src/alias/agent/agents/_deep_research_agent.py b/alias/src/alias/agent/agents/_deep_research_agent.py index e93b8c4..ad31cfb 100644 --- a/alias/src/alias/agent/agents/_deep_research_agent.py +++ b/alias/src/alias/agent/agents/_deep_research_agent.py @@ -1,51 +1,40 @@ # -*- coding: utf-8 -*- """Deep Research Agent""" # pylint: disable=too-many-lines, no-name-in-module -import os import json +import os import traceback import uuid - -from typing import Type, Optional, Any, Tuple from datetime import datetime +from typing import Any, Optional, Tuple, Type + # from copy import deepcopy import shortuuid from pydantic import BaseModel +from agentscope import logger +from agentscope.formatter import FormatterBase +from agentscope.memory import MemoryBase +from agentscope.message import Msg, TextBlock, ToolResultBlock, ToolUseBlock +from agentscope.model import ChatModelBase +from agentscope.tool import ToolResponse + from alias.agent.agents import AliasAgentBase -from alias.agent.tools import AliasToolkit +from alias.agent.agents._dragent_utils.built_in_prompt.promptmodule import ( + FollowupJudge, + ReflectFailure, + SubtasksDecomposition, + WebExtraction, +) +from alias.agent.agents._dragent_utils.utils import ( + get_dynamic_tool_call_json, + get_structure_output, + load_prompt_dict, +) from alias.agent.agents._planning_tools._planning_notebook import ( WorkerResponse, ) - -from alias.agent.agents._dragent_utils.built_in_prompt.promptmodule import ( - SubtasksDecomposition, - WebExtraction, - FollowupJudge, - ReflectFailure, -) -from alias.agent.agents._dragent_utils.utils import ( - load_prompt_dict, - get_dynamic_tool_call_json, - get_structure_output, -) - -from agentscope import logger, setup_logger -# from agentscope.mcp import StatefulClientBase -# from agentscope.agent import ReActAgent -from agentscope.model import ChatModelBase -from agentscope.formatter import FormatterBase -from agentscope.memory import MemoryBase -from agentscope.tool import ( - ToolResponse, - Toolkit, -) -from agentscope.message import ( - Msg, - ToolUseBlock, - TextBlock, - ToolResultBlock, -) +from alias.agent.tools import AliasToolkit _DEEP_RESEARCH_AGENT_DEFAULT_SYS_PROMPT = "You're a helpful assistant." @@ -61,7 +50,7 @@ class SubTaskItem(BaseModel): async def deep_research_pre_reply_hook( self: "DeepResearchAgent", - kwargs: dict[str, Any], # pylint: disable=W0613 + kwargs: dict[str, Any], ): # Maintain the subtask list msg: Msg = kwargs.get("msg") @@ -82,29 +71,40 @@ async def deep_research_pre_reply_hook( async def deep_research_post_reply_hook( self: "DeepResearchAgent", - kwargs: Any, - output: Any, + kwargs: Any, # pylint: disable=W0613 + output: Any, # pylint: disable=W0613 ): self.current_subtask = [] + def _dump_json( save_info: list[Msg] | dict, - dir: str = "./dr_execution_trac" + directory: str = "./dr_execution_trac", ): - if not os.path.isdir(dir): - os.makedirs(dir, exist_ok=True) - if isinstance(save_info, list) and len(save_info) > 0 and isinstance(save_info[0], Msg): + if not os.path.isdir(directory): + os.makedirs(directory, exist_ok=True) + if ( + isinstance(save_info, list) + and len(save_info) > 0 + and isinstance(save_info[0], Msg) + ): save_info = [msg.to_dict() for msg in save_info] - file_path = os.path.join(dir, "memory-" + str(uuid.uuid4().hex) + ".json") + file_path = os.path.join( + directory, + "memory-" + str(uuid.uuid4().hex) + ".json", + ) else: - file_path = os.path.join(dir, "plane-" + str(uuid.uuid4().hex) + ".json") - with open(file_path, "w") as f: + file_path = os.path.join( + directory, + "plane-" + str(uuid.uuid4().hex) + ".json", + ) + with open(file_path, "w", encoding="utf-8") as f: json.dump(save_info, f, ensure_ascii=False, indent=4) async def deep_research_pre_reasoning_hook( self: "DeepResearchAgent", - kwargs: Any, + kwargs: Any, # pylint: disable=W0613 ): memory = await self.memory.get_memory() _dump_json(memory) @@ -117,15 +117,17 @@ async def deep_research_pre_reasoning_hook( ] research_results = [] for tool_call in self.search_call_buffer: - msg = await self._get_research_result(tool_call.get("id")) + msg = await self._get_research_result( # pylint: disable=W0212 + tool_call.get("id"), + ) if msg is not None: research_results.append( json.dumps( msg.get_content_blocks("tool_result"), ensure_ascii=False, - ) + ), ) - await self._follow_up( + await self._follow_up( # pylint: disable=W0212 search_results="\n".join(research_results), search_queries="\n".join(search_queries), ) @@ -142,9 +144,7 @@ async def deep_research_pre_reasoning_hook( reasoning_prompt = self.prompt_dict["reasoning_prompt"].format_map( { "objective": self.current_subtask[-1].objective, - "plan": cur_plan - if cur_plan - else "There is no working plan now.", + "plan": cur_plan if cur_plan else "There is no working plan now.", "knowledge_gap": f"## Knowledge Gaps:\n {cur_know_gap}" if cur_know_gap else "", @@ -166,8 +166,8 @@ async def deep_research_pre_reasoning_hook( async def deep_research_post_reasoning_hook( self: "DeepResearchAgent", # pylint: disable=W0613 - kwargs: Any, - output_msg: Msg, + kwargs: Any, # pylint: disable=W0613 + output_msg: Msg, # pylint: disable=W0613 ): num_msgs = await self.memory.size() if num_msgs > 1: @@ -178,7 +178,7 @@ async def deep_research_post_reasoning_hook( async def deep_research_post_action_hook( self: "DeepResearchAgent", kwargs: Any, - output_msg: Msg, + output_msg: Msg, # pylint: disable=W0613 ): tool_call = kwargs.get("tool_call", {}) if tool_call and tool_call.get("name") == self.search_function: @@ -274,7 +274,7 @@ class DeepResearchAgent(AliasAgentBase): "intermediate_summarize": self.summarize_function, "reflect_failure": "reflect_failure", "subtask_finish": "finish_current_subtask", - "finish_function_name": self.finish_function_name + "finish_function_name": self.finish_function_name, }, ) tool_use_rule = self.prompt_dict["tool_use_rule"].format_map( @@ -311,34 +311,34 @@ class DeepResearchAgent(AliasAgentBase): self.summarize_intermediate_results, ) self.toolkit.register_tool_function( - self.finish_current_subtask + self.finish_current_subtask, ) # add hooks self.register_instance_hook( "pre_reply", "deep_research_pre_reply_hook", - deep_research_pre_reply_hook + deep_research_pre_reply_hook, ) self.register_instance_hook( "post_reply", "deep_research_post_reply_hook", - deep_research_post_reply_hook + deep_research_post_reply_hook, ) self.register_instance_hook( "pre_reasoning", "deep_research_pre_reasoning_hook", - deep_research_pre_reasoning_hook + deep_research_pre_reasoning_hook, ) self.register_instance_hook( "post_reasoning", "deep_research_post_reasoning_hook", - deep_research_post_reasoning_hook + deep_research_post_reasoning_hook, ) self.register_instance_hook( "post_acting", "deep_research_post_action_hook", - deep_research_post_action_hook + deep_research_post_action_hook, ) self.search_call_buffer = [] @@ -471,7 +471,7 @@ class DeepResearchAgent(AliasAgentBase): "Identify the knowledge gaps of the current " "subtask and generate a working plan by subtask " "decomposition", - "assistant" + "assistant", ), ) @@ -548,7 +548,7 @@ class DeepResearchAgent(AliasAgentBase): "(Follow-up by extraction)" "Read the website more intensively to mine more " "information.", - "assistant" + "assistant", ), ) try: @@ -568,9 +568,9 @@ class DeepResearchAgent(AliasAgentBase): text=json.dumps( extraction_check, ensure_ascii=False, - indent=2 - ) - ) + indent=2, + ), + ), ], role="assistant", ) @@ -579,7 +579,7 @@ class DeepResearchAgent(AliasAgentBase): except Exception as e: # noqa: F841 logger.warning( f"Error when checking subtask finish status {e}" - f"{traceback.format_exc()}" + f"{traceback.format_exc()}", ) extraction_check = {} @@ -599,9 +599,9 @@ class DeepResearchAgent(AliasAgentBase): Msg( self.name, [TextBlock(type="text", text=f"Reading {urls}")], - "assistant" + "assistant", ), - last=True + last=True, ) # call the extract_function @@ -629,7 +629,7 @@ class DeepResearchAgent(AliasAgentBase): self.name, "(Follow-up to explore)" "Check if current subtask knowledge gaps are fulfilled", - "assistant" + "assistant", ), ) msgs = [ @@ -646,7 +646,7 @@ class DeepResearchAgent(AliasAgentBase): "user", self.prompt_dict["follow_up_judge_sys_prompt"], role="user", - ) + ), ] follow_up_judge = await self.get_model_output( msgs=msgs, @@ -660,23 +660,26 @@ class DeepResearchAgent(AliasAgentBase): type="text", text=json.dumps( follow_up_judge, - ensure_ascii=False, indent=2 - ) - ) + ensure_ascii=False, + indent=2, + ), + ), ], role="assistant", ) await self.memory.add(follow_up_msg) except Exception as e: # noqa: F841 logger.warning( - f"Error when checking subtask finish status {e}" + f"Error when checking subtask finish status {e}", ) logger.error(traceback.format_exc()) follow_up_judge = {} if follow_up_judge.get("knowledge_gap_revision", ""): - self.current_subtask[-1].knowledge_gaps = \ - follow_up_judge.get("knowledge_gap_revision", "") + self.current_subtask[-1].knowledge_gaps = follow_up_judge.get( + "knowledge_gap_revision", + "", + ) if ( follow_up_judge.get("to_further_explore", False) @@ -690,15 +693,13 @@ class DeepResearchAgent(AliasAgentBase): TextBlock( type="text", text="Still need to do more research " - f"to figure out {subtask}", - ) + f"to figure out {subtask}", + ), ], - role="assistant" - ) - ) - intermediate_report = ( - await self.summarize_intermediate_results() + role="assistant", + ), ) + intermediate_report = await self.summarize_intermediate_results() self.current_subtask.append( SubTaskItem(objective=subtask), ) @@ -748,14 +749,12 @@ class DeepResearchAgent(AliasAgentBase): for msg in reversed(memory_msgs): if msg.metadata and msg.metadata.get("is_report_msg"): break - else: - intermediate_memory.append(msg) + intermediate_memory.append(msg) intermediate_memory.reverse() if remove_last_tool_use: - while ( - len(intermediate_memory) > 0 and - intermediate_memory[-1].has_content_blocks("tool_use") - ): + while len(intermediate_memory) > 0 and intermediate_memory[ + -1 + ].has_content_blocks("tool_use"): intermediate_memory.pop(-1) return intermediate_memory @@ -765,34 +764,33 @@ class DeepResearchAgent(AliasAgentBase): for msg in reversed(memory_msgs): if msg.metadata and msg.metadata.get("is_report_msg"): break - elif msg.role == "user": + if msg.role == "user": break - elif msg.has_content_blocks("tool_use"): + if msg.has_content_blocks("tool_use"): stop = False for block in msg.get_content_blocks("tool_use"): if block.get("name") == self.summarize_function: stop = True if stop: break - else: - remove_num += 1 + remove_num += 1 else: remove_num += 1 start_index = len(memory_msgs) - remove_num logger.info( "---> delete messages: " - f"{list(range(start_index, len(memory_msgs)))}" + f"{list(range(start_index, len(memory_msgs)))}", ) await self.memory.delete(list(range(start_index, len(memory_msgs)))) async def _get_research_result( self, - tool_call_id: str + tool_call_id: str, ) -> Msg | None: memory_msgs = await self.memory.get_memory() for msg in reversed(memory_msgs): if msg.has_content_blocks("tool_result"): - for block in msg.get_content_blocks('tool_result'): + for block in msg.get_content_blocks("tool_result"): if block.get("id") == tool_call_id: return msg return None @@ -823,7 +821,7 @@ class DeepResearchAgent(AliasAgentBase): "[summarize_intermediate_results]" "Examine whether the knowledge gaps or objective" "have been fulfill", - "assistant" + "assistant", ), ) @@ -883,7 +881,7 @@ class DeepResearchAgent(AliasAgentBase): Msg( self.name, "Summarize the intermediate results into a report", - "assistant" + "assistant", ), ) @@ -934,7 +932,7 @@ class DeepResearchAgent(AliasAgentBase): ), ), ], - metadata={"is_report_msg": True,} + metadata={"is_report_msg": True}, ) else: # add to memory for the follow-up case @@ -948,7 +946,7 @@ class DeepResearchAgent(AliasAgentBase): ), ], role="assistant", - metadata={"is_report_msg": True} + metadata={"is_report_msg": True}, ), ) return ToolResponse( @@ -989,7 +987,7 @@ class DeepResearchAgent(AliasAgentBase): tmp_report_path = os.path.join( self.tmp_file_storage_dir, f"{self.report_path_based}_" - f"inprocess_report_{index + 1}.md" + f"inprocess_report_{index + 1}.md", ) params = { "file_path": tmp_report_path, @@ -1010,11 +1008,11 @@ class DeepResearchAgent(AliasAgentBase): TextBlock( type="text", text="Reading progress report: " - f"{tmp_report_path}" - ) + f"{tmp_report_path}", + ), ], - "assistant" - ) + "assistant", + ), ) msgs = [ @@ -1031,7 +1029,7 @@ class DeepResearchAgent(AliasAgentBase): ] else: # Use only intermediate memory to generate report intermediate_memory = await self._get_intermediate_memory( - remove_last_tool_use=True + remove_last_tool_use=True, ) msgs = [ Msg( @@ -1045,7 +1043,7 @@ class DeepResearchAgent(AliasAgentBase): Msg( self.name, "Collect and polish all draft reports into a final report", - "assistant" + "assistant", ), ) try: @@ -1104,8 +1102,10 @@ class DeepResearchAgent(AliasAgentBase): name=self.name, role="assistant", content=[ - TextBlock(type="text", - text=subtask_progress_summary, ) + TextBlock( + type="text", + text=subtask_progress_summary, + ), ], metadata=structure_response.model_dump(), ) @@ -1122,7 +1122,7 @@ class DeepResearchAgent(AliasAgentBase): The reflection about plan rephrasing and subtask decomposition. """ intermediate_memory = await self._get_intermediate_memory( - remove_last_tool_use=True + remove_last_tool_use=True, ) reflect_sys_prompt = self.prompt_dict["reflect_sys_prompt"] conversation_history = "" @@ -1148,7 +1148,7 @@ class DeepResearchAgent(AliasAgentBase): Msg( self.name, "Reflect on the failure of the action", - "assistant" + "assistant", ), ) reflection = await self.get_model_output( @@ -1193,7 +1193,7 @@ class DeepResearchAgent(AliasAgentBase): save_msg = None for msg in reversed(msgs): for i, block in enumerate( - msg.get_content_blocks("tool_use") + msg.get_content_blocks("tool_use"), ): if block.get("name") == "reflect_failure": save_msg = msg @@ -1286,8 +1286,8 @@ class DeepResearchAgent(AliasAgentBase): TextBlock( type="text", text="All subtasks are done. " - "Consider using generate_response to" - "generate final report", + "Consider using generate_response to" + "generate final report", ), ], metadata={ @@ -1296,7 +1296,6 @@ class DeepResearchAgent(AliasAgentBase): is_last=True, ) - # pylint: disable=invalid-overridden-method, unused-argument async def generate_response( # self, @@ -1333,15 +1332,17 @@ class DeepResearchAgent(AliasAgentBase): detailed_report_path: ( f"Final detailed report generated by {self.name}" f"for '{str(self.user_query)}'" - ) + ), }, ) response_msg = Msg( name=self.name, role="assistant", content=[ - TextBlock(type="text", - text=subtask_progress_summary,) + TextBlock( + type="text", + text=subtask_progress_summary, + ), ], metadata=structure_response.model_dump(), ) @@ -1377,4 +1378,4 @@ class DeepResearchAgent(AliasAgentBase): "success": False, # do not allow to exit }, is_last=True, - ) \ No newline at end of file + ) diff --git a/alias/src/alias/agent/agents/_dragent_utils/built_in_prompt/prompt_decompose_subtask.md b/alias/src/alias/agent/agents/_dragent_utils/built_in_prompt/prompt_decompose_subtask.md index 7552c51..76458ea 100644 --- a/alias/src/alias/agent/agents/_dragent_utils/built_in_prompt/prompt_decompose_subtask.md +++ b/alias/src/alias/agent/agents/_dragent_utils/built_in_prompt/prompt_decompose_subtask.md @@ -29,7 +29,7 @@ The successful research plan must meet these standards: 2. **Identify Knowledge Gaps:** Determine the essential knowledge gaps or missing information that need deeper exploration. Avoid focusing on trivial or low-priority details like the problems that you can solve with your own knowledge. Instead, concentrate on: - Foundational gaps critical to task completion - Identifying opportunities for step expansion by considering alternative approaches, connections to related topics, or ways to enrich the final output. Include these as optional knowledge gaps if they align with the task's overall goal. - The knowledge gaps should strictly be in the format of a markdown checklist and flag gaps requiring perspective expansion with `(EXPANSION)` tag (e.g., "- [ ] (EXPANSION) Analysis report of X"). + The knowledge gaps should strictly be in the format of a markdown checklist and flag gaps requiring perspective expansion with `(EXPANSION)` tag (e.g., "- [ ] (EXPANSION) Analysis report of X"). 3. **Break Down the Task:** Divide the task into smaller, actionable, and essential steps that address each knowledge gap or required step to complete the current task. Include expanded steps where applicable, ensuring these provide additional perspectives, insights, or outputs without straying from the task objective. These expanded steps should enhance the richness of the final output. 4. **Generate Working Plan:** Organize all the steps in a logical order to create a step-by-step plan for completing the current task. @@ -40,7 +40,7 @@ When generating extension steps, you can refer to the following perspectives tha - Timeline Researcher: Examine how the subject has evolved over time, previous iterations, and historical context. Think systemically about long-term impacts, scalability, and paradigm shifts in the future. - Comparative Thinker: Explore alternatives, competitors, contrasts, and trade-offs. Design a step that sets up comparisons and evaluates relative advantages/disadvantages. - Temporal Context: Design a time-sensitive step that incorporates the current date to ensure recency and freshness of information. -- Public Opinion Collector: Design a step to aggregate user-generated content like text posts or comments, digital photos or videos from Twitter, Youtube, Facebook and other social media. +- Public Opinion Collector: Design a step to aggregate user-generated content like text posts or comments, digital photos or videos from Twitter, Youtube, Facebook and other social media. - Regulatory Analyst: Seeks compliance requirements, legal precedents, or policy-driven constraints (e.g. "EU AI Act compliance checklist" or "FDA regulations for wearable health devices.") - Academic Professor: Design a step based on the necessary steps of doing an academic research (e.g. "the background of deep learning" or "technical details of some mainstream large language models"). diff --git a/alias/src/alias/agent/agents/_dragent_utils/built_in_prompt/prompt_deeper_expansion.md b/alias/src/alias/agent/agents/_dragent_utils/built_in_prompt/prompt_deeper_expansion.md index ac2a2a7..9d5e20b 100644 --- a/alias/src/alias/agent/agents/_dragent_utils/built_in_prompt/prompt_deeper_expansion.md +++ b/alias/src/alias/agent/agents/_dragent_utils/built_in_prompt/prompt_deeper_expansion.md @@ -2,10 +2,10 @@ You are a sharp-eyed Knowledge Discoverer, capable of identifying and leveraging any potentially useful piece of information gathered from web search, no matter how brief. And the information will later be deeper extracted for more contents. ## Instructions -1. **Find information with valuable, but insufficient or shallow content**: Carefully review the web search results to assess whether there is any snippet or web content that +1. **Find information with valuable, but insufficient or shallow content**: Carefully review the web search results to assess whether there is any snippet or web content that - could potentially help address the given query as the content increases - **but whose content is limited or only briefly mentioned**! -2. **Identify the snippet**: If such information is found, you are encouraged to set `need_extraction` to true, and locate the specific **url** of the information snippet you have found for later extraction. +2. **Identify the snippet**: If such information is found, you are encouraged to set `need_extraction` to true, and locate the specific **url** of the information snippet you have found for later extraction. 3. **Reduce unnecessary extraction**: If all snippets are only generally related, or unlikely to address the query, or their contents are rich and sufficient enough, or incomplete but not essential, set `need_extraction` to false. ## Important Notes diff --git a/alias/src/alias/agent/agents/_dragent_utils/built_in_prompt/prompt_deepresearch_summary_report.md b/alias/src/alias/agent/agents/_dragent_utils/built_in_prompt/prompt_deepresearch_summary_report.md index f73dcaa..efe3b69 100644 --- a/alias/src/alias/agent/agents/_dragent_utils/built_in_prompt/prompt_deepresearch_summary_report.md +++ b/alias/src/alias/agent/agents/_dragent_utils/built_in_prompt/prompt_deepresearch_summary_report.md @@ -34,19 +34,19 @@ Please revise the provided draft research report into a finalized, professional, - The tone must be formal, objective, and professional throughout. - Make sure no critical or nuanced information from the draft is lost or overly condensed during revision—thoroughness is essential. - Check that all cited sources are accurately referenced. -- Each section, subsection, and even bullet point MUST contain enough depth, relevant details, and specific information rather than being a brief summary of only a few sentences. +- Each section, subsection, and even bullet point MUST contain enough depth, relevant details, and specific information rather than being a brief summary of only a few sentences. ### Report Format (Fill in appropriate content in [] and ... parts): [Your Report Title] -# Introduction: +# Introduction: [Introduction to the report] -# [Section 1 title]: +# [Section 1 title]: [Section 1 content] -## [Subsection 1.1 title]: +## [Subsection 1.1 title]: [Subsection 1.1 content] -# [Section 2 title]: +# [Section 2 title]: ... -# Conclusion: +# Conclusion: [Conclusion to the report] Format your report professionally with consistent heading levels and proper spacing. diff --git a/alias/src/alias/agent/agents/_dragent_utils/built_in_prompt/prompt_inprocess_report.md b/alias/src/alias/agent/agents/_dragent_utils/built_in_prompt/prompt_inprocess_report.md index f7e90a0..d624485 100644 --- a/alias/src/alias/agent/agents/_dragent_utils/built_in_prompt/prompt_inprocess_report.md +++ b/alias/src/alias/agent/agents/_dragent_utils/built_in_prompt/prompt_inprocess_report.md @@ -17,5 +17,5 @@ You are a professional researcher expert in writing comprehensive reports from y ## Important Notes 1. Avoid combining, excessively paraphrasing, omitting, or condensing any individual snippet that provides unique or relevant details. The final report must cover ALL key information as presented in the original results. -2. Each bullet point should be sufficiently detailed (at least **2000 chars**) +2. Each bullet point should be sufficiently detailed (at least **2000 chars**) 3. Both items with and without `(EXPANSION)` tag in knowledge gaps list are important and useful for task completion. \ No newline at end of file diff --git a/alias/src/alias/agent/agents/_dragent_utils/built_in_prompt/prompt_reflect_failure.md b/alias/src/alias/agent/agents/_dragent_utils/built_in_prompt/prompt_reflect_failure.md index 2d11cde..a2eadce 100644 --- a/alias/src/alias/agent/agents/_dragent_utils/built_in_prompt/prompt_reflect_failure.md +++ b/alias/src/alias/agent/agents/_dragent_utils/built_in_prompt/prompt_reflect_failure.md @@ -2,7 +2,7 @@ Your job is to reflect on your failure based on your work history and generate t ## Instructions 1. Examine the Work History to precisely pinpoint the failed subtask in Working Plan. -2. Review the Current Subtask and Task Final Objective provided in Work History. Carefully analyze whether this subtask was designed incorrectly due to a misunderstanding of the task. If so, +2. Review the Current Subtask and Task Final Objective provided in Work History. Carefully analyze whether this subtask was designed incorrectly due to a misunderstanding of the task. If so, * set `need_rephrase` in `rephrase_subtask` to true * Only replace the inappropriate subtask with the modified subtask, while keeping the rest of the Working Plan unchanged. You should output the updated Working Plan in `rephrased_plan`. * If the subtask was not poorly designed, proceed to Step 3. @@ -15,10 +15,10 @@ Your job is to reflect on your failure based on your work history and generate t 2. Set `need_decompose` and `need_rephrase` to false simultaneously when you find that you are getting stuck in a repetitive failure pattern. ## Example -Work History: +Work History: 1. Reflect on the failure of this subtask and identify the failed subtask "Convert the extracted geographic coordinates or landmarks into corresponding five-digit zip codes by mapping tools or geo-mapping APIs". -2. Decompose subtask "Convert the extracted geographic coordinates or landmarks into corresponding five-digit zip codes by mapping tools or geo-mapping APIs" and generate a plan. -Working Plan: +2. Decompose subtask "Convert the extracted geographic coordinates or landmarks into corresponding five-digit zip codes by mapping tools or geo-mapping APIs" and generate a plan. +Working Plan: 1. Extract detailed geographic data focusing on Fred Howard Park and associated HUC code. 2. Use mapping tools or geo-mapping APIs (e.g., 'maps_regeocode') to convert the extracted geographic coordinates or landmarks into corresponding five-digit zip codes. 3. Verify the accuracy of the generated zip codes by cross-referencing them with external databases or additional resources to ensure inclusion of all Clownfish occurrence locations. diff --git a/alias/src/alias/agent/agents/_dragent_utils/built_in_prompt/prompt_worker_additional_sys_prompt.md b/alias/src/alias/agent/agents/_dragent_utils/built_in_prompt/prompt_worker_additional_sys_prompt.md index 816d2b7..82c3812 100644 --- a/alias/src/alias/agent/agents/_dragent_utils/built_in_prompt/prompt_worker_additional_sys_prompt.md +++ b/alias/src/alias/agent/agents/_dragent_utils/built_in_prompt/prompt_worker_additional_sys_prompt.md @@ -53,14 +53,14 @@ ### Important Constraints 1. DO NOT TRY TO MAKE A PLAN yourself. 2. ALWAYS FOLLOW THE WORKING PLAN SEQUENCE STEP BY STEP!! -3. For each step, you MUST provide a reason or analysis to **review what was done in the previous step** and **explain why to call a function / use a tool in this step**. -4. After each action, YOU MUST seriously confirm that the current item in the plan is done before starting the next item, referring to the following rules: - - Carefully analyze whether the information obtained from the tool is sufficient to fill the knowledge gap corresponding to the current item. +3. For each step, you MUST provide a reason or analysis to **review what was done in the previous step** and **explain why to call a function / use a tool in this step**. +4. After each action, YOU MUST seriously confirm that the current item in the plan is done before starting the next item, referring to the following rules: + - Carefully analyze whether the information obtained from the tool is sufficient to fill the knowledge gap corresponding to the current item. - Pay more attention to details. Confidently assuming that all tool calls will bring complete information often leads to serious errors (e.g., mistaking the rental website name for the apartment name when renting). If the current item in the plan is done, call `summarize_inprocess_results_into_report` to generate an in-process report, then move on to the next item. 5. Always pay attention to the current subtask and working plan as they may be updated during the workflow. 6. Each time you reason and act, remember that **Current Subtask** is your primary goal, while **Final Task Objective** constrains your process from deviating from the final goal. -7. You should use `{subtask_finish}` to mark that you have finished a subtask and proceed to the next one. +7. You should use `{subtask_finish}` to mark that you have finished a subtask and proceed to the next one. 8. You should use the `{finish_function_name}` tool to return your research results when Research Depth = 1 and all checklist items are completed. diff --git a/alias/src/alias/agent/agents/_dragent_utils/built_in_prompt/promptmodule.py b/alias/src/alias/agent/agents/_dragent_utils/built_in_prompt/promptmodule.py index 3790f07..5f1c4b5 100644 --- a/alias/src/alias/agent/agents/_dragent_utils/built_in_prompt/promptmodule.py +++ b/alias/src/alias/agent/agents/_dragent_utils/built_in_prompt/promptmodule.py @@ -1,5 +1,7 @@ +# -*- coding: utf-8 -*- from pydantic import BaseModel, Field + class SubtasksDecomposition(BaseModel): """ Model for structured subtask decomposition output in deep research. @@ -7,20 +9,23 @@ class SubtasksDecomposition(BaseModel): knowledge_gaps: str = Field( description=( - "A markdown checklist of essential knowledge gaps and optional " - "perspective-expansion gaps (flagged with (EXPANSION)), each on its own line. " - "E.g. '- [ ] Detailed analysis of JD.com's ...\\n- [ ] (EXPANSION) X...'." + "A markdown checklist of essential knowledge gaps and " + "optional perspective-expansion gaps (flagged with " + "(EXPANSION)), each on its own line. E.g. '- [ ] Detailed " + "analysis of JD.com's ...\\n- [ ] (EXPANSION) X...'." ), ) working_plan: str = Field( description=( - "A logically ordered step-by-step working plan (3-5 steps), " - "each step starting with its number (1., 2., etc), including both " - "core and expansion steps. Expanded steps should be clearly marked " - "with (EXPANSION) and provide contextual or analytical depth.." + "A logically ordered step-by-step working plan (3-5 steps)," + " each step starting with its number (1., 2., etc), " + "including both core and expansion steps. Expanded steps " + "should be clearly marked with (EXPANSION) and provide " + "contextual or analytical depth.." ), ) + class WebExtraction(BaseModel): """ Model for structured follow-up web extraction output in deep research. @@ -42,6 +47,7 @@ class WebExtraction(BaseModel): ), ) + class FollowupJudge(BaseModel): """ Model for structured follow-up decompose judging output in deep research. @@ -49,15 +55,15 @@ class FollowupJudge(BaseModel): reasoning: str = Field( description=( - "The reasoning for your decision, including a summary of evidence " - "and logic for whether more information is needed. You should " - "include specific gaps or opportunities if the current " - "information is still insufficient" + "The reasoning for your decision, including a summary of " + "evidence and logic for whether more information is needed. " + "You should include specific gaps or opportunities if the " + "current information is still insufficient" ), ) knowledge_gap_revision: str = Field( "Revise the knowledge gaps in the current. " - "Mark the gaps with sufficient information as [x]." + "Mark the gaps with sufficient information as [x].", ) to_further_explore: bool = Field( description=( @@ -81,10 +87,11 @@ class ReflectFailure(BaseModel): rephrase_subtask: dict = Field( description=( - "Information about whether the problematic subtask needs to be " - "rephrased due to a design flaw or misunderstanding. If rephrasing " - "is needed, provide the modified working plan with only the inappropriate " - "subtask replaced by its improved version." + "Information about whether the problematic subtask needs to " + "be rephrased due to a design flaw or misunderstanding. If " + "rephrasing is needed, provide the modified working plan with" + " only the inappropriate subtask replaced by its improved " + "version." ), json_schema_extra={ "additionalProperties": { @@ -92,25 +99,30 @@ class ReflectFailure(BaseModel): "properties": { "need_rephrase": { "type": "boolean", - "description": "Set to 'true' if the failed subtask " - "needs to be rephrased due to a design " - "flaw or misunderstanding; otherwise, 'false'.", + "description": ( + "Set to 'true' if the failed subtask needs " + "to be rephrased due to a design flaw or " + "misunderstanding; otherwise, 'false'." + ), }, "rephrased_plan": { "type": "string", - "description": "The modified working plan with only the inappropriate " - "subtask replaced by its improved version. If no " - "rephrasing is needed, provide an empty string.", + "description": ( + "The modified working plan with only the " + "inappropriate subtask replaced by its " + "improved version. If no rephrasing is " + "needed, provide an empty string." + ), }, - } - } - } + }, + }, + }, ) decompose_subtask: dict = Field( description=( - "Information about whether the problematic subtask should be further " - "decomposed. If decomposition is required, provide the failed subtask " - "and the reason for its decomposition." + "Information about whether the problematic subtask should be " + "further decomposed. If decomposition is required, provide " + "the failed subtask and the reason for its decomposition." ), json_schema_extra={ "additionalProperties": { @@ -118,15 +130,19 @@ class ReflectFailure(BaseModel): "properties": { "need_decompose": { "type": "boolean", - "description": "Set to 'true' if the failed subtask should " - "be further decomposed; otherwise, 'false'.", + "description": ( + "Set to 'true' if the failed subtask should " + "be further decomposed; otherwise, 'false'." + ), }, "failed_subtask": { "type": "string", - "description": "The failed subtask that needs to be further " - "decomposed.", + "description": ( + "The failed subtask that needs to be further " + "decomposed." + ), }, - } - } - } + }, + }, + }, ) diff --git a/alias/src/alias/agent/agents/_dragent_utils/utils.py b/alias/src/alias/agent/agents/_dragent_utils/utils.py index a4e9013..e1ac6e5 100644 --- a/alias/src/alias/agent/agents/_dragent_utils/utils.py +++ b/alias/src/alias/agent/agents/_dragent_utils/utils.py @@ -1,13 +1,13 @@ # -*- coding: utf-8 -*- """The utilities for deep research agent""" -import os import json -from typing import Union, Sequence, Any, Type -from pydantic import BaseModel +import os import re +from typing import Any, Sequence, Type, Union + +from pydantic import BaseModel from agentscope.tool import Toolkit, ToolResponse -from agentscope.agent import ReActAgent TOOL_RESULTS_MAX_WORDS = 30000 @@ -24,12 +24,13 @@ def get_prompt_from_file( prompt = f.read() return prompt + async def count_by_words(sentence: str) -> float: """Count words of a sentence""" words = re.findall( r"\w+|[^\w\s]", sentence, - re.UNICODE + re.UNICODE, ) word_count = 0.0 diff --git a/alias/src/alias/agent/agents/_meta_planner.py b/alias/src/alias/agent/agents/_meta_planner.py index 1fffc9c..7831a59 100644 --- a/alias/src/alias/agent/agents/_meta_planner.py +++ b/alias/src/alias/agent/agents/_meta_planner.py @@ -4,19 +4,20 @@ Meta Planner agent class that can handle complicated tasks with planning-execution pattern. """ # pylint: disable=W0613 +import json import os import uuid from functools import partial -from typing import Optional, Any, Literal, Callable -import json from pathlib import Path +from typing import Any, Callable, Literal, Optional + from pydantic import BaseModel, Field -from agentscope import logger -from agentscope.message import Msg, ToolUseBlock, TextBlock, ToolResultBlock -from agentscope.tool import ToolResponse -from agentscope.model import ChatModelBase + from agentscope.formatter import FormatterBase from agentscope.memory import MemoryBase +from agentscope.message import Msg, TextBlock, ToolResultBlock, ToolUseBlock +from agentscope.model import ChatModelBase +from agentscope.tool import ToolResponse from alias.agent.agents import AliasAgentBase from alias.agent.tools import AliasToolkit @@ -306,7 +307,6 @@ class MetaPlanner(AliasAgentBase): generate_response_post_action_hook, ) - def prepare_planner_tools( self, planner_mode: Literal["disable", "enforced", "dynamic"], diff --git a/alias/src/alias/agent/agents/_planning_tools/_roadmap_manager.py b/alias/src/alias/agent/agents/_planning_tools/_roadmap_manager.py index 1ff2a6f..a49a4a8 100644 --- a/alias/src/alias/agent/agents/_planning_tools/_roadmap_manager.py +++ b/alias/src/alias/agent/agents/_planning_tools/_roadmap_manager.py @@ -43,7 +43,7 @@ class RoadmapManager(StateModule): async def decompose_task_and_build_roadmap( self, - user_latest_input: str, + user_latest_input: str, # pylint: disable=W0613 given_task_conclusion: str, detail_analysis_for_plan: str, decomposed_subtasks: list[SubTaskSpecification], diff --git a/alias/src/alias/agent/agents/_planning_tools/_worker_manager.py b/alias/src/alias/agent/agents/_planning_tools/_worker_manager.py index 81bdb8a..ec94b4e 100644 --- a/alias/src/alias/agent/agents/_planning_tools/_worker_manager.py +++ b/alias/src/alias/agent/agents/_planning_tools/_worker_manager.py @@ -514,7 +514,7 @@ class WorkerManager(StateModule): subtask_idx: int, selected_worker_name: str, detailed_instruction: str, - reset_worker_memory: bool = False + reset_worker_memory: bool = False, ) -> ToolResponse: """ Execute a worker agent for the next unfinished subtask. @@ -539,7 +539,7 @@ class WorkerManager(StateModule): memory can also be reset for better performance (but require providing sufficient context information in `detailed_instruction`); 3) if a worker is stopped just because - hitting th maximum round constraint in the previous execution + hitting the maximum round constraint in the previous execution and it's going to work on the sam task, DO NOT reset the memory. diff --git a/alias/src/alias/agent/mock/__init__.py b/alias/src/alias/agent/mock/__init__.py index 37328cd..05d3139 100644 --- a/alias/src/alias/agent/mock/__init__.py +++ b/alias/src/alias/agent/mock/__init__.py @@ -4,7 +4,7 @@ from .mock_message_models import ( BaseMessage, MessageState, MockMessage, - UserMessage + UserMessage, ) __all__ = [ diff --git a/alias/src/alias/agent/mock/mock_message_models.py b/alias/src/alias/agent/mock/mock_message_models.py index 6dc77d4..ac2e2af 100644 --- a/alias/src/alias/agent/mock/mock_message_models.py +++ b/alias/src/alias/agent/mock/mock_message_models.py @@ -1,13 +1,15 @@ # -*- coding: utf-8 -*- """Mock message models for local testing without api_server dependency.""" -from enum import Enum import uuid -from typing import Any, Optional, List +from enum import Enum +from typing import Any, Optional + from pydantic import BaseModel class MessageState(str, Enum): """Message state enumeration.""" + RUNNING = "running" FINISHED = "finished" FAILED = "failed" @@ -15,6 +17,7 @@ class MessageState(str, Enum): class MessageType(str, Enum): """Message type enumeration.""" + RESPONSE = "response" SUB_RESPONSE = "sub_response" THOUGHT = "thought" @@ -27,6 +30,7 @@ class MessageType(str, Enum): class BaseMessage(BaseModel): """Base message class for local testing.""" + role: str = "assistant" content: Any = "" name: Optional[str] = None @@ -36,6 +40,7 @@ class BaseMessage(BaseModel): class UserMessage(BaseMessage): """User message for local testing.""" + role: str = "user" name: str = "User" @@ -43,4 +48,4 @@ class UserMessage(BaseMessage): class MockMessage: id: uuid.UUID = uuid.uuid4() message: Optional[dict] = None - files: list[Any] = [] \ No newline at end of file + files: list[Any] = [] diff --git a/alias/src/alias/agent/mock/mock_session_service.py b/alias/src/alias/agent/mock/mock_session_service.py index d80d6cc..6869bd7 100644 --- a/alias/src/alias/agent/mock/mock_session_service.py +++ b/alias/src/alias/agent/mock/mock_session_service.py @@ -211,4 +211,3 @@ class MockSessionService: async def get_state(self) -> dict: return self.state - diff --git a/alias/src/alias/agent/run.py b/alias/src/alias/agent/run.py index cf36d28..3783503 100644 --- a/alias/src/alias/agent/run.py +++ b/alias/src/alias/agent/run.py @@ -1,41 +1,27 @@ # -*- coding: utf-8 -*- # pylint: disable=W0612,E0611,C2801 import os -from typing import Optional -from datetime import datetime import traceback +from datetime import datetime + from loguru import logger -from agentscope.message import Msg -from agentscope.model import ( - OpenAIChatModel, - AnthropicChatModel, - DashScopeChatModel, -) -from agentscope.formatter import ( - OpenAIChatFormatter, - AnthropicChatFormatter, - DashScopeChatFormatter, -) +from agentscope.formatter import DashScopeChatFormatter +from agentscope.mcp import StdIOStatefulClient from agentscope.memory import InMemoryMemory -from agentscope.mcp import StdIOStatefulClient, StatefulClientBase -from agentscope.token import OpenAITokenCounter +from agentscope.message import Msg +from agentscope.model import DashScopeChatModel from agentscope_runtime.sandbox.box.sandbox import Sandbox -from alias.agent.agents import ( - MetaPlanner, - DeepResearchAgent, - BrowserAgent, -) -from alias.agent.tools import AliasToolkit +from alias.agent.agents import BrowserAgent, DeepResearchAgent, MetaPlanner from alias.agent.agents._planning_tools._worker_manager import share_tools -from alias.agent.utils.constants import BROWSER_AGENT_DESCRIPTION +from alias.agent.mock import MockSessionService +from alias.agent.tools import AliasToolkit from alias.agent.tools.improved_tools import DashScopeMultiModalTools from alias.agent.tools.toolkit_hooks import LongTextPostHook +from alias.agent.utils.constants import BROWSER_AGENT_DESCRIPTION # Open source version always uses mock services -from alias.agent.mock import MockSessionService - SessionService = MockSessionService @@ -126,7 +112,7 @@ async def add_tools( async def arun_agents( - session_service: SessionService, + session_service: SessionService, # type: ignore[valid-type] sandbox: Sandbox = None, enable_clarification: bool = True, ): @@ -188,7 +174,7 @@ async def arun_agents( async def test_deepresearch_agent( task_str: str, - session_service: SessionService, + session_service: SessionService, # type: ignore[valid-type] sandbox: Sandbox = None, ): instruction = Msg( @@ -234,7 +220,7 @@ async def test_deepresearch_agent( async def test_browseruse_agent( task_str: str, - session_service: SessionService, + session_service: SessionService, # type: ignore[valid-type] sandbox: Sandbox = None, ): time_str = datetime.now().strftime("%Y%m%d%H%M%S") diff --git a/alias/src/alias/agent/tools/alias_toolkit.py b/alias/src/alias/agent/tools/alias_toolkit.py index ace5eb6..1e36b0c 100644 --- a/alias/src/alias/agent/tools/alias_toolkit.py +++ b/alias/src/alias/agent/tools/alias_toolkit.py @@ -1,19 +1,16 @@ # -*- coding: utf-8 -*- # pylint: disable=R1724 -from typing import Optional, Callable, Any import asyncio +from typing import Any, Callable, Optional + from loguru import logger -from agentscope.mcp import StatefulClientBase, MCPClientBase -from agentscope.tool import ( - Toolkit, - ToolResponse, -) -from agentscope.message import ToolUseBlock, TextBlock -from agentscope_runtime.sandbox import FilesystemSandbox, BrowserSandbox +from agentscope.mcp import MCPClientBase, StatefulClientBase +from agentscope.message import TextBlock, ToolUseBlock +from agentscope.tool import ToolResponse, Toolkit from alias.agent.tools.toolkit_hooks import ( - LongTextPostHook + LongTextPostHook, ) from alias.agent.tools.improved_tools import ImprovedFileOperations from alias.agent.tools.tool_blacklist import TOOL_BLACKLIST @@ -21,6 +18,9 @@ from alias.agent.tools.toolkit_hooks import read_file_post_hook from alias.runtime.alias_sandbox.alias_sandbox import AliasSandbox +FilesystemSandbox = AliasSandbox + + class AliasToolkit(Toolkit): def __init__( # pylint: disable=W0102 self, @@ -44,9 +44,8 @@ class AliasToolkit(Toolkit): # Get tools tools_schema = self.sandbox.list_tools() for category, function_dicts in tools_schema.items(): - if ( - (is_browser_toolkit and category == "playwright") - or (not is_browser_toolkit and category != "playwright") + if (is_browser_toolkit and category == "playwright") or ( + not is_browser_toolkit and category != "playwright" ): for _, function_json in function_dicts.items(): if function_json["name"] not in self.tool_blacklist: @@ -66,7 +65,7 @@ class AliasToolkit(Toolkit): def _add_io_function( self, json_schema: dict, - is_browser_tool: bool = False + is_browser_tool: bool = False, # pylint: disable=W0613 ) -> None: tool_name = json_schema["name"] @@ -142,8 +141,9 @@ class AliasToolkit(Toolkit): if tool_func.startswith(("read_file", "read_multiple_files")): self.tools[tool_func].postprocess_func = read_file_post_hook if tool_func.startswith("tavily"): - self.tools[tool_func].postprocess_func = \ - long_text_hook.truncate_and_save_response + self.tools[ + tool_func + ].postprocess_func = long_text_hook.truncate_and_save_response async def add_and_connet_mcp_client( self, @@ -193,10 +193,10 @@ async def test_toolkit(): type="tool_use", id="", name="list_allowed_directories", - input={} - ) + input={}, + ), ) - print(f"Allow directory:") + print("Allow directory:") async for response in res: print(response) @@ -216,5 +216,6 @@ async def test_toolkit(): await toolkit.close_mcp_clients() + if __name__ == "__main__": asyncio.run(test_toolkit()) diff --git a/alias/src/alias/agent/tools/improved_tools/__init__.py b/alias/src/alias/agent/tools/improved_tools/__init__.py index a85dd80..97647f7 100644 --- a/alias/src/alias/agent/tools/improved_tools/__init__.py +++ b/alias/src/alias/agent/tools/improved_tools/__init__.py @@ -2,8 +2,8 @@ """ Improved tools module for Alias agent toolkit. -This module contains enhanced tool functions that provide additional functionality -beyond the basic tools available in the standard toolkit. +This module contains enhanced tool functions that provide additional +functionality beyond the basic tools available in the standard toolkit. """ from .file_operations import ImprovedFileOperations diff --git a/alias/src/alias/agent/tools/improved_tools/file_operations.py b/alias/src/alias/agent/tools/improved_tools/file_operations.py index 357dce2..7ff1b5d 100644 --- a/alias/src/alias/agent/tools/improved_tools/file_operations.py +++ b/alias/src/alias/agent/tools/improved_tools/file_operations.py @@ -7,19 +7,20 @@ original read_file functionality and adds support for reading specific line ranges from files. """ -from typing import Optional -from loguru import logger import asyncio import os +from typing import Optional + +from loguru import logger -from agentscope.tool import ToolResponse from agentscope.message import TextBlock +from agentscope.tool import ToolResponse from alias.agent.utils.constants import TMP_FILE_DIR from alias.agent.tools.sandbox_util import ( TEXT_EXTENSIONS, create_or_edit_workspace_file, - create_workspace_directory + create_workspace_directory, ) from alias.runtime.alias_sandbox import AliasSandbox @@ -41,7 +42,7 @@ class ImprovedFileOperations: """init with sandbox""" self.sandbox = sandbox - async def read_file( + async def read_file( # pylint: disable=R0911,R0912 self, file_path: str, offset: int = 0, @@ -96,13 +97,14 @@ class ImprovedFileOperations: if self.sandbox is None: return ToolResponse( metadata={ - "success": False, "error": "No sandbox provided" + "success": False, + "error": "No sandbox provided", }, content=[ TextBlock( type="text", text="Error: No sandbox provided to " - "call the original read_file tool", + "call the original read_file tool", ), ], ) @@ -116,7 +118,7 @@ class ImprovedFileOperations: # Call the original read_file tool tool_res = self.sandbox.call_tool( name="read_file", - arguments=params + arguments=params, ) elif file_extension in TO_MARKDOWN_SUPPORT_MAPPING: tool_res = _transfer_to_markdown_text(file_path, self.sandbox) @@ -130,9 +132,10 @@ class ImprovedFileOperations: ): return ToolResponse( metadata={ - "success": False, "error": "Error when read file" + "success": False, + "error": "Error when read file", }, - content=tool_res.get("content", []) + content=tool_res.get("content", []), ) elif ( tool_res.get("isError", True) @@ -144,15 +147,15 @@ class ImprovedFileOperations: TextBlock( type="text", text=f"Fail to read file on path {file_path}", - ) - ] + ), + ], ) # Get the text content from the first content block full_content = "" for block in tool_res.get("content", []): - if isinstance(block, dict) and 'text' in block: - full_content += block['text'] + "\n" + if isinstance(block, dict) and "text" in block: + full_content += block["text"] + "\n" # Split into lines lines = full_content.splitlines(keepends=True) @@ -171,7 +174,7 @@ class ImprovedFileOperations: ) # Handle offset and limit - start_line = (offset or 0) # 0-based index + start_line = offset or 0 # 0-based index end_line = start_line + (limit or total_lines) # Validate range @@ -182,7 +185,7 @@ class ImprovedFileOperations: TextBlock( type="text", text=f"Error: Start line {offset} is " - f"beyond file length ({total_lines} lines).", + f"beyond file length ({total_lines} lines).", ), ], ) @@ -193,11 +196,13 @@ class ImprovedFileOperations: # Extract the requested lines selected_lines = lines[start_line:end_line] - content = ''.join(selected_lines) + content = "".join(selected_lines) # Add summary information - summary = (f"Read lines {start_line}-{end_line} of " - f"{total_lines} total lines from '{file_path}'") + summary = ( + f"Read lines {start_line}-{end_line} of " + f"{total_lines} total lines from '{file_path}'" + ) # save as markdown return_content = [ @@ -208,18 +213,20 @@ class ImprovedFileOperations: TextBlock( type="text", text=summary, - ) + ), ] if file_extension in TO_MARKDOWN_SUPPORT_MAPPING: file_name_with_ext = os.path.basename(file_path) filename_without_ext = os.path.splitext(file_name_with_ext)[0] file_path = os.path.join( TMP_FILE_DIR, - filename_without_ext + ".md" + filename_without_ext + ".md", ) create_workspace_directory(self.sandbox, TMP_FILE_DIR) create_or_edit_workspace_file( - self.sandbox, file_path, full_content + self.sandbox, + file_path, + full_content, ) return_content.append( TextBlock( @@ -229,8 +236,8 @@ class ImprovedFileOperations: "The (full) file is converted as markdown file" " and saved completely at: " f"{file_path}" - ) - ) + ), + ), ) return ToolResponse( @@ -257,7 +264,8 @@ class ImprovedFileOperations: def _transfer_to_markdown_text( - file_path: str, sandbox: AliasSandbox = None + file_path: str, + sandbox: AliasSandbox = None, ) -> dict: ext = os.path.splitext(file_path)[1].lower() @@ -268,46 +276,46 @@ def _transfer_to_markdown_text( { "type": "text", "text": f"File extension '{ext}' not supported in " - f"{TO_MARKDOWN_SUPPORT_MAPPING}." - } - ] + f"{TO_MARKDOWN_SUPPORT_MAPPING}.", + }, + ], } params = { - "uri": "file:" + file_path + "uri": "file:" + file_path, } try: - res = sandbox.call_tool( + result = sandbox.call_tool( # pylint: disable=W0621 name="convert_to_markdown", - arguments=params + arguments=params, ) - content = res.get("content", []) + content = result.get("content", []) new_content = [] - for i, block in enumerate(content): + for i, _block in enumerate(content): if content[i].get("text", "").startswith("Converted content:"): continue - elif content[i].get("text", "").startswith("Output file:"): + if content[i].get("text", "").startswith("Output file:"): continue - else: - new_content.append(res["content"][i]) + new_content.append(result["content"][i]) - res["content"] = new_content + result["content"] = new_content except Exception as e: - res = { + result = { "isError": True, - "error": str(e) + "error": str(e), } - return res + return result if __name__ == "__main__": from alias.agent.tools.sandbox_util import copy_local_file_to_workspace + with AliasSandbox() as box: res = copy_local_file_to_workspace( box, "/Users/zitao.l/Downloads/22051_Which_LLM_Multi_Agent.pdf", - "/workspace/test.pdf" + "/workspace/test.pdf", ) print(res) toolset = ImprovedFileOperations(box) diff --git a/alias/src/alias/agent/tools/improved_tools/multimodal_to_text.py b/alias/src/alias/agent/tools/improved_tools/multimodal_to_text.py index 410cc87..f726607 100644 --- a/alias/src/alias/agent/tools/improved_tools/multimodal_to_text.py +++ b/alias/src/alias/agent/tools/improved_tools/multimodal_to_text.py @@ -95,7 +95,7 @@ class DashScopeMultiModalTools: "content": [ { "text": "Transcript the content in the audio " - "to text." + "to text.", }, ], }, @@ -253,6 +253,7 @@ class DashScopeMultiModalTools: ) except Exception as e: import traceback + print(traceback.format_exc()) return ToolResponse( [ @@ -268,7 +269,7 @@ if __name__ == "__main__": with AliasSandbox() as box: tool_result = box.call_tool( "run_shell_command", - arguments={"command": "apt update"} + arguments={"command": "apt update"}, ) print(tool_result) tool_result = box.call_tool( @@ -299,7 +300,7 @@ if __name__ == "__main__": ) toolset = DashScopeMultiModalTools( sandbox=box, - dashscope_api_key=os.getenv("DASHSCOPE_API_KEY", "") + dashscope_api_key=os.getenv("DASHSCOPE_API_KEY", ""), ) result = toolset.dashscope_image_to_text( image_url=picture_path, diff --git a/alias/src/alias/agent/tools/sandbox_util.py b/alias/src/alias/agent/tools/sandbox_util.py index fc9073e..290b2d1 100644 --- a/alias/src/alias/agent/tools/sandbox_util.py +++ b/alias/src/alias/agent/tools/sandbox_util.py @@ -1,14 +1,17 @@ # -*- coding: utf-8 -*- -import os -from typing import Optional -import json -from pathlib import Path import base64 -from loguru import logger import io +import json +import os import tarfile +from pathlib import Path +from typing import Optional -from agentscope_runtime.sandbox.manager.container_clients.docker_client import DockerClient +from loguru import logger + +from agentscope_runtime.sandbox.manager.container_clients.docker_client import ( # noqa: E501 # pylint: disable=C0301 + DockerClient, +) from alias.runtime.alias_sandbox import AliasSandbox @@ -386,7 +389,7 @@ def copy_local_file_to_workspace( { "type": "text", "text": "Copying file is not support sandbox " - f"with client type {type(client)}", + f"with client type {type(client)}", }, ], } @@ -395,7 +398,8 @@ def copy_local_file_to_workspace( # Create a tar archive in memory tar_stream = io.BytesIO() - tar = tarfile.open(fileobj=tar_stream, mode='w') + # pylint: disable=R1732 + tar = tarfile.open(fileobj=tar_stream, mode="w") # Add file to tar archive tar.add(local_path, arcname=os.path.basename(target_path)) @@ -418,7 +422,6 @@ def copy_local_file_to_workspace( } - if __name__ == "__main__": with AliasSandbox() as box: create_or_edit_workspace_file( diff --git a/alias/src/alias/agent/tools/toolkit_hooks/__init__.py b/alias/src/alias/agent/tools/toolkit_hooks/__init__.py index a141fc3..bd01e1b 100644 --- a/alias/src/alias/agent/tools/toolkit_hooks/__init__.py +++ b/alias/src/alias/agent/tools/toolkit_hooks/__init__.py @@ -1,7 +1,8 @@ +# -*- coding: utf-8 -*- from .long_text_post_hook import LongTextPostHook from .read_file_post_hook import read_file_post_hook __all__ = [ "LongTextPostHook", "read_file_post_hook", -] \ No newline at end of file +] diff --git a/alias/src/alias/agent/tools/toolkit_hooks/long_text_post_hook.py b/alias/src/alias/agent/tools/toolkit_hooks/long_text_post_hook.py index bcb82eb..8cea590 100644 --- a/alias/src/alias/agent/tools/toolkit_hooks/long_text_post_hook.py +++ b/alias/src/alias/agent/tools/toolkit_hooks/long_text_post_hook.py @@ -10,7 +10,7 @@ from agentscope.message import ToolUseBlock, TextBlock from alias.agent.utils.constants import TMP_FILE_DIR from alias.agent.tools.sandbox_util import ( create_or_edit_workspace_file, - create_workspace_directory + create_workspace_directory, ) @@ -18,7 +18,7 @@ class LongTextPostHook: def __init__(self, sandbox): self.sandbox = sandbox - def truncate_and_save_response( + def truncate_and_save_response( # pylint: disable=R1710 self, tool_use: ToolUseBlock, # pylint: disable=W0613 tool_response: ToolResponse, @@ -35,25 +35,24 @@ class LongTextPostHook: tool_response: The tool response to potentially truncate. Note: - The budget is set to approximately 80K tokens (8194 * 10 characters) - to ensure responses remain manageable for the language model. + The budget is set to approximately 80K tokens + (8194 * 10 characters) to ensure responses remain + manageable for the language model. """ # Set budget to prevent overwhelming the model with too much content budget = 8194 * 10 # Approximately 80K tokens of content - append_hint = ( - "\n\n[Content is too long and truncated....]" - ) + append_hint = "\n\n[Content is too long and truncated....]" new_tool_response = ToolResponse( id=tool_response.id, stream=tool_response.stream, is_last=tool_response.is_last, is_interrupted=tool_response.is_interrupted, - content=[] + content=[], ) if isinstance(tool_response.content, list): save_text_block = None - for i, block in enumerate(tool_response.content): + for _i, block in enumerate(tool_response.content): if block["type"] == "text": text = block["text"] text_len = len(text) @@ -67,7 +66,7 @@ class LongTextPostHook: tmp_file_name_prefix = tool_use.get("name", "") save_text_block = self._save_tmp_file( tmp_file_name_prefix, - tool_response.content + tool_response.content, ) new_tool_response.append = ( text[:threshold] + append_hint @@ -75,8 +74,8 @@ class LongTextPostHook: new_tool_response.content.append( TextBlock( type="text", - text=text[:threshold] + append_hint - ) + text=text[:threshold] + append_hint, + ), ) else: new_tool_response.content.append(block) @@ -91,29 +90,32 @@ class LongTextPostHook: tmp_file_name_prefix = tool_use.get("name", "") save_text_block = self._save_tmp_file( tmp_file_name_prefix, - tool_response.content + tool_response.content, ) # Calculate truncation threshold (80% of proportional budget) threshold = int(budget / text_len * len(text) * 0.8) - tool_response.content = ( - text[:threshold] + append_hint - ) + tool_response.content = text[:threshold] + append_hint tool_response.content = [ TextBlock(type="text", text=tool_response.content), - save_text_block + save_text_block, ] - + return tool_response return tool_response def _save_tmp_file(self, save_file_name_prefix: str, content: list | str): create_workspace_directory(self.sandbox, TMP_FILE_DIR) - save_file_name = save_file_name_prefix + "-" + str( - uuid.uuid4().hex[:8] + save_file_name = ( + save_file_name_prefix + + "-" + + str( + uuid.uuid4().hex[:8], + ) ) file_path = os.path.join(TMP_FILE_DIR, save_file_name) json_str = json.dumps(content, ensure_ascii=False, indent=2) - wrapped = '\\n'.join( - [textwrap.fill(line, width=500) for line in json_str.split('\\n')]) + wrapped = "\\n".join( + [textwrap.fill(line, width=500) for line in json_str.split("\\n")], + ) create_or_edit_workspace_file( self.sandbox, file_path, @@ -122,10 +124,8 @@ class LongTextPostHook: return TextBlock( type="text", text=f"Dump the complete long file at {file_path}. " - "Don't try to read the complete file directly. " - "Use `grep -C 10 'YOUR_PATTERN' {file_path}` or " - "other bash command to extract " - "useful information.", + "Don't try to read the complete file directly. " + "Use `grep -C 10 'YOUR_PATTERN' {file_path}` or " + "other bash command to extract " + "useful information.", ) - - diff --git a/alias/src/alias/agent/tools/toolkit_hooks/read_file_post_hook.py b/alias/src/alias/agent/tools/toolkit_hooks/read_file_post_hook.py index 8af9d76..6aac2cb 100644 --- a/alias/src/alias/agent/tools/toolkit_hooks/read_file_post_hook.py +++ b/alias/src/alias/agent/tools/toolkit_hooks/read_file_post_hook.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- from agentscope.message import ToolUseBlock, TextBlock from agentscope.tool import ToolResponse @@ -21,8 +22,8 @@ def _summarize_csv(text_block: TextBlock) -> None: def read_file_post_hook( - tool_use: ToolUseBlock, - tool_response: ToolResponse, + tool_use: ToolUseBlock, + tool_response: ToolResponse, ) -> ToolResponse: """ Condense large CSV outputs after `read_file` or `read_multiple_files`. diff --git a/alias/src/alias/cli.py b/alias/src/alias/cli.py index 4a0a32b..b14f6f6 100644 --- a/alias/src/alias/cli.py +++ b/alias/src/alias/cli.py @@ -6,20 +6,17 @@ Alias Command Line Interface This module provides a terminal executable entry point for the Alias agent application. """ -import json -from typing import Optional -import asyncio import argparse -import sys +import asyncio import os +import sys import traceback import webbrowser +from typing import Optional from loguru import logger -from agentscope.agent import UserAgent, TerminalUserInput -from agentscope_runtime.sandbox import FilesystemSandbox, BrowserSandbox -from agentscope_runtime.sandbox.box.sandbox import Sandbox -from agentscope.mcp import StdIOStatefulClient + +from agentscope.agent import TerminalUserInput, UserAgent from alias.agent.mock import MockSessionService, UserMessage from alias.agent.run import ( @@ -27,10 +24,8 @@ from alias.agent.run import ( test_browseruse_agent, test_deepresearch_agent, ) -from alias.agent.tools import AliasToolkit -from alias.agent.tools.improved_tools import DashScopeMultiModalTools -from alias.runtime.alias_sandbox.alias_sandbox import AliasSandbox from alias.agent.tools.sandbox_util import copy_local_file_to_workspace +from alias.runtime.alias_sandbox.alias_sandbox import AliasSandbox async def run_agent_task( @@ -40,7 +35,7 @@ async def run_agent_task( ) -> None: """ Run an agent task with the specified configuration. - + Args: user_msg: The user's task/query mode: Agent mode ('all', 'worker', 'dr', 'browser') @@ -52,15 +47,15 @@ async def run_agent_task( # Create initial user message user_agent = UserAgent(name="User") user_agent.override_instance_input_method( - input_method = TerminalUserInput( - input_hint = "User (Enter `exit` or `quit` to exit): " - ) + input_method=TerminalUserInput( + input_hint="User (Enter `exit` or `quit` to exit): ", + ), ) - + # Run agent with sandbox context with AliasSandbox() as sandbox: logger.info( - f"Sandbox mount dir: {sandbox.get_info().get('mount_dir')}" + f"Sandbox mount dir: {sandbox.get_info().get('mount_dir')}", ) logger.info(f"Sandbox desktop URL: {sandbox.desktop_url}") webbrowser.open(sandbox.desktop_url) @@ -68,28 +63,27 @@ async def run_agent_task( if files: target_paths = [] logger.info( - f"Uploading {len(files)} file(s) to sandbox workspace..." + f"Uploading {len(files)} file(s) to sandbox workspace...", ) for file_path in files: if not os.path.exists(file_path): logger.error(f"File not found: {file_path}") continue - + # Get the filename and construct target path in workspace filename = os.path.basename(file_path) target_path = f"/workspace/{filename}" - + logger.info(f"Uploading {file_path} to {target_path}") result = copy_local_file_to_workspace( sandbox=sandbox, local_path=file_path, target_path=target_path, ) - + if result.get("isError"): raise ValueError(f"Failed to upload {file_path}: {result}") - else: - logger.info(f"Successfully uploaded to {result}") + logger.info(f"Successfully uploaded to {result}") target_paths.append(result.get("content", [])[0].get("text")) @@ -99,23 +93,24 @@ async def run_agent_task( content=user_msg, ) await session.create_message(initial_user_message) - + await _run_agent_loop( mode=mode, session=session, user_agent=user_agent, - sandbox=sandbox + sandbox=sandbox, ) + async def _run_agent_loop( mode: str, session: MockSessionService, user_agent: UserAgent, - sandbox: FilesystemSandbox, + sandbox: AliasSandbox, ) -> None: """ Execute the agent loop with follow-up interactions. - + Args: mode: Agent mode to run session: Session service instance @@ -133,8 +128,10 @@ async def _run_agent_loop( sandbox=sandbox, ) break - elif mode == "dr": - usr_msg = (await session.get_messages())[-1].message.get("content") + if mode == "dr": + usr_msg = (await session.get_messages())[-1].message.get( + "content", + ) logger.info(f"--> user_msg: {usr_msg}") await test_deepresearch_agent( usr_msg, @@ -142,7 +139,7 @@ async def _run_agent_loop( sandbox=sandbox, ) break - elif mode == "all": + if mode == "all": await arun_agents( session, sandbox=sandbox, @@ -150,16 +147,16 @@ async def _run_agent_loop( ) else: raise ValueError(f"Unknown mode: {mode}") - + # Check for follow-up interaction follow_msg = await user_agent() - if ( - len(follow_msg.content) == 0 - or follow_msg.content.lower() in ["exit", "quit"] - ): + if len(follow_msg.content) == 0 or follow_msg.content.lower() in [ + "exit", + "quit", + ]: logger.info("Exiting agent loop") break - + await session.create_message(UserMessage(content=follow_msg.content)) @@ -174,25 +171,26 @@ def main(): ), formatter_class=argparse.RawDescriptionHelpFormatter, ) - + subparsers = parser.add_subparsers( - dest="command", help="Available commands" + dest="command", + help="Available commands", ) - + # Run command run_parser = subparsers.add_parser( "run", help="Run an agent task", formatter_class=argparse.RawDescriptionHelpFormatter, ) - + run_parser.add_argument( "--task", type=str, required=True, help="The task or query for the agent to execute", ) - + run_parser.add_argument( "--mode", choices=["all", "worker", "dr", "browser"], @@ -205,37 +203,37 @@ def main(): "'browser' (browser agent)" ), ) - + run_parser.add_argument( "--verbose", "-v", action="store_true", help="Enable verbose logging", ) - + run_parser.add_argument( "--files", "-f", type=str, nargs="+", help="Local file paths to upload to sandbox workspace " - "for agent to use (e.g., --files file1.txt file2.csv)", + "for agent to use (e.g., --files file1.txt file2.csv)", ) - + # Version command parser.add_argument( "--version", action="version", version="Alias 0.1.0", ) - + args = parser.parse_args() - + # Configure logging if hasattr(args, "verbose") and args.verbose: logger.remove() logger.add(sys.stderr, level="DEBUG") - + # Handle commands if args.command == "run": try: @@ -244,7 +242,7 @@ def main(): user_msg=args.task, mode=args.mode, files=args.files if hasattr(args, "files") else None, - ) + ), ) except KeyboardInterrupt: logger.info("\nInterrupted by user") diff --git a/alias/src/alias/runtime/__init__.py b/alias/src/alias/runtime/__init__.py index 3e06201..c508444 100644 --- a/alias/src/alias/runtime/__init__.py +++ b/alias/src/alias/runtime/__init__.py @@ -1,3 +1,7 @@ # -*- coding: utf-8 -*- +"""Runtime module for Alias""" -from agentscope_runtime.sandbox.box.sandbox import Sandbox +__all__ = ["alias_sandbox"] + +# Import submodule to make it accessible via alias.runtime.alias_sandbox +from . import alias_sandbox # noqa: E402, F401 diff --git a/alias/src/alias/runtime/alias_sandbox/__init__.py b/alias/src/alias/runtime/alias_sandbox/__init__.py index f2e7108..325afe4 100644 --- a/alias/src/alias/runtime/alias_sandbox/__init__.py +++ b/alias/src/alias/runtime/alias_sandbox/__init__.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- from .alias_sandbox import AliasSandbox -__all__ = ['AliasSandbox'] \ No newline at end of file +__all__ = ["AliasSandbox"]