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

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

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

View File

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

View File

@@ -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").

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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."
),
},
}
}
}
},
},
},
)

View File

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

View File

@@ -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"],

View File

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

View File

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

View File

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

View File

@@ -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] = []
files: list[Any] = []

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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",
]
]

View File

@@ -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.",
)

View File

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

View File

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

View File

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

View File

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