diff --git a/alias/README.md b/alias/README.md
index ea58955..d4ec513 100644
--- a/alias/README.md
+++ b/alias/README.md
@@ -288,7 +288,7 @@ The backend server will:
- Create the initial superuser account (if not exists)
- Start on `http://localhost:8000` (or the port specified in `.env`)
-Verify the server is running by visiting `http://localhost:8000/api/v1/monitor/health`.
+Verify the server is running by visiting `http://localhost:8000/api/v1/health`.
#### Start the Frontend
@@ -361,7 +361,7 @@ Once both servers are running:
- **Frontend UI**: Open `http://localhost:5173` in your browser
- **Backend API**: Available at `http://localhost:8000`
- **API Documentation**: Available at `http://localhost:8000/docs` (Swagger UI) or `http://localhost:8000/api/v1/openapi.json` (OpenAPI JSON)
-- **Health Check**: `http://localhost:8000/api/v1/monitor/health`
+- **Health Check**: `http://localhost:8000/api/v1/health`
#### Default Login Credentials
diff --git a/alias/README_ZH.md b/alias/README_ZH.md
index 58d1aa6..a3ff850 100644
--- a/alias/README_ZH.md
+++ b/alias/README_ZH.md
@@ -289,7 +289,7 @@ python -m uvicorn alias.server.main:app --host 0.0.0.0 --port 8000 --reload
- 创建初始超级用户账户(如果不存在)
- 在 `http://localhost:8000` 启动(或 `.env` 中指定的端口)
-通过访问 `http://localhost:8000/api/v1/monitor/health` 来验证服务器是否正在运行。
+通过访问 `http://localhost:8000/api/v1/health` 来验证服务器是否正在运行。
#### 启动前端
@@ -362,7 +362,7 @@ bash script/start_memory_service.sh
- **前端 UI**:在浏览器中打开 `http://localhost:5173`
- **后端 API**:可在 `http://localhost:8000` 访问
- **API 文档**:可在 `http://localhost:8000/docs` (Swagger UI) 或 `http://localhost:8000/api/v1/openapi.json` (OpenAPI JSON) 访问
-- **健康检查**:`http://localhost:8000/api/v1/monitor/health`
+- **健康检查**:`http://localhost:8000/api/v1/health`
#### 默认登录凭据
diff --git a/alias/pyproject.toml b/alias/pyproject.toml
index 75f29f7..e9a1a28 100644
--- a/alias/pyproject.toml
+++ b/alias/pyproject.toml
@@ -31,7 +31,7 @@ dependencies = [
"elasticsearch>=9.0.0",
"jinja2>=3.1.6",
"bcrypt==4.0.1",
- "agentscope[full]==1.0.7",
+ "agentscope[full]==1.0.11",
"tenacity>=8.5.0",
"apscheduler>=3.11.0",
"chardet>=5.2.0",
@@ -42,7 +42,7 @@ dependencies = [
"alembic>=1.16.1",
"openpyxl>=3.1.5",
"sentry-sdk[fastapi]===2.30.0",
- "agentscope-runtime==0.2.0",
+ "agentscope-runtime>=1.0.0",
"aiosqlite>=0.21.0",
"asyncpg>=0.30.0",
"itsdangerous>=2.2.0"
diff --git a/alias/src/alias/agent/agents/_alias_agent_base.py b/alias/src/alias/agent/agents/_alias_agent_base.py
index bfb32fd..25ca1df 100644
--- a/alias/src/alias/agent/agents/_alias_agent_base.py
+++ b/alias/src/alias/agent/agents/_alias_agent_base.py
@@ -11,7 +11,7 @@ from agentscope.agent import ReActAgent
from agentscope.model import ChatModelBase
from agentscope.formatter import FormatterBase
from agentscope.memory import MemoryBase, LongTermMemoryBase
-from agentscope.message import Msg, TextBlock, ToolUseBlock, ToolResultBlock
+from agentscope.message import Msg, ToolUseBlock, ToolResultBlock
from alias.agent.tools import AliasToolkit
from alias.agent.utils.constants import DEFAULT_PLANNER_NAME
@@ -23,24 +23,6 @@ from alias.agent.utils.constants import DEFAULT_BROWSER_WORKER_NAME
from alias.agent.utils.constants import MODEL_MAX_RETRIES
-def alias_agent_post_reply_hook(
- self: "AliasAgentBase",
- kwargs: dict[str, Any], # pylint: disable=unused-argument
- output: Any,
-):
- """
- This is a monkey patch to ensure that when the agent is interrupted in
- a tool call, the control returns to user
- """
- if (
- self.tool_call_interrupt_return
- and isinstance(output, Msg)
- and output.metadata
- and output.metadata.get("is_interrupted", False)
- ):
- raise asyncio.CancelledError()
-
-
class AliasAgentBase(ReActAgent):
def __init__(
self,
@@ -53,7 +35,6 @@ class AliasAgentBase(ReActAgent):
state_saving_dir: Optional[str] = None,
sys_prompt: Optional[str] = None,
max_iters: int = 10,
- tool_call_interrupt_return: bool = True,
long_term_memory: Optional[LongTermMemoryBase] = None,
long_term_memory_mode: Literal[
"agent_control",
@@ -77,14 +58,7 @@ class AliasAgentBase(ReActAgent):
self.message_sending_mapping = {}
self.state_saving_dir = state_saving_dir
self.agent_stop_function_names = [self.finish_function_name]
- self.tool_call_interrupt_return = tool_call_interrupt_return
- # interrupted if the
- self.register_instance_hook(
- "post_reply",
- "alias_agent_post_reply_hook",
- alias_agent_post_reply_hook,
- )
# for message output to backend
self.register_instance_hook(
"post_print",
@@ -92,7 +66,16 @@ class AliasAgentBase(ReActAgent):
alias_post_print_hook,
)
- async def _reasoning(self):
+ # register finish_function_name
+ if self.finish_function_name not in self.toolkit.tools:
+ self.toolkit.register_tool_function(
+ getattr(self, self.finish_function_name),
+ )
+
+ async def _reasoning(
+ self,
+ tool_choice: Literal["auto", "none", "required"] | None = None,
+ ):
"""Override _reasoning to add retry logic."""
# Call the parent class's _reasoning method directly to
@@ -109,7 +92,8 @@ class AliasAgentBase(ReActAgent):
if hasattr(original_method, "__wrapped__"):
# This is the wrapped version, get the original
original_method = original_method.__wrapped__
- return await original_method(self)
+
+ return await original_method(self, tool_choice=tool_choice)
for i in range(MODEL_MAX_RETRIES - 1):
try:
@@ -132,21 +116,18 @@ class AliasAgentBase(ReActAgent):
# final attempt
await call_parent_reasoning()
- async def _acting(self, tool_call: ToolUseBlock) -> Msg | None:
- """Perform the acting process.
-
- TODO: (part 2)
- this is just a monkey patch for AS when not support interruption
- during tool call; can be remove when AS framework updated
+ async def _acting(self, tool_call: ToolUseBlock) -> dict | None:
+ """Perform the acting process, and return the structured output if
+ it's generated and verified in the finish function call.
Args:
tool_call (`ToolUseBlock`):
The tool use block to be executed.
Returns:
- `Union[Msg, None]`:
- Return a message to the user if the `_finish_function` is
- called, otherwise return `None`.
+ `Union[dict, None]`:
+ Return the structured output if it's verified in the finish
+ function call.
"""
tool_res_msg = Msg(
@@ -165,7 +146,6 @@ class AliasAgentBase(ReActAgent):
# Execute the tool call
tool_res = await self.toolkit.call_tool_function(tool_call)
- response_msg = None
# Async generator handling
async for chunk in tool_res:
# Turn into a tool result block
@@ -191,28 +171,26 @@ class AliasAgentBase(ReActAgent):
tool_call["name"] != self.finish_function_name
or (
tool_call["name"] == self.finish_function_name
+ and chunk.metadata
and not chunk.metadata.get("success")
)
):
await self.print(tool_res_msg, chunk.is_last)
# Return message if generate_response is called successfully
- if tool_call[
- "name"
- ] in self.agent_stop_function_names and chunk.metadata.get(
- "success",
- True,
+ if (
+ tool_call["name"] in self.agent_stop_function_names
+ and chunk.metadata
+ and chunk.metadata.get(
+ "success",
+ True,
+ )
):
- response_msg = chunk.metadata.get("response_msg")
+ return chunk.metadata.get("structured_output")
elif chunk.is_interrupted:
- # TODO: monkey patch happens here
- response_msg = tool_res_msg
- if response_msg.metadata is None:
- response_msg.metadata = {"is_interrupted": True}
- else:
- response_msg.metadata["is_interrupted"] = True
+ raise asyncio.CancelledError
- return response_msg
+ return None
finally:
# Record the tool result message in the memory
await self.memory.add(tool_res_msg)
@@ -228,16 +206,16 @@ class AliasAgentBase(ReActAgent):
"""
response_msg = Msg(
self.name,
- content=[
- TextBlock(
- type="text",
- text="I got interrupted by the user. "
- "Pivot to handle the user's new request.",
- ),
- ],
- role="assistant",
- metadata={},
+ "I noticed that you have interrupted me. What can I "
+ "do for you?",
+ "assistant",
+ metadata={
+ # Expose this field to indicate the interruption
+ "_is_interrupted": True,
+ },
)
+
+ await self.print(response_msg, True)
await self.memory.add(response_msg)
# update and save agent states
diff --git a/alias/src/alias/agent/agents/_browser_agent.py b/alias/src/alias/agent/agents/_browser_agent.py
index 3463f4a..ad79454 100644
--- a/alias/src/alias/agent/agents/_browser_agent.py
+++ b/alias/src/alias/agent/agents/_browser_agent.py
@@ -10,7 +10,7 @@ import os
import json
import inspect
from functools import wraps
-from typing import Type, Optional, Any
+from typing import Type, Optional, Any, Literal
import asyncio
import copy
from loguru import logger
@@ -104,6 +104,10 @@ with open(
_BROWSER_AGENT_SUMMARIZE_TASK_PROMPT = f.read()
+class EmptyModel(BaseModel):
+ pass
+
+
async def browser_pre_reply_hook(
self,
kwargs: dict[str, Any],
@@ -150,6 +154,7 @@ async def browser_post_acting_hook(
] = self._filter_execution_text(return_json["text"])
if tool_call["name"] != self.finish_function_name or (
tool_call["name"] == self.finish_function_name
+ and tool_res_msg.metadata
and not tool_res_msg.metadata.get("success")
):
await self.print(tool_res_msg)
@@ -343,6 +348,7 @@ class BrowserAgent(AliasAgentBase):
or "gpt-5" in self.model.model_name
)
+ # pylint: disable=R0912,R0915
async def reply(
self,
msg: Msg | list[Msg] | None = None,
@@ -370,20 +376,38 @@ class BrowserAgent(AliasAgentBase):
else ""
)
+ if structured_model is None:
+ structured_model = EmptyModel
+
+ tool_choice: Literal["auto", "none", "required"] | None = None
+
self._required_structured_model = structured_model
# Record structured output model if provided
if structured_model:
+ # Register generate_response tool only when structured output
+ # is required
+ if self.finish_function_name not in self.toolkit.tools:
+ self.toolkit.register_tool_function(
+ getattr(self, self.finish_function_name),
+ )
+
self.toolkit.set_extended_model(
self.finish_function_name,
structured_model,
)
+ tool_choice = "required"
+ else:
+ # Remove generate_response tool if no structured output is required
+ self.toolkit.remove_tool_function(self.finish_function_name)
+
# The reasoning-acting loop
+ structured_output = None
reply_msg = None
for iter_n in range(self.max_iters):
self.iter_n = iter_n + 1
await self._summarize_mem()
- msg_reasoning = await self._pure_reasoning()
+ msg_reasoning = await self._pure_reasoning(tool_choice)
tool_calls = msg_reasoning.get_content_blocks("tool_use")
if tool_calls and tool_calls[0]["name"] == "browser_snapshot":
msg_reasoning = await self._reasoning_with_observation()
@@ -397,27 +421,69 @@ class BrowserAgent(AliasAgentBase):
# Parallel tool calls or not
if self.parallel_tool_calls:
- acting_responses = await asyncio.gather(*futures)
+ structured_outputs = await asyncio.gather(*futures)
else:
# Sequential tool calls
- acting_responses = [await _ for _ in futures]
+ structured_outputs = [await _ for _ in futures]
- # Find the first non-None replying message from the acting
- for acting_msg in acting_responses:
- reply_msg = reply_msg or acting_msg
+ # -------------- Check for exit condition --------------
+ # If structured output is still not satisfied
+ if self._required_structured_model:
+ # Remove None results
+ structured_outputs = [_ for _ in structured_outputs if _]
- if reply_msg:
+ msg_hint = None
+ # If the acting step generates structured outputs
+ if structured_outputs:
+ # Cache the structured output data
+ structured_output = structured_outputs[-1]
+
+ reply_msg = Msg(
+ self.name,
+ structured_output.get("subtask_progress_summary", ""),
+ "assistant",
+ metadata=structured_output,
+ )
+ break
+
+ if not msg_reasoning.has_content_blocks("tool_use"):
+ # If structured output is required but no tool call is
+ # made, remind the llm to go on the task
+ msg_hint = Msg(
+ "user",
+ "Structured output is "
+ f"required, go on to finish your task or call "
+ f"'{self.finish_function_name}' to generate the "
+ f"required structured output.",
+ "user",
+ )
+ await self._reasoning_hint_msgs.add(msg_hint)
+ # Require tool call in the next reasoning step
+ tool_choice = "required"
+
+ if msg_hint and self.print_hint_msg:
+ await self.print(msg_hint)
+
+ elif not msg_reasoning.has_content_blocks("tool_use"):
+ # Exit the loop when no structured output is required (or
+ # already satisfied) and only text response is generated
+ msg_reasoning.metadata = structured_output
+ reply_msg = msg_reasoning
break
- # When the maximum iterations are reached
- if not reply_msg:
- reply_msg = await self._summarizing()
- await self.memory.add(reply_msg)
+ # When the maximum iterations are reached
+ # and no reply message is generated
+ if reply_msg is None:
+ reply_msg = await self._summarizing()
+ reply_msg.metadata = structured_output
+ await self.memory.add(reply_msg)
+
return reply_msg
async def _pure_reasoning(
self,
+ tool_choice: Literal["auto", "none", "required"] | None = None,
) -> Msg:
msg = Msg(
"user",
@@ -433,12 +499,18 @@ class BrowserAgent(AliasAgentBase):
Msg("system", self.sys_prompt, "system"),
*await self.memory.get_memory(),
msg,
+ # The hint messages to guide the agent's behavior, maybe empty
+ *await self._reasoning_hint_msgs.get_memory(),
],
)
+ # Clear the hint messages after use
+ await self._reasoning_hint_msgs.clear()
+
res = await self.model(
prompt,
tools=self.no_screenshot_tool_list,
+ tool_choice=tool_choice,
)
# handle output from the model
interrupted_by_user = False
@@ -1260,15 +1332,6 @@ class BrowserAgent(AliasAgentBase):
subtask_progress_summary=summary_text,
generated_files={},
)
-
- response_msg = Msg(
- self.name,
- content=[
- TextBlock(type="text", text=summary_text),
- ],
- role="assistant",
- metadata=structure_response.model_dump(),
- )
return ToolResponse(
content=[
TextBlock(
@@ -1278,7 +1341,7 @@ class BrowserAgent(AliasAgentBase):
],
metadata={
"success": True,
- "response_msg": response_msg,
+ "structured_output": structure_response.model_dump(),
},
is_last=True,
)
@@ -1290,7 +1353,7 @@ class BrowserAgent(AliasAgentBase):
text=f"Here is a summary of current status:\n{summary_text}\nPlease continue.\n Following steps \n {finish_status}",
),
],
- metadata={"success": False, "response_msg": None},
+ metadata={"success": False, "structured_output": None},
is_last=True,
)
except Exception as e:
@@ -1310,8 +1373,8 @@ class BrowserAgent(AliasAgentBase):
sys_prompt = (
"You are an expert in task validation. "
"Your job is to determine if the agent has completed its task"
- " based on the provided summary. If finished, strictly reply "
- '"BROWSER_AGENT_TASK_FINISHED", otherwise return the remaining '
+ " based on the provided summary. If the summary is `NO_ANSWER`, this task is not over. If finished, strictly reply "
+ '"BROWSER_AGENT_TASK_FINISHED" and your reason, otherwise return the remaining '
"tasks or next steps."
)
# Extract user question from memory
diff --git a/alias/src/alias/agent/agents/_data_science_agent.py b/alias/src/alias/agent/agents/_data_science_agent.py
index 005e00c..8ac192d 100644
--- a/alias/src/alias/agent/agents/_data_science_agent.py
+++ b/alias/src/alias/agent/agents/_data_science_agent.py
@@ -4,10 +4,9 @@ import asyncio
import json
import os
from functools import partial
-from typing import List, Dict, Optional, Any, Type, cast
+from typing import List, Dict, Optional, Any, Type, cast, Literal
import uuid
-import shortuuid
from agentscope.formatter import FormatterBase
from agentscope.memory import MemoryBase
from agentscope.message import Msg, TextBlock, ToolUseBlock, ToolResultBlock
@@ -15,7 +14,7 @@ from agentscope.model import ChatModelBase
from agentscope.tool import ToolResponse
from agentscope.tracing import trace_reply
from loguru import logger
-from pydantic import BaseModel, ValidationError
+from pydantic import BaseModel, ValidationError, Field
from tenacity import retry, stop_after_attempt, wait_fixed
from alias.agent.agents import AliasAgentBase
@@ -37,6 +36,13 @@ from .ds_agent_utils import (
from .ds_agent_utils.ds_config import PROMPT_DS_BASE_PATH
+class DefaultStructuredResponse(BaseModel):
+ response: str = Field(
+ description="Just a placeholder. "
+ "Enter any character to trigger report generation",
+ )
+
+
class DataScienceAgent(AliasAgentBase):
def __init__(
self,
@@ -194,27 +200,140 @@ class DataScienceAgent(AliasAgentBase):
"pre_reply",
"files_filter_pre_reply_hook",
)
- return await super().reply(msg, structured_model)
+
+ if structured_model is None:
+ structured_model = DefaultStructuredResponse
+
+ # Record the input message(s) in the memory
+ await self.memory.add(msg)
+
+ # -------------- Retrieval process --------------
+ # Retrieve relevant records from the long-term memory if activated
+ await self._retrieve_from_long_term_memory(msg)
+ # Retrieve relevant documents from the knowledge base(s) if any
+ await self._retrieve_from_knowledge(msg)
+
+ # Control if LLM generates tool calls in each reasoning step
+ tool_choice: Literal["auto", "none", "required"] | None = None
+
+ # -------------- Structured output management --------------
+ self._required_structured_model = structured_model
+
+ # Register generate_response tool only when structured output
+ # is required
+ if self.finish_function_name not in self.toolkit.tools:
+ self.toolkit.register_tool_function(
+ getattr(self, self.finish_function_name),
+ )
+
+ # Set the structured output model
+ self.toolkit.set_extended_model(
+ self.finish_function_name,
+ structured_model,
+ )
+ tool_choice = "required"
+
+ # -------------- The reasoning-acting loop --------------
+ # Cache the structured output generated in the finish function call
+ structured_output = None
+ reply_msg = None
+ for _ in range(self.max_iters):
+ # -------------- The reasoning process --------------
+ msg_reasoning = await self._reasoning(tool_choice)
+
+ # -------------- The acting process --------------
+ futures = [
+ self._acting(tool_call)
+ for tool_call in msg_reasoning.get_content_blocks(
+ "tool_use",
+ )
+ ]
+ # Parallel tool calls or not
+ if self.parallel_tool_calls:
+ structured_outputs = await asyncio.gather(*futures)
+ else:
+ # Sequential tool calls
+ structured_outputs = [await _ for _ in futures]
+
+ # -------------- Check for exit condition --------------
+ # Remove None results
+ structured_outputs = [_ for _ in structured_outputs if _]
+
+ msg_hint = None
+ # If the acting step generates structured outputs
+ if structured_outputs:
+ # Cache the structured output data
+ structured_output = structured_outputs[-1]
+
+ reply_msg = Msg(
+ self.name,
+ structured_output.get("response"),
+ "assistant",
+ metadata=structured_output,
+ )
+ break
+
+ if not msg_reasoning.has_content_blocks("tool_use"):
+ # If structured output is required but no tool call is
+ # made, remind the llm to go on the task
+ msg_hint = Msg(
+ "user",
+ "Structured output is "
+ f"required, go on to finish your task or call "
+ f"'{self.finish_function_name}' to generate the "
+ f"required structured output.",
+ "user",
+ )
+ await self._reasoning_hint_msgs.add(msg_hint)
+
+ if msg_hint and self.print_hint_msg:
+ await self.print(msg_hint)
+
+ # When the maximum iterations are reached
+ # and no reply message is generated
+ if reply_msg is None:
+ reply_msg = await self._summarizing()
+ reply_msg.metadata = structured_output
+ await self.memory.add(reply_msg)
+
+ # Post-process the memory, long-term memory
+ if self._static_control:
+ await self.long_term_memory.record(
+ [
+ *([*msg] if isinstance(msg, list) else [msg]),
+ *await self.memory.get_memory(),
+ reply_msg,
+ ],
+ )
+
+ return reply_msg
@retry(stop=stop_after_attempt(10), wait=wait_fixed(5), reraise=True)
async def _reasoning(
self,
+ tool_choice: str = "required",
) -> Msg:
"""Perform the reasoning process."""
prompt = await self.formatter.format(
msgs=[
Msg("system", self.sys_prompt, "system"),
*await self.memory.get_memory(),
+ # The hint messages to guide the agent's behavior, maybe empty
+ *await self._reasoning_hint_msgs.get_memory(),
],
)
+ # Clear the hint messages after use
+ await self._reasoning_hint_msgs.clear()
+
try:
res = await self.model(
prompt,
tools=self.toolkit.get_json_schemas(),
+ tool_choice=tool_choice,
)
except Exception as e:
- print(str(e))
+ logger.debug("Error while calling model in _reasoning: {}", e)
# handle output from the model
interrupted_by_user = False
@@ -238,18 +357,6 @@ class DataScienceAgent(AliasAgentBase):
raise e from None
finally:
- if msg and not msg.has_content_blocks("tool_use"):
- # Turn plain text response into a tool call of the finish
- # function
- msg.content = [
- ToolUseBlock(
- id=shortuuid.uuid(),
- type="tool_use",
- name=self.think_function_name,
- input={"response": msg.get_text_content()},
- ),
- ]
-
# None will be ignored by the memory
await self.memory.add(msg)
@@ -279,17 +386,10 @@ class DataScienceAgent(AliasAgentBase):
# pylint: disable=invalid-overridden-method, unused-argument
async def generate_response(
self,
- response: str,
**kwargs: Any,
) -> ToolResponse:
- """Call this function when you have either completed the task
- or cannot continue due to insurmountable reasons.
- Provide in the `response` argument any information you believe
- the user needs to be informed of.
-
- Args:
- response (`str`):
- Your response to the user.
+ """
+ Generate required structured output by this function and return it
"""
memory = await self.memory.get_memory()
memory_log = "\n\n".join(
@@ -350,20 +450,15 @@ class DataScienceAgent(AliasAgentBase):
f"{self.detailed_report_path}."
)
- response_msg = Msg(
- self.name,
- response,
- "assistant",
- )
-
- await self.print(response_msg, True)
+ kwargs["response"] = response
+ structured_output = {}
# Prepare structured output
if self._required_structured_model:
try:
# Use the metadata field of the message to store the
# structured output
- response_msg.metadata = (
+ structured_output = (
self._required_structured_model.model_validate(
kwargs,
).model_dump()
@@ -379,10 +474,18 @@ class DataScienceAgent(AliasAgentBase):
],
metadata={
"success": False,
- "response_msg": None,
+ "structured_output": {},
},
)
+ await self.print(
+ Msg(
+ name=self.name,
+ content=response,
+ role="assistant",
+ ),
+ True,
+ )
return ToolResponse(
content=[
TextBlock(
@@ -392,7 +495,7 @@ class DataScienceAgent(AliasAgentBase):
],
metadata={
"success": True,
- "response_msg": response_msg,
+ "structured_output": structured_output,
},
is_last=True,
)
diff --git a/alias/src/alias/agent/agents/_deep_research_agent_v2.py b/alias/src/alias/agent/agents/_deep_research_agent_v2.py
index 232ad97..6e3cc75 100644
--- a/alias/src/alias/agent/agents/_deep_research_agent_v2.py
+++ b/alias/src/alias/agent/agents/_deep_research_agent_v2.py
@@ -107,6 +107,8 @@ class DeepResearchAgent(AliasAgentBase):
x,
x.get("task_type", "general"),
)
+ if x
+ else None
),
)
self.node_level_report = node_level_report
diff --git a/alias/src/alias/agent/agents/_meta_planner.py b/alias/src/alias/agent/agents/_meta_planner.py
index 095b66c..b2e65ed 100644
--- a/alias/src/alias/agent/agents/_meta_planner.py
+++ b/alias/src/alias/agent/agents/_meta_planner.py
@@ -245,12 +245,19 @@ class MetaPlanner(AliasAgentBase):
long_term_memory.tool_memory_retrieve,
)
+ # register finish_function_name
+ if not self.toolkit.tools.get(self.finish_function_name):
+ self.toolkit.register_tool_function(
+ self.finish_function_name,
+ )
+
+ response_func = self.toolkit.tools.get(self.finish_function_name)
+
# adjust ReActAgent parameters
if enable_clarification:
self._required_structured_model = (
MetaPlannerResponseWithClarification
)
- response_func = self.toolkit.tools.get(self.finish_function_name)
response_func.json_schema["function"][
"description"
] = response_func.json_schema["function"].get(
@@ -265,7 +272,6 @@ class MetaPlanner(AliasAgentBase):
self._required_structured_model = (
MetaPlannerResponseNoClarification
)
- response_func = self.toolkit.tools.get(self.finish_function_name)
response_func.json_schema["function"][
"description"
] = response_func.json_schema["function"].get(
diff --git a/alias/src/alias/agent/agents/_react_worker.py b/alias/src/alias/agent/agents/_react_worker.py
index 0b77dd0..2176138 100644
--- a/alias/src/alias/agent/agents/_react_worker.py
+++ b/alias/src/alias/agent/agents/_react_worker.py
@@ -1,15 +1,11 @@
# -*- coding: utf-8 -*-
# pylint: disable=C2801, W0611, W0212
-from typing import Optional, Any
+from typing import Optional, Any, Callable
+from functools import partial
from agentscope.model import ChatModelBase
from agentscope.formatter import FormatterBase
from agentscope.memory import MemoryBase
-from agentscope.tool import ToolResponse
-from agentscope.message import (
- Msg,
- TextBlock,
-)
from dotenv import load_dotenv
from alias.agent.agents import AliasAgentBase
@@ -50,21 +46,22 @@ class ReActWorker(AliasAgentBase):
state_saving_dir=state_saving_dir,
)
+ self._required_structured_model = WorkerResponse
+ self.reply: Callable = partial(
+ self.reply,
+ structured_model=self._required_structured_model,
+ )
+
self.max_iters: int = max(self.max_iters, WORKER_MAX_ITER)
def generate_response(
self,
- response: str = "",
- task_done: bool = True,
- subtask_progress_summary: str = "",
- generated_files: dict[str, str] = None,
- ) -> ToolResponse:
+ **kwargs,
+ ): # pylint: disable=useless-parent-delegation
"""
Generate a response summarizing the execution progress of the
given subtask.
Args:
- response (str):
- The response text (compatible with AgentScope finish function).
task_done (bool):
REQUIRED! Whether the subtask was done or not.
subtask_progress_summary (str):
@@ -76,37 +73,4 @@ class ReActWorker(AliasAgentBase):
paths of generated files (e.g. '/FULL/PATH/OF/FILE_1.md') and
the values are short descriptions about the generated files.
"""
- if generated_files is None:
- generated_files = {}
-
- # If only response is provided,
- # use it as subtask_progress_summary
- if not subtask_progress_summary and response:
- subtask_progress_summary = response
-
- structure_response = WorkerResponse(
- task_done=task_done,
- subtask_progress_summary=subtask_progress_summary,
- generated_files=generated_files,
- )
- response_msg = Msg(
- self.name,
- content=[
- TextBlock(type="text", text=subtask_progress_summary),
- ],
- role="assistant",
- metadata=structure_response.model_dump(),
- )
- return ToolResponse(
- content=[
- TextBlock(
- type="text",
- text="Successfully generated response.",
- ),
- ],
- metadata={
- "success": True,
- "response_msg": response_msg,
- },
- is_last=True,
- )
+ return super().generate_response(**kwargs)
diff --git a/alias/src/alias/agent/agents/common_agent_utils/_common_agent_hooks.py b/alias/src/alias/agent/agents/common_agent_utils/_common_agent_hooks.py
index b07f395..b1f2de9 100644
--- a/alias/src/alias/agent/agents/common_agent_utils/_common_agent_hooks.py
+++ b/alias/src/alias/agent/agents/common_agent_utils/_common_agent_hooks.py
@@ -106,7 +106,7 @@ async def save_post_reasoning_state(
async def save_post_action_state(
self: AliasAgentBase,
action_input: dict[str, Any], # pylint: disable=W0613
- tool_output: Optional[Msg], # pylint: disable=W0613
+ tool_output: Optional[dict], # pylint: disable=W0613
) -> None:
"""Hook func for save state after action step"""
await _update_and_save_state_with_session(self)
@@ -115,23 +115,23 @@ async def save_post_action_state(
async def generate_response_post_action_hook(
self: AliasAgentBase,
action_input: dict[str, Any], # pylint: disable=W0613
- tool_output: Optional[Msg], # pylint: disable=W0613
+ tool_output: Optional[dict], # pylint: disable=W0613
) -> None:
"""Hook func for printing clarification"""
if not (hasattr(self, "session_service") and self.session_service):
return
- if isinstance(tool_output, Msg):
- if tool_output.metadata and tool_output.metadata.get(
+ if isinstance(tool_output, dict):
+ if tool_output.get(
"require_clarification",
False,
):
clarification_dict = {
- "clarification_question": tool_output.metadata.get(
+ "clarification_question": tool_output.get(
"clarification_question",
"",
),
- "clarification_options": tool_output.metadata.get(
+ "clarification_options": tool_output.get(
"clarification_options",
"",
),
@@ -144,7 +144,7 @@ async def generate_response_post_action_hook(
indent=4,
),
role="assistant",
- metadata=tool_output.metadata,
+ metadata=tool_output,
)
await self.print(msg, last=True)
diff --git a/alias/src/alias/agent/agents/common_agent_utils/_common_models.py b/alias/src/alias/agent/agents/common_agent_utils/_common_models.py
index 5cfc265..01a8db2 100644
--- a/alias/src/alias/agent/agents/common_agent_utils/_common_models.py
+++ b/alias/src/alias/agent/agents/common_agent_utils/_common_models.py
@@ -76,14 +76,15 @@ class WorkerResponse(BaseModel):
"""
subtask_progress_summary: str = Field(
- ...,
+ default="",
description=WORKER_PROGRESS_SUMMARY,
)
generated_files: dict = Field(
- ...,
+ default=dict,
description=WORKER_FILE_COLLECTION_INSTRUCTION,
)
task_done: bool = Field(
- ...,
- description="Whether task is done or it require addition effort",
+ default=True,
+ description="Whether the task is done or "
+ "it requires additional effort",
)
diff --git a/alias/src/alias/agent/agents/ds_agent_utils/__init__.py b/alias/src/alias/agent/agents/ds_agent_utils/__init__.py
index 7db8eff..1c40afc 100644
--- a/alias/src/alias/agent/agents/ds_agent_utils/__init__.py
+++ b/alias/src/alias/agent/agents/ds_agent_utils/__init__.py
@@ -9,7 +9,9 @@ from .utils import (
)
from .ds_toolkit import add_ds_specific_tool
from .prompt_selector import LLMPromptSelector
-from .agent_hook import files_filter_pre_reply_hook
+from .agent_hook import (
+ files_filter_pre_reply_hook,
+)
__all__ = [
"ReportGenerator",
diff --git a/alias/src/alias/agent/agents/meta_planner_utils/_meta_planner_hooks.py b/alias/src/alias/agent/agents/meta_planner_utils/_meta_planner_hooks.py
index 63b2f01..baa61f8 100644
--- a/alias/src/alias/agent/agents/meta_planner_utils/_meta_planner_hooks.py
+++ b/alias/src/alias/agent/agents/meta_planner_utils/_meta_planner_hooks.py
@@ -128,7 +128,7 @@ async def _planner_save_plan_with_session(
async def planner_save_post_action_state(
self: MetaPlanner,
action_input: dict[str, Any], # pylint: disable=W0613
- tool_output: Optional[Msg], # pylint: disable=W0613
+ tool_output: Optional[dict], # pylint: disable=W0613
) -> None:
"""Hook func for save state after action step"""
await _update_and_save_state_with_session(self)
diff --git a/alias/src/alias/server/api/v1/__init__.py b/alias/src/alias/server/api/v1/__init__.py
index 3c7472f..0dce93f 100644
--- a/alias/src/alias/server/api/v1/__init__.py
+++ b/alias/src/alias/server/api/v1/__init__.py
@@ -10,6 +10,7 @@ from alias.server.api.v1.file import router as file_router
from alias.server.api.v1.inner import router as inner_router
from alias.server.api.v1.share import router as share_router
from alias.server.api.v1.user import router as user_router
+from alias.server.api.v1.monitor import router as monitor_router
router = APIRouter()
router.include_router(user_router)
@@ -19,3 +20,4 @@ router.include_router(chat_router)
router.include_router(file_router)
router.include_router(inner_router)
router.include_router(share_router)
+router.include_router(monitor_router)
diff --git a/alias/src/alias/server/services/conversation_service.py b/alias/src/alias/server/services/conversation_service.py
index effed83..6a72160 100644
--- a/alias/src/alias/server/services/conversation_service.py
+++ b/alias/src/alias/server/services/conversation_service.py
@@ -80,6 +80,7 @@ class ConversationService(BaseService[Conversation]):
base_url=settings.SANDBOX_URL,
bearer_token=settings.SANDBOX_BEARER_TOKEN,
)
+ sandbox.__enter__()
conversation_data = Conversation(
user_id=user_id,