Fix alias pre-commit errors (#28)

This commit is contained in:
ZiTao-Li
2025-11-06 23:42:52 -08:00
committed by GitHub
parent 120ca6d814
commit 1f0c5de27f
31 changed files with 430 additions and 386 deletions

View File

@@ -66,6 +66,7 @@ repos:
| \.html$ | \.html$
) )
args: [ args: [
"--init-hook=import sys; sys.path.insert(0, 'alias/src')",
--disable=W0511, --disable=W0511,
--disable=W0718, --disable=W0718,
--disable=W0122, --disable=W0122,

View File

@@ -3,3 +3,9 @@
__version__ = "0.0.1" __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

View File

@@ -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

View File

@@ -1,11 +1,12 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from typing import Optional, Any, Type, Callable
import asyncio import asyncio
import time
from pydantic import BaseModel
from loguru import logger
import traceback
import json 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.agent import ReActAgent
from agentscope.model import ChatModelBase from agentscope.model import ChatModelBase
@@ -49,6 +50,7 @@ class AliasAgentBase(ReActAgent):
async def _reasoning(self): async def _reasoning(self):
"""Override _reasoning to add retry logic.""" """Override _reasoning to add retry logic."""
# Call the parent class's _reasoning method directly to # Call the parent class's _reasoning method directly to
# avoid double hook execution # avoid double hook execution
# We need to call the underlying implementation without hooks # We need to call the underlying implementation without hooks
@@ -57,10 +59,10 @@ class AliasAgentBase(ReActAgent):
# metaclass processing # metaclass processing
# Access the method from the class that defines it # Access the method from the class that defines it
# (before metaclass wrapping) # (before metaclass wrapping)
original_method = ReActAgent.__dict__['_reasoning'] original_method = ReActAgent.__dict__["_reasoning"]
# Check if this is the wrapped version by looking for # Check if this is the wrapped version by looking for
# the wrapper attributes # the wrapper attributes
if hasattr(original_method, '__wrapped__'): if hasattr(original_method, "__wrapped__"):
# This is the wrapped version, get the original # This is the wrapped version, get the original
original_method = original_method.__wrapped__ original_method = original_method.__wrapped__
return await original_method(self) return await original_method(self)
@@ -68,17 +70,17 @@ class AliasAgentBase(ReActAgent):
for i in range(MODEL_MAX_RETRIES - 1): for i in range(MODEL_MAX_RETRIES - 1):
try: try:
return await call_parent_reasoning() return await call_parent_reasoning()
except Exception as e: except Exception:
logger.warning( logger.warning(
f"Reasoning fail at attempt {i + 1}. " f"Reasoning fail at attempt {i + 1}. "
f"Max attempts {MODEL_MAX_RETRIES}\n" f"Max attempts {MODEL_MAX_RETRIES}\n"
f"{traceback.format_exc()}" f"{traceback.format_exc()}",
) )
memory_msgs = await self.memory.get_memory() memory_msgs = await self.memory.get_memory()
mem_len = len(memory_msgs) mem_len = len(memory_msgs)
# ensure the last message has no tool_use before next attempt # ensure the last message has no tool_use before next attempt
if mem_len > 0 and memory_msgs[-1].has_content_blocks( if mem_len > 0 and memory_msgs[-1].has_content_blocks(
"tool_use" "tool_use",
): ):
await self.memory.delete(index=mem_len - 1) await self.memory.delete(index=mem_len - 1)
time.sleep(2) time.sleep(2)
@@ -241,7 +243,6 @@ class AliasAgentBase(ReActAgent):
# Skip non-serializable values # Skip non-serializable values
pass pass
# Skip the printing of the finish function call # Skip the printing of the finish function call
if ( if (
tool_call["name"] != self.finish_function_name tool_call["name"] != self.finish_function_name

View File

@@ -1,51 +1,40 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
"""Deep Research Agent""" """Deep Research Agent"""
# pylint: disable=too-many-lines, no-name-in-module # pylint: disable=too-many-lines, no-name-in-module
import os
import json import json
import os
import traceback import traceback
import uuid import uuid
from typing import Type, Optional, Any, Tuple
from datetime import datetime from datetime import datetime
from typing import Any, Optional, Tuple, Type
# from copy import deepcopy # from copy import deepcopy
import shortuuid import shortuuid
from pydantic import BaseModel 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.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 ( from alias.agent.agents._planning_tools._planning_notebook import (
WorkerResponse, WorkerResponse,
) )
from alias.agent.tools import AliasToolkit
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,
)
_DEEP_RESEARCH_AGENT_DEFAULT_SYS_PROMPT = "You're a helpful assistant." _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( async def deep_research_pre_reply_hook(
self: "DeepResearchAgent", self: "DeepResearchAgent",
kwargs: dict[str, Any], # pylint: disable=W0613 kwargs: dict[str, Any],
): ):
# Maintain the subtask list # Maintain the subtask list
msg: Msg = kwargs.get("msg") msg: Msg = kwargs.get("msg")
@@ -82,29 +71,40 @@ async def deep_research_pre_reply_hook(
async def deep_research_post_reply_hook( async def deep_research_post_reply_hook(
self: "DeepResearchAgent", self: "DeepResearchAgent",
kwargs: Any, kwargs: Any, # pylint: disable=W0613
output: Any, output: Any, # pylint: disable=W0613
): ):
self.current_subtask = [] self.current_subtask = []
def _dump_json( def _dump_json(
save_info: list[Msg] | dict, save_info: list[Msg] | dict,
dir: str = "./dr_execution_trac" directory: str = "./dr_execution_trac",
): ):
if not os.path.isdir(dir): if not os.path.isdir(directory):
os.makedirs(dir, exist_ok=True) os.makedirs(directory, exist_ok=True)
if isinstance(save_info, list) and len(save_info) > 0 and isinstance(save_info[0], Msg): 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] 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: else:
file_path = os.path.join(dir, "plane-" + str(uuid.uuid4().hex) + ".json") file_path = os.path.join(
with open(file_path, "w") as f: 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) json.dump(save_info, f, ensure_ascii=False, indent=4)
async def deep_research_pre_reasoning_hook( async def deep_research_pre_reasoning_hook(
self: "DeepResearchAgent", self: "DeepResearchAgent",
kwargs: Any, kwargs: Any, # pylint: disable=W0613
): ):
memory = await self.memory.get_memory() memory = await self.memory.get_memory()
_dump_json(memory) _dump_json(memory)
@@ -117,15 +117,17 @@ async def deep_research_pre_reasoning_hook(
] ]
research_results = [] research_results = []
for tool_call in self.search_call_buffer: 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: if msg is not None:
research_results.append( research_results.append(
json.dumps( json.dumps(
msg.get_content_blocks("tool_result"), msg.get_content_blocks("tool_result"),
ensure_ascii=False, ensure_ascii=False,
) ),
) )
await self._follow_up( await self._follow_up( # pylint: disable=W0212
search_results="\n".join(research_results), search_results="\n".join(research_results),
search_queries="\n".join(search_queries), 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( reasoning_prompt = self.prompt_dict["reasoning_prompt"].format_map(
{ {
"objective": self.current_subtask[-1].objective, "objective": self.current_subtask[-1].objective,
"plan": cur_plan "plan": cur_plan if cur_plan else "There is no working plan now.",
if cur_plan
else "There is no working plan now.",
"knowledge_gap": f"## Knowledge Gaps:\n {cur_know_gap}" "knowledge_gap": f"## Knowledge Gaps:\n {cur_know_gap}"
if cur_know_gap if cur_know_gap
else "", else "",
@@ -166,8 +166,8 @@ async def deep_research_pre_reasoning_hook(
async def deep_research_post_reasoning_hook( async def deep_research_post_reasoning_hook(
self: "DeepResearchAgent", # pylint: disable=W0613 self: "DeepResearchAgent", # pylint: disable=W0613
kwargs: Any, kwargs: Any, # pylint: disable=W0613
output_msg: Msg, output_msg: Msg, # pylint: disable=W0613
): ):
num_msgs = await self.memory.size() num_msgs = await self.memory.size()
if num_msgs > 1: if num_msgs > 1:
@@ -178,7 +178,7 @@ async def deep_research_post_reasoning_hook(
async def deep_research_post_action_hook( async def deep_research_post_action_hook(
self: "DeepResearchAgent", self: "DeepResearchAgent",
kwargs: Any, kwargs: Any,
output_msg: Msg, output_msg: Msg, # pylint: disable=W0613
): ):
tool_call = kwargs.get("tool_call", {}) tool_call = kwargs.get("tool_call", {})
if tool_call and tool_call.get("name") == self.search_function: if tool_call and tool_call.get("name") == self.search_function:
@@ -274,7 +274,7 @@ class DeepResearchAgent(AliasAgentBase):
"intermediate_summarize": self.summarize_function, "intermediate_summarize": self.summarize_function,
"reflect_failure": "reflect_failure", "reflect_failure": "reflect_failure",
"subtask_finish": "finish_current_subtask", "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( tool_use_rule = self.prompt_dict["tool_use_rule"].format_map(
@@ -311,34 +311,34 @@ class DeepResearchAgent(AliasAgentBase):
self.summarize_intermediate_results, self.summarize_intermediate_results,
) )
self.toolkit.register_tool_function( self.toolkit.register_tool_function(
self.finish_current_subtask self.finish_current_subtask,
) )
# add hooks # add hooks
self.register_instance_hook( self.register_instance_hook(
"pre_reply", "pre_reply",
"deep_research_pre_reply_hook", "deep_research_pre_reply_hook",
deep_research_pre_reply_hook deep_research_pre_reply_hook,
) )
self.register_instance_hook( self.register_instance_hook(
"post_reply", "post_reply",
"deep_research_post_reply_hook", "deep_research_post_reply_hook",
deep_research_post_reply_hook deep_research_post_reply_hook,
) )
self.register_instance_hook( self.register_instance_hook(
"pre_reasoning", "pre_reasoning",
"deep_research_pre_reasoning_hook", "deep_research_pre_reasoning_hook",
deep_research_pre_reasoning_hook deep_research_pre_reasoning_hook,
) )
self.register_instance_hook( self.register_instance_hook(
"post_reasoning", "post_reasoning",
"deep_research_post_reasoning_hook", "deep_research_post_reasoning_hook",
deep_research_post_reasoning_hook deep_research_post_reasoning_hook,
) )
self.register_instance_hook( self.register_instance_hook(
"post_acting", "post_acting",
"deep_research_post_action_hook", "deep_research_post_action_hook",
deep_research_post_action_hook deep_research_post_action_hook,
) )
self.search_call_buffer = [] self.search_call_buffer = []
@@ -471,7 +471,7 @@ class DeepResearchAgent(AliasAgentBase):
"Identify the knowledge gaps of the current " "Identify the knowledge gaps of the current "
"subtask and generate a working plan by subtask " "subtask and generate a working plan by subtask "
"decomposition", "decomposition",
"assistant" "assistant",
), ),
) )
@@ -548,7 +548,7 @@ class DeepResearchAgent(AliasAgentBase):
"(Follow-up by extraction)" "(Follow-up by extraction)"
"Read the website more intensively to mine more " "Read the website more intensively to mine more "
"information.", "information.",
"assistant" "assistant",
), ),
) )
try: try:
@@ -568,9 +568,9 @@ class DeepResearchAgent(AliasAgentBase):
text=json.dumps( text=json.dumps(
extraction_check, extraction_check,
ensure_ascii=False, ensure_ascii=False,
indent=2 indent=2,
) ),
) ),
], ],
role="assistant", role="assistant",
) )
@@ -579,7 +579,7 @@ class DeepResearchAgent(AliasAgentBase):
except Exception as e: # noqa: F841 except Exception as e: # noqa: F841
logger.warning( logger.warning(
f"Error when checking subtask finish status {e}" f"Error when checking subtask finish status {e}"
f"{traceback.format_exc()}" f"{traceback.format_exc()}",
) )
extraction_check = {} extraction_check = {}
@@ -599,9 +599,9 @@ class DeepResearchAgent(AliasAgentBase):
Msg( Msg(
self.name, self.name,
[TextBlock(type="text", text=f"Reading {urls}")], [TextBlock(type="text", text=f"Reading {urls}")],
"assistant" "assistant",
), ),
last=True last=True,
) )
# call the extract_function # call the extract_function
@@ -629,7 +629,7 @@ class DeepResearchAgent(AliasAgentBase):
self.name, self.name,
"(Follow-up to explore)" "(Follow-up to explore)"
"Check if current subtask knowledge gaps are fulfilled", "Check if current subtask knowledge gaps are fulfilled",
"assistant" "assistant",
), ),
) )
msgs = [ msgs = [
@@ -646,7 +646,7 @@ class DeepResearchAgent(AliasAgentBase):
"user", "user",
self.prompt_dict["follow_up_judge_sys_prompt"], self.prompt_dict["follow_up_judge_sys_prompt"],
role="user", role="user",
) ),
] ]
follow_up_judge = await self.get_model_output( follow_up_judge = await self.get_model_output(
msgs=msgs, msgs=msgs,
@@ -660,23 +660,26 @@ class DeepResearchAgent(AliasAgentBase):
type="text", type="text",
text=json.dumps( text=json.dumps(
follow_up_judge, follow_up_judge,
ensure_ascii=False, indent=2 ensure_ascii=False,
) indent=2,
) ),
),
], ],
role="assistant", role="assistant",
) )
await self.memory.add(follow_up_msg) await self.memory.add(follow_up_msg)
except Exception as e: # noqa: F841 except Exception as e: # noqa: F841
logger.warning( logger.warning(
f"Error when checking subtask finish status {e}" f"Error when checking subtask finish status {e}",
) )
logger.error(traceback.format_exc()) logger.error(traceback.format_exc())
follow_up_judge = {} follow_up_judge = {}
if follow_up_judge.get("knowledge_gap_revision", ""): if follow_up_judge.get("knowledge_gap_revision", ""):
self.current_subtask[-1].knowledge_gaps = \ self.current_subtask[-1].knowledge_gaps = follow_up_judge.get(
follow_up_judge.get("knowledge_gap_revision", "") "knowledge_gap_revision",
"",
)
if ( if (
follow_up_judge.get("to_further_explore", False) follow_up_judge.get("to_further_explore", False)
@@ -690,15 +693,13 @@ class DeepResearchAgent(AliasAgentBase):
TextBlock( TextBlock(
type="text", type="text",
text="Still need to do more research " text="Still need to do more research "
f"to figure out {subtask}", f"to figure out {subtask}",
) ),
], ],
role="assistant" role="assistant",
) ),
)
intermediate_report = (
await self.summarize_intermediate_results()
) )
intermediate_report = await self.summarize_intermediate_results()
self.current_subtask.append( self.current_subtask.append(
SubTaskItem(objective=subtask), SubTaskItem(objective=subtask),
) )
@@ -748,14 +749,12 @@ class DeepResearchAgent(AliasAgentBase):
for msg in reversed(memory_msgs): for msg in reversed(memory_msgs):
if msg.metadata and msg.metadata.get("is_report_msg"): if msg.metadata and msg.metadata.get("is_report_msg"):
break break
else: intermediate_memory.append(msg)
intermediate_memory.append(msg)
intermediate_memory.reverse() intermediate_memory.reverse()
if remove_last_tool_use: if remove_last_tool_use:
while ( while len(intermediate_memory) > 0 and intermediate_memory[
len(intermediate_memory) > 0 and -1
intermediate_memory[-1].has_content_blocks("tool_use") ].has_content_blocks("tool_use"):
):
intermediate_memory.pop(-1) intermediate_memory.pop(-1)
return intermediate_memory return intermediate_memory
@@ -765,34 +764,33 @@ class DeepResearchAgent(AliasAgentBase):
for msg in reversed(memory_msgs): for msg in reversed(memory_msgs):
if msg.metadata and msg.metadata.get("is_report_msg"): if msg.metadata and msg.metadata.get("is_report_msg"):
break break
elif msg.role == "user": if msg.role == "user":
break break
elif msg.has_content_blocks("tool_use"): if msg.has_content_blocks("tool_use"):
stop = False stop = False
for block in msg.get_content_blocks("tool_use"): for block in msg.get_content_blocks("tool_use"):
if block.get("name") == self.summarize_function: if block.get("name") == self.summarize_function:
stop = True stop = True
if stop: if stop:
break break
else: remove_num += 1
remove_num += 1
else: else:
remove_num += 1 remove_num += 1
start_index = len(memory_msgs) - remove_num start_index = len(memory_msgs) - remove_num
logger.info( logger.info(
"---> delete messages: " "---> 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)))) await self.memory.delete(list(range(start_index, len(memory_msgs))))
async def _get_research_result( async def _get_research_result(
self, self,
tool_call_id: str tool_call_id: str,
) -> Msg | None: ) -> Msg | None:
memory_msgs = await self.memory.get_memory() memory_msgs = await self.memory.get_memory()
for msg in reversed(memory_msgs): for msg in reversed(memory_msgs):
if msg.has_content_blocks("tool_result"): 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: if block.get("id") == tool_call_id:
return msg return msg
return None return None
@@ -823,7 +821,7 @@ class DeepResearchAgent(AliasAgentBase):
"[summarize_intermediate_results]" "[summarize_intermediate_results]"
"Examine whether the knowledge gaps or objective" "Examine whether the knowledge gaps or objective"
"have been fulfill", "have been fulfill",
"assistant" "assistant",
), ),
) )
@@ -883,7 +881,7 @@ class DeepResearchAgent(AliasAgentBase):
Msg( Msg(
self.name, self.name,
"Summarize the intermediate results into a report", "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: else:
# add to memory for the follow-up case # add to memory for the follow-up case
@@ -948,7 +946,7 @@ class DeepResearchAgent(AliasAgentBase):
), ),
], ],
role="assistant", role="assistant",
metadata={"is_report_msg": True} metadata={"is_report_msg": True},
), ),
) )
return ToolResponse( return ToolResponse(
@@ -989,7 +987,7 @@ class DeepResearchAgent(AliasAgentBase):
tmp_report_path = os.path.join( tmp_report_path = os.path.join(
self.tmp_file_storage_dir, self.tmp_file_storage_dir,
f"{self.report_path_based}_" f"{self.report_path_based}_"
f"inprocess_report_{index + 1}.md" f"inprocess_report_{index + 1}.md",
) )
params = { params = {
"file_path": tmp_report_path, "file_path": tmp_report_path,
@@ -1010,11 +1008,11 @@ class DeepResearchAgent(AliasAgentBase):
TextBlock( TextBlock(
type="text", type="text",
text="Reading progress report: " text="Reading progress report: "
f"{tmp_report_path}" f"{tmp_report_path}",
) ),
], ],
"assistant" "assistant",
) ),
) )
msgs = [ msgs = [
@@ -1031,7 +1029,7 @@ class DeepResearchAgent(AliasAgentBase):
] ]
else: # Use only intermediate memory to generate report else: # Use only intermediate memory to generate report
intermediate_memory = await self._get_intermediate_memory( intermediate_memory = await self._get_intermediate_memory(
remove_last_tool_use=True remove_last_tool_use=True,
) )
msgs = [ msgs = [
Msg( Msg(
@@ -1045,7 +1043,7 @@ class DeepResearchAgent(AliasAgentBase):
Msg( Msg(
self.name, self.name,
"Collect and polish all draft reports into a final report", "Collect and polish all draft reports into a final report",
"assistant" "assistant",
), ),
) )
try: try:
@@ -1104,8 +1102,10 @@ class DeepResearchAgent(AliasAgentBase):
name=self.name, name=self.name,
role="assistant", role="assistant",
content=[ content=[
TextBlock(type="text", TextBlock(
text=subtask_progress_summary, ) type="text",
text=subtask_progress_summary,
),
], ],
metadata=structure_response.model_dump(), metadata=structure_response.model_dump(),
) )
@@ -1122,7 +1122,7 @@ class DeepResearchAgent(AliasAgentBase):
The reflection about plan rephrasing and subtask decomposition. The reflection about plan rephrasing and subtask decomposition.
""" """
intermediate_memory = await self._get_intermediate_memory( 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"] reflect_sys_prompt = self.prompt_dict["reflect_sys_prompt"]
conversation_history = "" conversation_history = ""
@@ -1148,7 +1148,7 @@ class DeepResearchAgent(AliasAgentBase):
Msg( Msg(
self.name, self.name,
"Reflect on the failure of the action", "Reflect on the failure of the action",
"assistant" "assistant",
), ),
) )
reflection = await self.get_model_output( reflection = await self.get_model_output(
@@ -1193,7 +1193,7 @@ class DeepResearchAgent(AliasAgentBase):
save_msg = None save_msg = None
for msg in reversed(msgs): for msg in reversed(msgs):
for i, block in enumerate( for i, block in enumerate(
msg.get_content_blocks("tool_use") msg.get_content_blocks("tool_use"),
): ):
if block.get("name") == "reflect_failure": if block.get("name") == "reflect_failure":
save_msg = msg save_msg = msg
@@ -1286,8 +1286,8 @@ class DeepResearchAgent(AliasAgentBase):
TextBlock( TextBlock(
type="text", type="text",
text="All subtasks are done. " text="All subtasks are done. "
"Consider using generate_response to" "Consider using generate_response to"
"generate final report", "generate final report",
), ),
], ],
metadata={ metadata={
@@ -1296,7 +1296,6 @@ class DeepResearchAgent(AliasAgentBase):
is_last=True, is_last=True,
) )
# pylint: disable=invalid-overridden-method, unused-argument # pylint: disable=invalid-overridden-method, unused-argument
async def generate_response( # async def generate_response( #
self, self,
@@ -1333,15 +1332,17 @@ class DeepResearchAgent(AliasAgentBase):
detailed_report_path: ( detailed_report_path: (
f"Final detailed report generated by {self.name}" f"Final detailed report generated by {self.name}"
f"for '{str(self.user_query)}'" f"for '{str(self.user_query)}'"
) ),
}, },
) )
response_msg = Msg( response_msg = Msg(
name=self.name, name=self.name,
role="assistant", role="assistant",
content=[ content=[
TextBlock(type="text", TextBlock(
text=subtask_progress_summary,) type="text",
text=subtask_progress_summary,
),
], ],
metadata=structure_response.model_dump(), metadata=structure_response.model_dump(),
) )

View File

@@ -1,5 +1,7 @@
# -*- coding: utf-8 -*-
from pydantic import BaseModel, Field from pydantic import BaseModel, Field
class SubtasksDecomposition(BaseModel): class SubtasksDecomposition(BaseModel):
""" """
Model for structured subtask decomposition output in deep research. Model for structured subtask decomposition output in deep research.
@@ -7,20 +9,23 @@ class SubtasksDecomposition(BaseModel):
knowledge_gaps: str = Field( knowledge_gaps: str = Field(
description=( description=(
"A markdown checklist of essential knowledge gaps and optional " "A markdown checklist of essential knowledge gaps and "
"perspective-expansion gaps (flagged with (EXPANSION)), each on its own line. " "optional perspective-expansion gaps (flagged with "
"E.g. '- [ ] Detailed analysis of JD.com's ...\\n- [ ] (EXPANSION) X...'." "(EXPANSION)), each on its own line. E.g. '- [ ] Detailed "
"analysis of JD.com's ...\\n- [ ] (EXPANSION) X...'."
), ),
) )
working_plan: str = Field( working_plan: str = Field(
description=( description=(
"A logically ordered step-by-step working plan (3-5 steps), " "A logically ordered step-by-step working plan (3-5 steps),"
"each step starting with its number (1., 2., etc), including both " " each step starting with its number (1., 2., etc), "
"core and expansion steps. Expanded steps should be clearly marked " "including both core and expansion steps. Expanded steps "
"with (EXPANSION) and provide contextual or analytical depth.." "should be clearly marked with (EXPANSION) and provide "
"contextual or analytical depth.."
), ),
) )
class WebExtraction(BaseModel): class WebExtraction(BaseModel):
""" """
Model for structured follow-up web extraction output in deep research. Model for structured follow-up web extraction output in deep research.
@@ -42,6 +47,7 @@ class WebExtraction(BaseModel):
), ),
) )
class FollowupJudge(BaseModel): class FollowupJudge(BaseModel):
""" """
Model for structured follow-up decompose judging output in deep research. Model for structured follow-up decompose judging output in deep research.
@@ -49,15 +55,15 @@ class FollowupJudge(BaseModel):
reasoning: str = Field( reasoning: str = Field(
description=( description=(
"The reasoning for your decision, including a summary of evidence " "The reasoning for your decision, including a summary of "
"and logic for whether more information is needed. You should " "evidence and logic for whether more information is needed. "
"include specific gaps or opportunities if the current " "You should include specific gaps or opportunities if the "
"information is still insufficient" "current information is still insufficient"
), ),
) )
knowledge_gap_revision: str = Field( knowledge_gap_revision: str = Field(
"Revise the knowledge gaps in the current. " "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( to_further_explore: bool = Field(
description=( description=(
@@ -81,10 +87,11 @@ class ReflectFailure(BaseModel):
rephrase_subtask: dict = Field( rephrase_subtask: dict = Field(
description=( description=(
"Information about whether the problematic subtask needs to be " "Information about whether the problematic subtask needs to "
"rephrased due to a design flaw or misunderstanding. If rephrasing " "be rephrased due to a design flaw or misunderstanding. If "
"is needed, provide the modified working plan with only the inappropriate " "rephrasing is needed, provide the modified working plan with"
"subtask replaced by its improved version." " only the inappropriate subtask replaced by its improved "
"version."
), ),
json_schema_extra={ json_schema_extra={
"additionalProperties": { "additionalProperties": {
@@ -92,25 +99,30 @@ class ReflectFailure(BaseModel):
"properties": { "properties": {
"need_rephrase": { "need_rephrase": {
"type": "boolean", "type": "boolean",
"description": "Set to 'true' if the failed subtask " "description": (
"needs to be rephrased due to a design " "Set to 'true' if the failed subtask needs "
"flaw or misunderstanding; otherwise, 'false'.", "to be rephrased due to a design flaw or "
"misunderstanding; otherwise, 'false'."
),
}, },
"rephrased_plan": { "rephrased_plan": {
"type": "string", "type": "string",
"description": "The modified working plan with only the inappropriate " "description": (
"subtask replaced by its improved version. If no " "The modified working plan with only the "
"rephrasing is needed, provide an empty string.", "inappropriate subtask replaced by its "
"improved version. If no rephrasing is "
"needed, provide an empty string."
),
}, },
} },
} },
} },
) )
decompose_subtask: dict = Field( decompose_subtask: dict = Field(
description=( description=(
"Information about whether the problematic subtask should be further " "Information about whether the problematic subtask should be "
"decomposed. If decomposition is required, provide the failed subtask " "further decomposed. If decomposition is required, provide "
"and the reason for its decomposition." "the failed subtask and the reason for its decomposition."
), ),
json_schema_extra={ json_schema_extra={
"additionalProperties": { "additionalProperties": {
@@ -118,15 +130,19 @@ class ReflectFailure(BaseModel):
"properties": { "properties": {
"need_decompose": { "need_decompose": {
"type": "boolean", "type": "boolean",
"description": "Set to 'true' if the failed subtask should " "description": (
"be further decomposed; otherwise, 'false'.", "Set to 'true' if the failed subtask should "
"be further decomposed; otherwise, 'false'."
),
}, },
"failed_subtask": { "failed_subtask": {
"type": "string", "type": "string",
"description": "The failed subtask that needs to be further " "description": (
"decomposed.", "The failed subtask that needs to be further "
"decomposed."
),
}, },
} },
} },
} },
) )

View File

@@ -1,13 +1,13 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
"""The utilities for deep research agent""" """The utilities for deep research agent"""
import os
import json import json
from typing import Union, Sequence, Any, Type import os
from pydantic import BaseModel
import re import re
from typing import Any, Sequence, Type, Union
from pydantic import BaseModel
from agentscope.tool import Toolkit, ToolResponse from agentscope.tool import Toolkit, ToolResponse
from agentscope.agent import ReActAgent
TOOL_RESULTS_MAX_WORDS = 30000 TOOL_RESULTS_MAX_WORDS = 30000
@@ -24,12 +24,13 @@ def get_prompt_from_file(
prompt = f.read() prompt = f.read()
return prompt return prompt
async def count_by_words(sentence: str) -> float: async def count_by_words(sentence: str) -> float:
"""Count words of a sentence""" """Count words of a sentence"""
words = re.findall( words = re.findall(
r"\w+|[^\w\s]", r"\w+|[^\w\s]",
sentence, sentence,
re.UNICODE re.UNICODE,
) )
word_count = 0.0 word_count = 0.0

View File

@@ -4,19 +4,20 @@ Meta Planner agent class that can handle complicated tasks with
planning-execution pattern. planning-execution pattern.
""" """
# pylint: disable=W0613 # pylint: disable=W0613
import json
import os import os
import uuid import uuid
from functools import partial from functools import partial
from typing import Optional, Any, Literal, Callable
import json
from pathlib import Path from pathlib import Path
from typing import Any, Callable, Literal, Optional
from pydantic import BaseModel, Field 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.formatter import FormatterBase
from agentscope.memory import MemoryBase 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.agents import AliasAgentBase
from alias.agent.tools import AliasToolkit from alias.agent.tools import AliasToolkit
@@ -306,7 +307,6 @@ class MetaPlanner(AliasAgentBase):
generate_response_post_action_hook, generate_response_post_action_hook,
) )
def prepare_planner_tools( def prepare_planner_tools(
self, self,
planner_mode: Literal["disable", "enforced", "dynamic"], planner_mode: Literal["disable", "enforced", "dynamic"],

View File

@@ -43,7 +43,7 @@ class RoadmapManager(StateModule):
async def decompose_task_and_build_roadmap( async def decompose_task_and_build_roadmap(
self, self,
user_latest_input: str, user_latest_input: str, # pylint: disable=W0613
given_task_conclusion: str, given_task_conclusion: str,
detail_analysis_for_plan: str, detail_analysis_for_plan: str,
decomposed_subtasks: list[SubTaskSpecification], decomposed_subtasks: list[SubTaskSpecification],

View File

@@ -514,7 +514,7 @@ class WorkerManager(StateModule):
subtask_idx: int, subtask_idx: int,
selected_worker_name: str, selected_worker_name: str,
detailed_instruction: str, detailed_instruction: str,
reset_worker_memory: bool = False reset_worker_memory: bool = False,
) -> ToolResponse: ) -> ToolResponse:
""" """
Execute a worker agent for the next unfinished subtask. 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 memory can also be reset for better performance (but require
providing sufficient context information in providing sufficient context information in
`detailed_instruction`); 3) if a worker is stopped just because `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 and it's going to work on the sam task, DO NOT reset the
memory. memory.

View File

@@ -4,7 +4,7 @@ from .mock_message_models import (
BaseMessage, BaseMessage,
MessageState, MessageState,
MockMessage, MockMessage,
UserMessage UserMessage,
) )
__all__ = [ __all__ = [

View File

@@ -1,13 +1,15 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
"""Mock message models for local testing without api_server dependency.""" """Mock message models for local testing without api_server dependency."""
from enum import Enum
import uuid import uuid
from typing import Any, Optional, List from enum import Enum
from typing import Any, Optional
from pydantic import BaseModel from pydantic import BaseModel
class MessageState(str, Enum): class MessageState(str, Enum):
"""Message state enumeration.""" """Message state enumeration."""
RUNNING = "running" RUNNING = "running"
FINISHED = "finished" FINISHED = "finished"
FAILED = "failed" FAILED = "failed"
@@ -15,6 +17,7 @@ class MessageState(str, Enum):
class MessageType(str, Enum): class MessageType(str, Enum):
"""Message type enumeration.""" """Message type enumeration."""
RESPONSE = "response" RESPONSE = "response"
SUB_RESPONSE = "sub_response" SUB_RESPONSE = "sub_response"
THOUGHT = "thought" THOUGHT = "thought"
@@ -27,6 +30,7 @@ class MessageType(str, Enum):
class BaseMessage(BaseModel): class BaseMessage(BaseModel):
"""Base message class for local testing.""" """Base message class for local testing."""
role: str = "assistant" role: str = "assistant"
content: Any = "" content: Any = ""
name: Optional[str] = None name: Optional[str] = None
@@ -36,6 +40,7 @@ class BaseMessage(BaseModel):
class UserMessage(BaseMessage): class UserMessage(BaseMessage):
"""User message for local testing.""" """User message for local testing."""
role: str = "user" role: str = "user"
name: str = "User" name: str = "User"

View File

@@ -211,4 +211,3 @@ class MockSessionService:
async def get_state(self) -> dict: async def get_state(self) -> dict:
return self.state return self.state

View File

@@ -1,41 +1,27 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# pylint: disable=W0612,E0611,C2801 # pylint: disable=W0612,E0611,C2801
import os import os
from typing import Optional
from datetime import datetime
import traceback import traceback
from datetime import datetime
from loguru import logger from loguru import logger
from agentscope.message import Msg from agentscope.formatter import DashScopeChatFormatter
from agentscope.model import ( from agentscope.mcp import StdIOStatefulClient
OpenAIChatModel,
AnthropicChatModel,
DashScopeChatModel,
)
from agentscope.formatter import (
OpenAIChatFormatter,
AnthropicChatFormatter,
DashScopeChatFormatter,
)
from agentscope.memory import InMemoryMemory from agentscope.memory import InMemoryMemory
from agentscope.mcp import StdIOStatefulClient, StatefulClientBase from agentscope.message import Msg
from agentscope.token import OpenAITokenCounter from agentscope.model import DashScopeChatModel
from agentscope_runtime.sandbox.box.sandbox import Sandbox from agentscope_runtime.sandbox.box.sandbox import Sandbox
from alias.agent.agents import ( from alias.agent.agents import BrowserAgent, DeepResearchAgent, MetaPlanner
MetaPlanner,
DeepResearchAgent,
BrowserAgent,
)
from alias.agent.tools import AliasToolkit
from alias.agent.agents._planning_tools._worker_manager import share_tools 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.improved_tools import DashScopeMultiModalTools
from alias.agent.tools.toolkit_hooks import LongTextPostHook from alias.agent.tools.toolkit_hooks import LongTextPostHook
from alias.agent.utils.constants import BROWSER_AGENT_DESCRIPTION
# Open source version always uses mock services # Open source version always uses mock services
from alias.agent.mock import MockSessionService
SessionService = MockSessionService SessionService = MockSessionService
@@ -126,7 +112,7 @@ async def add_tools(
async def arun_agents( async def arun_agents(
session_service: SessionService, session_service: SessionService, # type: ignore[valid-type]
sandbox: Sandbox = None, sandbox: Sandbox = None,
enable_clarification: bool = True, enable_clarification: bool = True,
): ):
@@ -188,7 +174,7 @@ async def arun_agents(
async def test_deepresearch_agent( async def test_deepresearch_agent(
task_str: str, task_str: str,
session_service: SessionService, session_service: SessionService, # type: ignore[valid-type]
sandbox: Sandbox = None, sandbox: Sandbox = None,
): ):
instruction = Msg( instruction = Msg(
@@ -234,7 +220,7 @@ async def test_deepresearch_agent(
async def test_browseruse_agent( async def test_browseruse_agent(
task_str: str, task_str: str,
session_service: SessionService, session_service: SessionService, # type: ignore[valid-type]
sandbox: Sandbox = None, sandbox: Sandbox = None,
): ):
time_str = datetime.now().strftime("%Y%m%d%H%M%S") time_str = datetime.now().strftime("%Y%m%d%H%M%S")

View File

@@ -1,19 +1,16 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# pylint: disable=R1724 # pylint: disable=R1724
from typing import Optional, Callable, Any
import asyncio import asyncio
from typing import Any, Callable, Optional
from loguru import logger from loguru import logger
from agentscope.mcp import StatefulClientBase, MCPClientBase from agentscope.mcp import MCPClientBase, StatefulClientBase
from agentscope.tool import ( from agentscope.message import TextBlock, ToolUseBlock
Toolkit, from agentscope.tool import ToolResponse, Toolkit
ToolResponse,
)
from agentscope.message import ToolUseBlock, TextBlock
from agentscope_runtime.sandbox import FilesystemSandbox, BrowserSandbox
from alias.agent.tools.toolkit_hooks import ( from alias.agent.tools.toolkit_hooks import (
LongTextPostHook LongTextPostHook,
) )
from alias.agent.tools.improved_tools import ImprovedFileOperations from alias.agent.tools.improved_tools import ImprovedFileOperations
from alias.agent.tools.tool_blacklist import TOOL_BLACKLIST 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 from alias.runtime.alias_sandbox.alias_sandbox import AliasSandbox
FilesystemSandbox = AliasSandbox
class AliasToolkit(Toolkit): class AliasToolkit(Toolkit):
def __init__( # pylint: disable=W0102 def __init__( # pylint: disable=W0102
self, self,
@@ -44,9 +44,8 @@ class AliasToolkit(Toolkit):
# Get tools # Get tools
tools_schema = self.sandbox.list_tools() tools_schema = self.sandbox.list_tools()
for category, function_dicts in tools_schema.items(): for category, function_dicts in tools_schema.items():
if ( if (is_browser_toolkit and category == "playwright") or (
(is_browser_toolkit and category == "playwright") not is_browser_toolkit and category != "playwright"
or (not is_browser_toolkit and category != "playwright")
): ):
for _, function_json in function_dicts.items(): for _, function_json in function_dicts.items():
if function_json["name"] not in self.tool_blacklist: if function_json["name"] not in self.tool_blacklist:
@@ -66,7 +65,7 @@ class AliasToolkit(Toolkit):
def _add_io_function( def _add_io_function(
self, self,
json_schema: dict, json_schema: dict,
is_browser_tool: bool = False is_browser_tool: bool = False, # pylint: disable=W0613
) -> None: ) -> None:
tool_name = json_schema["name"] tool_name = json_schema["name"]
@@ -142,8 +141,9 @@ class AliasToolkit(Toolkit):
if tool_func.startswith(("read_file", "read_multiple_files")): if tool_func.startswith(("read_file", "read_multiple_files")):
self.tools[tool_func].postprocess_func = read_file_post_hook self.tools[tool_func].postprocess_func = read_file_post_hook
if tool_func.startswith("tavily"): if tool_func.startswith("tavily"):
self.tools[tool_func].postprocess_func = \ self.tools[
long_text_hook.truncate_and_save_response tool_func
].postprocess_func = long_text_hook.truncate_and_save_response
async def add_and_connet_mcp_client( async def add_and_connet_mcp_client(
self, self,
@@ -193,10 +193,10 @@ async def test_toolkit():
type="tool_use", type="tool_use",
id="", id="",
name="list_allowed_directories", name="list_allowed_directories",
input={} input={},
) ),
) )
print(f"Allow directory:") print("Allow directory:")
async for response in res: async for response in res:
print(response) print(response)
@@ -216,5 +216,6 @@ async def test_toolkit():
await toolkit.close_mcp_clients() await toolkit.close_mcp_clients()
if __name__ == "__main__": if __name__ == "__main__":
asyncio.run(test_toolkit()) asyncio.run(test_toolkit())

View File

@@ -2,8 +2,8 @@
""" """
Improved tools module for Alias agent toolkit. Improved tools module for Alias agent toolkit.
This module contains enhanced tool functions that provide additional functionality This module contains enhanced tool functions that provide additional
beyond the basic tools available in the standard toolkit. functionality beyond the basic tools available in the standard toolkit.
""" """
from .file_operations import ImprovedFileOperations from .file_operations import ImprovedFileOperations

View File

@@ -7,19 +7,20 @@ original read_file functionality and adds support for
reading specific line ranges from files. reading specific line ranges from files.
""" """
from typing import Optional
from loguru import logger
import asyncio import asyncio
import os import os
from typing import Optional
from loguru import logger
from agentscope.tool import ToolResponse
from agentscope.message import TextBlock from agentscope.message import TextBlock
from agentscope.tool import ToolResponse
from alias.agent.utils.constants import TMP_FILE_DIR from alias.agent.utils.constants import TMP_FILE_DIR
from alias.agent.tools.sandbox_util import ( from alias.agent.tools.sandbox_util import (
TEXT_EXTENSIONS, TEXT_EXTENSIONS,
create_or_edit_workspace_file, create_or_edit_workspace_file,
create_workspace_directory create_workspace_directory,
) )
from alias.runtime.alias_sandbox import AliasSandbox from alias.runtime.alias_sandbox import AliasSandbox
@@ -41,7 +42,7 @@ class ImprovedFileOperations:
"""init with sandbox""" """init with sandbox"""
self.sandbox = sandbox self.sandbox = sandbox
async def read_file( async def read_file( # pylint: disable=R0911,R0912
self, self,
file_path: str, file_path: str,
offset: int = 0, offset: int = 0,
@@ -96,13 +97,14 @@ class ImprovedFileOperations:
if self.sandbox is None: if self.sandbox is None:
return ToolResponse( return ToolResponse(
metadata={ metadata={
"success": False, "error": "No sandbox provided" "success": False,
"error": "No sandbox provided",
}, },
content=[ content=[
TextBlock( TextBlock(
type="text", type="text",
text="Error: No sandbox provided to " 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 # Call the original read_file tool
tool_res = self.sandbox.call_tool( tool_res = self.sandbox.call_tool(
name="read_file", name="read_file",
arguments=params arguments=params,
) )
elif file_extension in TO_MARKDOWN_SUPPORT_MAPPING: elif file_extension in TO_MARKDOWN_SUPPORT_MAPPING:
tool_res = _transfer_to_markdown_text(file_path, self.sandbox) tool_res = _transfer_to_markdown_text(file_path, self.sandbox)
@@ -130,9 +132,10 @@ class ImprovedFileOperations:
): ):
return ToolResponse( return ToolResponse(
metadata={ 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 ( elif (
tool_res.get("isError", True) tool_res.get("isError", True)
@@ -144,15 +147,15 @@ class ImprovedFileOperations:
TextBlock( TextBlock(
type="text", type="text",
text=f"Fail to read file on path {file_path}", text=f"Fail to read file on path {file_path}",
) ),
] ],
) )
# Get the text content from the first content block # Get the text content from the first content block
full_content = "" full_content = ""
for block in tool_res.get("content", []): for block in tool_res.get("content", []):
if isinstance(block, dict) and 'text' in block: if isinstance(block, dict) and "text" in block:
full_content += block['text'] + "\n" full_content += block["text"] + "\n"
# Split into lines # Split into lines
lines = full_content.splitlines(keepends=True) lines = full_content.splitlines(keepends=True)
@@ -171,7 +174,7 @@ class ImprovedFileOperations:
) )
# Handle offset and limit # 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) end_line = start_line + (limit or total_lines)
# Validate range # Validate range
@@ -182,7 +185,7 @@ class ImprovedFileOperations:
TextBlock( TextBlock(
type="text", type="text",
text=f"Error: Start line {offset} is " 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 # Extract the requested lines
selected_lines = lines[start_line:end_line] selected_lines = lines[start_line:end_line]
content = ''.join(selected_lines) content = "".join(selected_lines)
# Add summary information # Add summary information
summary = (f"Read lines {start_line}-{end_line} of " summary = (
f"{total_lines} total lines from '{file_path}'") f"Read lines {start_line}-{end_line} of "
f"{total_lines} total lines from '{file_path}'"
)
# save as markdown # save as markdown
return_content = [ return_content = [
@@ -208,18 +213,20 @@ class ImprovedFileOperations:
TextBlock( TextBlock(
type="text", type="text",
text=summary, text=summary,
) ),
] ]
if file_extension in TO_MARKDOWN_SUPPORT_MAPPING: if file_extension in TO_MARKDOWN_SUPPORT_MAPPING:
file_name_with_ext = os.path.basename(file_path) file_name_with_ext = os.path.basename(file_path)
filename_without_ext = os.path.splitext(file_name_with_ext)[0] filename_without_ext = os.path.splitext(file_name_with_ext)[0]
file_path = os.path.join( file_path = os.path.join(
TMP_FILE_DIR, TMP_FILE_DIR,
filename_without_ext + ".md" filename_without_ext + ".md",
) )
create_workspace_directory(self.sandbox, TMP_FILE_DIR) create_workspace_directory(self.sandbox, TMP_FILE_DIR)
create_or_edit_workspace_file( create_or_edit_workspace_file(
self.sandbox, file_path, full_content self.sandbox,
file_path,
full_content,
) )
return_content.append( return_content.append(
TextBlock( TextBlock(
@@ -229,8 +236,8 @@ class ImprovedFileOperations:
"The (full) file is converted as markdown file" "The (full) file is converted as markdown file"
" and saved completely at: " " and saved completely at: "
f"{file_path}" f"{file_path}"
) ),
) ),
) )
return ToolResponse( return ToolResponse(
@@ -257,7 +264,8 @@ class ImprovedFileOperations:
def _transfer_to_markdown_text( def _transfer_to_markdown_text(
file_path: str, sandbox: AliasSandbox = None file_path: str,
sandbox: AliasSandbox = None,
) -> dict: ) -> dict:
ext = os.path.splitext(file_path)[1].lower() ext = os.path.splitext(file_path)[1].lower()
@@ -268,46 +276,46 @@ def _transfer_to_markdown_text(
{ {
"type": "text", "type": "text",
"text": f"File extension '{ext}' not supported in " "text": f"File extension '{ext}' not supported in "
f"{TO_MARKDOWN_SUPPORT_MAPPING}." f"{TO_MARKDOWN_SUPPORT_MAPPING}.",
} },
] ],
} }
params = { params = {
"uri": "file:" + file_path "uri": "file:" + file_path,
} }
try: try:
res = sandbox.call_tool( result = sandbox.call_tool( # pylint: disable=W0621
name="convert_to_markdown", name="convert_to_markdown",
arguments=params arguments=params,
) )
content = res.get("content", []) content = result.get("content", [])
new_content = [] new_content = []
for i, block in enumerate(content): for i, _block in enumerate(content):
if content[i].get("text", "").startswith("Converted content:"): if content[i].get("text", "").startswith("Converted content:"):
continue continue
elif content[i].get("text", "").startswith("Output file:"): if content[i].get("text", "").startswith("Output file:"):
continue continue
else: new_content.append(result["content"][i])
new_content.append(res["content"][i])
res["content"] = new_content result["content"] = new_content
except Exception as e: except Exception as e:
res = { result = {
"isError": True, "isError": True,
"error": str(e) "error": str(e),
} }
return res return result
if __name__ == "__main__": if __name__ == "__main__":
from alias.agent.tools.sandbox_util import copy_local_file_to_workspace from alias.agent.tools.sandbox_util import copy_local_file_to_workspace
with AliasSandbox() as box: with AliasSandbox() as box:
res = copy_local_file_to_workspace( res = copy_local_file_to_workspace(
box, box,
"/Users/zitao.l/Downloads/22051_Which_LLM_Multi_Agent.pdf", "/Users/zitao.l/Downloads/22051_Which_LLM_Multi_Agent.pdf",
"/workspace/test.pdf" "/workspace/test.pdf",
) )
print(res) print(res)
toolset = ImprovedFileOperations(box) toolset = ImprovedFileOperations(box)

View File

@@ -95,7 +95,7 @@ class DashScopeMultiModalTools:
"content": [ "content": [
{ {
"text": "Transcript the content in the audio " "text": "Transcript the content in the audio "
"to text." "to text.",
}, },
], ],
}, },
@@ -253,6 +253,7 @@ class DashScopeMultiModalTools:
) )
except Exception as e: except Exception as e:
import traceback import traceback
print(traceback.format_exc()) print(traceback.format_exc())
return ToolResponse( return ToolResponse(
[ [
@@ -268,7 +269,7 @@ if __name__ == "__main__":
with AliasSandbox() as box: with AliasSandbox() as box:
tool_result = box.call_tool( tool_result = box.call_tool(
"run_shell_command", "run_shell_command",
arguments={"command": "apt update"} arguments={"command": "apt update"},
) )
print(tool_result) print(tool_result)
tool_result = box.call_tool( tool_result = box.call_tool(
@@ -299,7 +300,7 @@ if __name__ == "__main__":
) )
toolset = DashScopeMultiModalTools( toolset = DashScopeMultiModalTools(
sandbox=box, sandbox=box,
dashscope_api_key=os.getenv("DASHSCOPE_API_KEY", "") dashscope_api_key=os.getenv("DASHSCOPE_API_KEY", ""),
) )
result = toolset.dashscope_image_to_text( result = toolset.dashscope_image_to_text(
image_url=picture_path, image_url=picture_path,

View File

@@ -1,14 +1,17 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import os
from typing import Optional
import json
from pathlib import Path
import base64 import base64
from loguru import logger
import io import io
import json
import os
import tarfile 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 from alias.runtime.alias_sandbox import AliasSandbox
@@ -386,7 +389,7 @@ def copy_local_file_to_workspace(
{ {
"type": "text", "type": "text",
"text": "Copying file is not support sandbox " "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 # Create a tar archive in memory
tar_stream = io.BytesIO() 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 # Add file to tar archive
tar.add(local_path, arcname=os.path.basename(target_path)) tar.add(local_path, arcname=os.path.basename(target_path))
@@ -418,7 +422,6 @@ def copy_local_file_to_workspace(
} }
if __name__ == "__main__": if __name__ == "__main__":
with AliasSandbox() as box: with AliasSandbox() as box:
create_or_edit_workspace_file( create_or_edit_workspace_file(

View File

@@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
from .long_text_post_hook import LongTextPostHook from .long_text_post_hook import LongTextPostHook
from .read_file_post_hook import read_file_post_hook from .read_file_post_hook import read_file_post_hook

View File

@@ -10,7 +10,7 @@ from agentscope.message import ToolUseBlock, TextBlock
from alias.agent.utils.constants import TMP_FILE_DIR from alias.agent.utils.constants import TMP_FILE_DIR
from alias.agent.tools.sandbox_util import ( from alias.agent.tools.sandbox_util import (
create_or_edit_workspace_file, create_or_edit_workspace_file,
create_workspace_directory create_workspace_directory,
) )
@@ -18,7 +18,7 @@ class LongTextPostHook:
def __init__(self, sandbox): def __init__(self, sandbox):
self.sandbox = sandbox self.sandbox = sandbox
def truncate_and_save_response( def truncate_and_save_response( # pylint: disable=R1710
self, self,
tool_use: ToolUseBlock, # pylint: disable=W0613 tool_use: ToolUseBlock, # pylint: disable=W0613
tool_response: ToolResponse, tool_response: ToolResponse,
@@ -35,25 +35,24 @@ class LongTextPostHook:
tool_response: The tool response to potentially truncate. tool_response: The tool response to potentially truncate.
Note: Note:
The budget is set to approximately 80K tokens (8194 * 10 characters) The budget is set to approximately 80K tokens
to ensure responses remain manageable for the language model. (8194 * 10 characters) to ensure responses remain
manageable for the language model.
""" """
# Set budget to prevent overwhelming the model with too much content # Set budget to prevent overwhelming the model with too much content
budget = 8194 * 10 # Approximately 80K tokens of content budget = 8194 * 10 # Approximately 80K tokens of content
append_hint = ( append_hint = "\n\n[Content is too long and truncated....]"
"\n\n[Content is too long and truncated....]"
)
new_tool_response = ToolResponse( new_tool_response = ToolResponse(
id=tool_response.id, id=tool_response.id,
stream=tool_response.stream, stream=tool_response.stream,
is_last=tool_response.is_last, is_last=tool_response.is_last,
is_interrupted=tool_response.is_interrupted, is_interrupted=tool_response.is_interrupted,
content=[] content=[],
) )
if isinstance(tool_response.content, list): if isinstance(tool_response.content, list):
save_text_block = None save_text_block = None
for i, block in enumerate(tool_response.content): for _i, block in enumerate(tool_response.content):
if block["type"] == "text": if block["type"] == "text":
text = block["text"] text = block["text"]
text_len = len(text) text_len = len(text)
@@ -67,7 +66,7 @@ class LongTextPostHook:
tmp_file_name_prefix = tool_use.get("name", "") tmp_file_name_prefix = tool_use.get("name", "")
save_text_block = self._save_tmp_file( save_text_block = self._save_tmp_file(
tmp_file_name_prefix, tmp_file_name_prefix,
tool_response.content tool_response.content,
) )
new_tool_response.append = ( new_tool_response.append = (
text[:threshold] + append_hint text[:threshold] + append_hint
@@ -75,8 +74,8 @@ class LongTextPostHook:
new_tool_response.content.append( new_tool_response.content.append(
TextBlock( TextBlock(
type="text", type="text",
text=text[:threshold] + append_hint text=text[:threshold] + append_hint,
) ),
) )
else: else:
new_tool_response.content.append(block) new_tool_response.content.append(block)
@@ -91,29 +90,32 @@ class LongTextPostHook:
tmp_file_name_prefix = tool_use.get("name", "") tmp_file_name_prefix = tool_use.get("name", "")
save_text_block = self._save_tmp_file( save_text_block = self._save_tmp_file(
tmp_file_name_prefix, tmp_file_name_prefix,
tool_response.content tool_response.content,
) )
# Calculate truncation threshold (80% of proportional budget) # Calculate truncation threshold (80% of proportional budget)
threshold = int(budget / text_len * len(text) * 0.8) threshold = int(budget / text_len * len(text) * 0.8)
tool_response.content = ( tool_response.content = text[:threshold] + append_hint
text[:threshold] + append_hint
)
tool_response.content = [ tool_response.content = [
TextBlock(type="text", text=tool_response.content), TextBlock(type="text", text=tool_response.content),
save_text_block save_text_block,
] ]
return tool_response
return tool_response return tool_response
def _save_tmp_file(self, save_file_name_prefix: str, content: list | str): def _save_tmp_file(self, save_file_name_prefix: str, content: list | str):
create_workspace_directory(self.sandbox, TMP_FILE_DIR) create_workspace_directory(self.sandbox, TMP_FILE_DIR)
save_file_name = save_file_name_prefix + "-" + str( save_file_name = (
uuid.uuid4().hex[:8] save_file_name_prefix
+ "-"
+ str(
uuid.uuid4().hex[:8],
)
) )
file_path = os.path.join(TMP_FILE_DIR, save_file_name) file_path = os.path.join(TMP_FILE_DIR, save_file_name)
json_str = json.dumps(content, ensure_ascii=False, indent=2) json_str = json.dumps(content, ensure_ascii=False, indent=2)
wrapped = '\\n'.join( wrapped = "\\n".join(
[textwrap.fill(line, width=500) for line in json_str.split('\\n')]) [textwrap.fill(line, width=500) for line in json_str.split("\\n")],
)
create_or_edit_workspace_file( create_or_edit_workspace_file(
self.sandbox, self.sandbox,
file_path, file_path,
@@ -122,10 +124,8 @@ class LongTextPostHook:
return TextBlock( return TextBlock(
type="text", type="text",
text=f"Dump the complete long file at {file_path}. " text=f"Dump the complete long file at {file_path}. "
"Don't try to read the complete file directly. " "Don't try to read the complete file directly. "
"Use `grep -C 10 'YOUR_PATTERN' {file_path}` or " "Use `grep -C 10 'YOUR_PATTERN' {file_path}` or "
"other bash command to extract " "other bash command to extract "
"useful information.", "useful information.",
) )

View File

@@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
from agentscope.message import ToolUseBlock, TextBlock from agentscope.message import ToolUseBlock, TextBlock
from agentscope.tool import ToolResponse from agentscope.tool import ToolResponse
@@ -21,8 +22,8 @@ def _summarize_csv(text_block: TextBlock) -> None:
def read_file_post_hook( def read_file_post_hook(
tool_use: ToolUseBlock, tool_use: ToolUseBlock,
tool_response: ToolResponse, tool_response: ToolResponse,
) -> ToolResponse: ) -> ToolResponse:
""" """
Condense large CSV outputs after `read_file` or `read_multiple_files`. Condense large CSV outputs after `read_file` or `read_multiple_files`.

View File

@@ -6,20 +6,17 @@ Alias Command Line Interface
This module provides a terminal executable entry point This module provides a terminal executable entry point
for the Alias agent application. for the Alias agent application.
""" """
import json
from typing import Optional
import asyncio
import argparse import argparse
import sys import asyncio
import os import os
import sys
import traceback import traceback
import webbrowser import webbrowser
from typing import Optional
from loguru import logger from loguru import logger
from agentscope.agent import UserAgent, TerminalUserInput
from agentscope_runtime.sandbox import FilesystemSandbox, BrowserSandbox from agentscope.agent import TerminalUserInput, UserAgent
from agentscope_runtime.sandbox.box.sandbox import Sandbox
from agentscope.mcp import StdIOStatefulClient
from alias.agent.mock import MockSessionService, UserMessage from alias.agent.mock import MockSessionService, UserMessage
from alias.agent.run import ( from alias.agent.run import (
@@ -27,10 +24,8 @@ from alias.agent.run import (
test_browseruse_agent, test_browseruse_agent,
test_deepresearch_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.agent.tools.sandbox_util import copy_local_file_to_workspace
from alias.runtime.alias_sandbox.alias_sandbox import AliasSandbox
async def run_agent_task( async def run_agent_task(
@@ -52,15 +47,15 @@ async def run_agent_task(
# Create initial user message # Create initial user message
user_agent = UserAgent(name="User") user_agent = UserAgent(name="User")
user_agent.override_instance_input_method( user_agent.override_instance_input_method(
input_method = TerminalUserInput( input_method=TerminalUserInput(
input_hint = "User (Enter `exit` or `quit` to exit): " input_hint="User (Enter `exit` or `quit` to exit): ",
) ),
) )
# Run agent with sandbox context # Run agent with sandbox context
with AliasSandbox() as sandbox: with AliasSandbox() as sandbox:
logger.info( 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}") logger.info(f"Sandbox desktop URL: {sandbox.desktop_url}")
webbrowser.open(sandbox.desktop_url) webbrowser.open(sandbox.desktop_url)
@@ -68,7 +63,7 @@ async def run_agent_task(
if files: if files:
target_paths = [] target_paths = []
logger.info( 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: for file_path in files:
if not os.path.exists(file_path): if not os.path.exists(file_path):
@@ -88,8 +83,7 @@ async def run_agent_task(
if result.get("isError"): if result.get("isError"):
raise ValueError(f"Failed to upload {file_path}: {result}") 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")) target_paths.append(result.get("content", [])[0].get("text"))
@@ -104,14 +98,15 @@ async def run_agent_task(
mode=mode, mode=mode,
session=session, session=session,
user_agent=user_agent, user_agent=user_agent,
sandbox=sandbox sandbox=sandbox,
) )
async def _run_agent_loop( async def _run_agent_loop(
mode: str, mode: str,
session: MockSessionService, session: MockSessionService,
user_agent: UserAgent, user_agent: UserAgent,
sandbox: FilesystemSandbox, sandbox: AliasSandbox,
) -> None: ) -> None:
""" """
Execute the agent loop with follow-up interactions. Execute the agent loop with follow-up interactions.
@@ -133,8 +128,10 @@ async def _run_agent_loop(
sandbox=sandbox, sandbox=sandbox,
) )
break break
elif mode == "dr": if mode == "dr":
usr_msg = (await session.get_messages())[-1].message.get("content") usr_msg = (await session.get_messages())[-1].message.get(
"content",
)
logger.info(f"--> user_msg: {usr_msg}") logger.info(f"--> user_msg: {usr_msg}")
await test_deepresearch_agent( await test_deepresearch_agent(
usr_msg, usr_msg,
@@ -142,7 +139,7 @@ async def _run_agent_loop(
sandbox=sandbox, sandbox=sandbox,
) )
break break
elif mode == "all": if mode == "all":
await arun_agents( await arun_agents(
session, session,
sandbox=sandbox, sandbox=sandbox,
@@ -153,10 +150,10 @@ async def _run_agent_loop(
# Check for follow-up interaction # Check for follow-up interaction
follow_msg = await user_agent() follow_msg = await user_agent()
if ( if len(follow_msg.content) == 0 or follow_msg.content.lower() in [
len(follow_msg.content) == 0 "exit",
or follow_msg.content.lower() in ["exit", "quit"] "quit",
): ]:
logger.info("Exiting agent loop") logger.info("Exiting agent loop")
break break
@@ -176,7 +173,8 @@ def main():
) )
subparsers = parser.add_subparsers( subparsers = parser.add_subparsers(
dest="command", help="Available commands" dest="command",
help="Available commands",
) )
# Run command # Run command
@@ -219,7 +217,7 @@ def main():
type=str, type=str,
nargs="+", nargs="+",
help="Local file paths to upload to sandbox workspace " 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 # Version command
@@ -244,7 +242,7 @@ def main():
user_msg=args.task, user_msg=args.task,
mode=args.mode, mode=args.mode,
files=args.files if hasattr(args, "files") else None, files=args.files if hasattr(args, "files") else None,
) ),
) )
except KeyboardInterrupt: except KeyboardInterrupt:
logger.info("\nInterrupted by user") logger.info("\nInterrupted by user")

View File

@@ -1,3 +1,7 @@
# -*- coding: utf-8 -*- # -*- 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

View File

@@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
from .alias_sandbox import AliasSandbox from .alias_sandbox import AliasSandbox
__all__ = ['AliasSandbox'] __all__ = ["AliasSandbox"]