refactor: update agentscope[full] to 1.0.11 (#92)

This commit is contained in:
MeiXin Chen
2026-01-09 17:03:35 +08:00
committed by GitHub
parent fe4b7f53b0
commit 72991cdddb
15 changed files with 312 additions and 190 deletions

View File

@@ -288,7 +288,7 @@ The backend server will:
- Create the initial superuser account (if not exists) - Create the initial superuser account (if not exists)
- Start on `http://localhost:8000` (or the port specified in `.env`) - 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 #### Start the Frontend
@@ -361,7 +361,7 @@ Once both servers are running:
- **Frontend UI**: Open `http://localhost:5173` in your browser - **Frontend UI**: Open `http://localhost:5173` in your browser
- **Backend API**: Available at `http://localhost:8000` - **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) - **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 #### Default Login Credentials

View File

@@ -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` 启动(或 `.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` - **前端 UI**:在浏览器中打开 `http://localhost:5173`
- **后端 API**:可在 `http://localhost:8000` 访问 - **后端 API**:可在 `http://localhost:8000` 访问
- **API 文档**:可在 `http://localhost:8000/docs` (Swagger UI) 或 `http://localhost:8000/api/v1/openapi.json` (OpenAPI JSON) 访问 - **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`
#### 默认登录凭据 #### 默认登录凭据

View File

@@ -31,7 +31,7 @@ dependencies = [
"elasticsearch>=9.0.0", "elasticsearch>=9.0.0",
"jinja2>=3.1.6", "jinja2>=3.1.6",
"bcrypt==4.0.1", "bcrypt==4.0.1",
"agentscope[full]==1.0.7", "agentscope[full]==1.0.11",
"tenacity>=8.5.0", "tenacity>=8.5.0",
"apscheduler>=3.11.0", "apscheduler>=3.11.0",
"chardet>=5.2.0", "chardet>=5.2.0",
@@ -42,7 +42,7 @@ dependencies = [
"alembic>=1.16.1", "alembic>=1.16.1",
"openpyxl>=3.1.5", "openpyxl>=3.1.5",
"sentry-sdk[fastapi]===2.30.0", "sentry-sdk[fastapi]===2.30.0",
"agentscope-runtime==0.2.0", "agentscope-runtime>=1.0.0",
"aiosqlite>=0.21.0", "aiosqlite>=0.21.0",
"asyncpg>=0.30.0", "asyncpg>=0.30.0",
"itsdangerous>=2.2.0" "itsdangerous>=2.2.0"

View File

@@ -11,7 +11,7 @@ from agentscope.agent import ReActAgent
from agentscope.model import ChatModelBase from agentscope.model import ChatModelBase
from agentscope.formatter import FormatterBase from agentscope.formatter import FormatterBase
from agentscope.memory import MemoryBase, LongTermMemoryBase 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.tools import AliasToolkit
from alias.agent.utils.constants import DEFAULT_PLANNER_NAME 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 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): class AliasAgentBase(ReActAgent):
def __init__( def __init__(
self, self,
@@ -53,7 +35,6 @@ class AliasAgentBase(ReActAgent):
state_saving_dir: Optional[str] = None, state_saving_dir: Optional[str] = None,
sys_prompt: Optional[str] = None, sys_prompt: Optional[str] = None,
max_iters: int = 10, max_iters: int = 10,
tool_call_interrupt_return: bool = True,
long_term_memory: Optional[LongTermMemoryBase] = None, long_term_memory: Optional[LongTermMemoryBase] = None,
long_term_memory_mode: Literal[ long_term_memory_mode: Literal[
"agent_control", "agent_control",
@@ -77,14 +58,7 @@ class AliasAgentBase(ReActAgent):
self.message_sending_mapping = {} self.message_sending_mapping = {}
self.state_saving_dir = state_saving_dir self.state_saving_dir = state_saving_dir
self.agent_stop_function_names = [self.finish_function_name] 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 # for message output to backend
self.register_instance_hook( self.register_instance_hook(
"post_print", "post_print",
@@ -92,7 +66,16 @@ class AliasAgentBase(ReActAgent):
alias_post_print_hook, 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.""" """Override _reasoning to add retry logic."""
# Call the parent class's _reasoning method directly to # Call the parent class's _reasoning method directly to
@@ -109,7 +92,8 @@ class AliasAgentBase(ReActAgent):
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, tool_choice=tool_choice)
for i in range(MODEL_MAX_RETRIES - 1): for i in range(MODEL_MAX_RETRIES - 1):
try: try:
@@ -132,21 +116,18 @@ class AliasAgentBase(ReActAgent):
# final attempt # final attempt
await call_parent_reasoning() await call_parent_reasoning()
async def _acting(self, tool_call: ToolUseBlock) -> Msg | None: async def _acting(self, tool_call: ToolUseBlock) -> dict | None:
"""Perform the acting process. """Perform the acting process, and return the structured output if
it's generated and verified in the finish function call.
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
Args: Args:
tool_call (`ToolUseBlock`): tool_call (`ToolUseBlock`):
The tool use block to be executed. The tool use block to be executed.
Returns: Returns:
`Union[Msg, None]`: `Union[dict, None]`:
Return a message to the user if the `_finish_function` is Return the structured output if it's verified in the finish
called, otherwise return `None`. function call.
""" """
tool_res_msg = Msg( tool_res_msg = Msg(
@@ -165,7 +146,6 @@ class AliasAgentBase(ReActAgent):
# Execute the tool call # Execute the tool call
tool_res = await self.toolkit.call_tool_function(tool_call) tool_res = await self.toolkit.call_tool_function(tool_call)
response_msg = None
# Async generator handling # Async generator handling
async for chunk in tool_res: async for chunk in tool_res:
# Turn into a tool result block # Turn into a tool result block
@@ -191,28 +171,26 @@ class AliasAgentBase(ReActAgent):
tool_call["name"] != self.finish_function_name tool_call["name"] != self.finish_function_name
or ( or (
tool_call["name"] == self.finish_function_name tool_call["name"] == self.finish_function_name
and chunk.metadata
and not chunk.metadata.get("success") and not chunk.metadata.get("success")
) )
): ):
await self.print(tool_res_msg, chunk.is_last) await self.print(tool_res_msg, chunk.is_last)
# Return message if generate_response is called successfully # Return message if generate_response is called successfully
if tool_call[ if (
"name" tool_call["name"] in self.agent_stop_function_names
] in self.agent_stop_function_names and chunk.metadata.get( and chunk.metadata
"success", and chunk.metadata.get(
True, "success",
True,
)
): ):
response_msg = chunk.metadata.get("response_msg") return chunk.metadata.get("structured_output")
elif chunk.is_interrupted: elif chunk.is_interrupted:
# TODO: monkey patch happens here raise asyncio.CancelledError
response_msg = tool_res_msg
if response_msg.metadata is None:
response_msg.metadata = {"is_interrupted": True}
else:
response_msg.metadata["is_interrupted"] = True
return response_msg return None
finally: finally:
# Record the tool result message in the memory # Record the tool result message in the memory
await self.memory.add(tool_res_msg) await self.memory.add(tool_res_msg)
@@ -228,16 +206,16 @@ class AliasAgentBase(ReActAgent):
""" """
response_msg = Msg( response_msg = Msg(
self.name, self.name,
content=[ "I noticed that you have interrupted me. What can I "
TextBlock( "do for you?",
type="text", "assistant",
text="I got interrupted by the user. " metadata={
"Pivot to handle the user's new request.", # Expose this field to indicate the interruption
), "_is_interrupted": True,
], },
role="assistant",
metadata={},
) )
await self.print(response_msg, True)
await self.memory.add(response_msg) await self.memory.add(response_msg)
# update and save agent states # update and save agent states

View File

@@ -10,7 +10,7 @@ import os
import json import json
import inspect import inspect
from functools import wraps from functools import wraps
from typing import Type, Optional, Any from typing import Type, Optional, Any, Literal
import asyncio import asyncio
import copy import copy
from loguru import logger from loguru import logger
@@ -104,6 +104,10 @@ with open(
_BROWSER_AGENT_SUMMARIZE_TASK_PROMPT = f.read() _BROWSER_AGENT_SUMMARIZE_TASK_PROMPT = f.read()
class EmptyModel(BaseModel):
pass
async def browser_pre_reply_hook( async def browser_pre_reply_hook(
self, self,
kwargs: dict[str, Any], kwargs: dict[str, Any],
@@ -150,6 +154,7 @@ async def browser_post_acting_hook(
] = self._filter_execution_text(return_json["text"]) ] = self._filter_execution_text(return_json["text"])
if tool_call["name"] != self.finish_function_name or ( if tool_call["name"] != self.finish_function_name or (
tool_call["name"] == self.finish_function_name tool_call["name"] == self.finish_function_name
and tool_res_msg.metadata
and not tool_res_msg.metadata.get("success") and not tool_res_msg.metadata.get("success")
): ):
await self.print(tool_res_msg) await self.print(tool_res_msg)
@@ -343,6 +348,7 @@ class BrowserAgent(AliasAgentBase):
or "gpt-5" in self.model.model_name or "gpt-5" in self.model.model_name
) )
# pylint: disable=R0912,R0915
async def reply( async def reply(
self, self,
msg: Msg | list[Msg] | None = None, msg: Msg | list[Msg] | None = None,
@@ -370,20 +376,38 @@ class BrowserAgent(AliasAgentBase):
else "" else ""
) )
if structured_model is None:
structured_model = EmptyModel
tool_choice: Literal["auto", "none", "required"] | None = None
self._required_structured_model = structured_model self._required_structured_model = structured_model
# Record structured output model if provided # Record structured output model if provided
if structured_model: 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.toolkit.set_extended_model(
self.finish_function_name, self.finish_function_name,
structured_model, 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 # The reasoning-acting loop
structured_output = None
reply_msg = None reply_msg = None
for iter_n in range(self.max_iters): for iter_n in range(self.max_iters):
self.iter_n = iter_n + 1 self.iter_n = iter_n + 1
await self._summarize_mem() 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") tool_calls = msg_reasoning.get_content_blocks("tool_use")
if tool_calls and tool_calls[0]["name"] == "browser_snapshot": if tool_calls and tool_calls[0]["name"] == "browser_snapshot":
msg_reasoning = await self._reasoning_with_observation() msg_reasoning = await self._reasoning_with_observation()
@@ -397,27 +421,69 @@ class BrowserAgent(AliasAgentBase):
# Parallel tool calls or not # Parallel tool calls or not
if self.parallel_tool_calls: if self.parallel_tool_calls:
acting_responses = await asyncio.gather(*futures) structured_outputs = await asyncio.gather(*futures)
else: else:
# Sequential tool calls # 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 # -------------- Check for exit condition --------------
for acting_msg in acting_responses: # If structured output is still not satisfied
reply_msg = reply_msg or acting_msg 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",
"<system-hint>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.</system-hint>",
"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 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 return reply_msg
async def _pure_reasoning( async def _pure_reasoning(
self, self,
tool_choice: Literal["auto", "none", "required"] | None = None,
) -> Msg: ) -> Msg:
msg = Msg( msg = Msg(
"user", "user",
@@ -433,12 +499,18 @@ class BrowserAgent(AliasAgentBase):
Msg("system", self.sys_prompt, "system"), Msg("system", self.sys_prompt, "system"),
*await self.memory.get_memory(), *await self.memory.get_memory(),
msg, 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( res = await self.model(
prompt, prompt,
tools=self.no_screenshot_tool_list, tools=self.no_screenshot_tool_list,
tool_choice=tool_choice,
) )
# handle output from the model # handle output from the model
interrupted_by_user = False interrupted_by_user = False
@@ -1260,15 +1332,6 @@ class BrowserAgent(AliasAgentBase):
subtask_progress_summary=summary_text, subtask_progress_summary=summary_text,
generated_files={}, generated_files={},
) )
response_msg = Msg(
self.name,
content=[
TextBlock(type="text", text=summary_text),
],
role="assistant",
metadata=structure_response.model_dump(),
)
return ToolResponse( return ToolResponse(
content=[ content=[
TextBlock( TextBlock(
@@ -1278,7 +1341,7 @@ class BrowserAgent(AliasAgentBase):
], ],
metadata={ metadata={
"success": True, "success": True,
"response_msg": response_msg, "structured_output": structure_response.model_dump(),
}, },
is_last=True, 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}", 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, is_last=True,
) )
except Exception as e: except Exception as e:
@@ -1310,8 +1373,8 @@ class BrowserAgent(AliasAgentBase):
sys_prompt = ( sys_prompt = (
"You are an expert in task validation. " "You are an expert in task validation. "
"Your job is to determine if the agent has completed its task" "Your job is to determine if the agent has completed its task"
" based on the provided summary. If finished, strictly reply " " based on the provided summary. If the summary is `NO_ANSWER`, this task is not over. If finished, strictly reply "
'"BROWSER_AGENT_TASK_FINISHED", otherwise return the remaining ' '"BROWSER_AGENT_TASK_FINISHED" and your reason, otherwise return the remaining '
"tasks or next steps." "tasks or next steps."
) )
# Extract user question from memory # Extract user question from memory

View File

@@ -4,10 +4,9 @@ import asyncio
import json import json
import os import os
from functools import partial 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 uuid
import shortuuid
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, ToolUseBlock, ToolResultBlock from agentscope.message import Msg, TextBlock, ToolUseBlock, ToolResultBlock
@@ -15,7 +14,7 @@ from agentscope.model import ChatModelBase
from agentscope.tool import ToolResponse from agentscope.tool import ToolResponse
from agentscope.tracing import trace_reply from agentscope.tracing import trace_reply
from loguru import logger 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 tenacity import retry, stop_after_attempt, wait_fixed
from alias.agent.agents import AliasAgentBase 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 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): class DataScienceAgent(AliasAgentBase):
def __init__( def __init__(
self, self,
@@ -194,27 +200,140 @@ class DataScienceAgent(AliasAgentBase):
"pre_reply", "pre_reply",
"files_filter_pre_reply_hook", "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",
"<system-hint>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.</system-hint>",
"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) @retry(stop=stop_after_attempt(10), wait=wait_fixed(5), reraise=True)
async def _reasoning( async def _reasoning(
self, self,
tool_choice: str = "required",
) -> Msg: ) -> Msg:
"""Perform the reasoning process.""" """Perform the reasoning process."""
prompt = await self.formatter.format( prompt = await self.formatter.format(
msgs=[ msgs=[
Msg("system", self.sys_prompt, "system"), Msg("system", self.sys_prompt, "system"),
*await self.memory.get_memory(), *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: try:
res = await self.model( res = await self.model(
prompt, prompt,
tools=self.toolkit.get_json_schemas(), tools=self.toolkit.get_json_schemas(),
tool_choice=tool_choice,
) )
except Exception as e: except Exception as e:
print(str(e)) logger.debug("Error while calling model in _reasoning: {}", e)
# handle output from the model # handle output from the model
interrupted_by_user = False interrupted_by_user = False
@@ -238,18 +357,6 @@ class DataScienceAgent(AliasAgentBase):
raise e from None raise e from None
finally: 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 # None will be ignored by the memory
await self.memory.add(msg) await self.memory.add(msg)
@@ -279,17 +386,10 @@ class DataScienceAgent(AliasAgentBase):
# pylint: disable=invalid-overridden-method, unused-argument # pylint: disable=invalid-overridden-method, unused-argument
async def generate_response( async def generate_response(
self, self,
response: str,
**kwargs: Any, **kwargs: Any,
) -> ToolResponse: ) -> ToolResponse:
"""Call this function when you have either completed the task """
or cannot continue due to insurmountable reasons. Generate required structured output by this function and return it
Provide in the `response` argument any information you believe
the user needs to be informed of.
Args:
response (`str`):
Your response to the user.
""" """
memory = await self.memory.get_memory() memory = await self.memory.get_memory()
memory_log = "\n\n".join( memory_log = "\n\n".join(
@@ -350,20 +450,15 @@ class DataScienceAgent(AliasAgentBase):
f"{self.detailed_report_path}." f"{self.detailed_report_path}."
) )
response_msg = Msg( kwargs["response"] = response
self.name, structured_output = {}
response,
"assistant",
)
await self.print(response_msg, True)
# Prepare structured output # Prepare structured output
if self._required_structured_model: if self._required_structured_model:
try: try:
# Use the metadata field of the message to store the # Use the metadata field of the message to store the
# structured output # structured output
response_msg.metadata = ( structured_output = (
self._required_structured_model.model_validate( self._required_structured_model.model_validate(
kwargs, kwargs,
).model_dump() ).model_dump()
@@ -379,10 +474,18 @@ class DataScienceAgent(AliasAgentBase):
], ],
metadata={ metadata={
"success": False, "success": False,
"response_msg": None, "structured_output": {},
}, },
) )
await self.print(
Msg(
name=self.name,
content=response,
role="assistant",
),
True,
)
return ToolResponse( return ToolResponse(
content=[ content=[
TextBlock( TextBlock(
@@ -392,7 +495,7 @@ class DataScienceAgent(AliasAgentBase):
], ],
metadata={ metadata={
"success": True, "success": True,
"response_msg": response_msg, "structured_output": structured_output,
}, },
is_last=True, is_last=True,
) )

View File

@@ -107,6 +107,8 @@ class DeepResearchAgent(AliasAgentBase):
x, x,
x.get("task_type", "general"), x.get("task_type", "general"),
) )
if x
else None
), ),
) )
self.node_level_report = node_level_report self.node_level_report = node_level_report

View File

@@ -245,12 +245,19 @@ class MetaPlanner(AliasAgentBase):
long_term_memory.tool_memory_retrieve, 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 # adjust ReActAgent parameters
if enable_clarification: if enable_clarification:
self._required_structured_model = ( self._required_structured_model = (
MetaPlannerResponseWithClarification MetaPlannerResponseWithClarification
) )
response_func = self.toolkit.tools.get(self.finish_function_name)
response_func.json_schema["function"][ response_func.json_schema["function"][
"description" "description"
] = response_func.json_schema["function"].get( ] = response_func.json_schema["function"].get(
@@ -265,7 +272,6 @@ class MetaPlanner(AliasAgentBase):
self._required_structured_model = ( self._required_structured_model = (
MetaPlannerResponseNoClarification MetaPlannerResponseNoClarification
) )
response_func = self.toolkit.tools.get(self.finish_function_name)
response_func.json_schema["function"][ response_func.json_schema["function"][
"description" "description"
] = response_func.json_schema["function"].get( ] = response_func.json_schema["function"].get(

View File

@@ -1,15 +1,11 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# pylint: disable=C2801, W0611, W0212 # 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.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.tool import ToolResponse
from agentscope.message import (
Msg,
TextBlock,
)
from dotenv import load_dotenv from dotenv import load_dotenv
from alias.agent.agents import AliasAgentBase from alias.agent.agents import AliasAgentBase
@@ -50,21 +46,22 @@ class ReActWorker(AliasAgentBase):
state_saving_dir=state_saving_dir, 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) self.max_iters: int = max(self.max_iters, WORKER_MAX_ITER)
def generate_response( def generate_response(
self, self,
response: str = "", **kwargs,
task_done: bool = True, ): # pylint: disable=useless-parent-delegation
subtask_progress_summary: str = "",
generated_files: dict[str, str] = None,
) -> ToolResponse:
""" """
Generate a response summarizing the execution progress of the Generate a response summarizing the execution progress of the
given subtask. given subtask.
Args: Args:
response (str):
The response text (compatible with AgentScope finish function).
task_done (bool): task_done (bool):
REQUIRED! Whether the subtask was done or not. REQUIRED! Whether the subtask was done or not.
subtask_progress_summary (str): subtask_progress_summary (str):
@@ -76,37 +73,4 @@ class ReActWorker(AliasAgentBase):
paths of generated files (e.g. '/FULL/PATH/OF/FILE_1.md') and paths of generated files (e.g. '/FULL/PATH/OF/FILE_1.md') and
the values are short descriptions about the generated files. the values are short descriptions about the generated files.
""" """
if generated_files is None: return super().generate_response(**kwargs)
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,
)

View File

@@ -106,7 +106,7 @@ async def save_post_reasoning_state(
async def save_post_action_state( async def save_post_action_state(
self: AliasAgentBase, self: AliasAgentBase,
action_input: dict[str, Any], # pylint: disable=W0613 action_input: dict[str, Any], # pylint: disable=W0613
tool_output: Optional[Msg], # pylint: disable=W0613 tool_output: Optional[dict], # pylint: disable=W0613
) -> None: ) -> None:
"""Hook func for save state after action step""" """Hook func for save state after action step"""
await _update_and_save_state_with_session(self) 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( async def generate_response_post_action_hook(
self: AliasAgentBase, self: AliasAgentBase,
action_input: dict[str, Any], # pylint: disable=W0613 action_input: dict[str, Any], # pylint: disable=W0613
tool_output: Optional[Msg], # pylint: disable=W0613 tool_output: Optional[dict], # pylint: disable=W0613
) -> None: ) -> None:
"""Hook func for printing clarification""" """Hook func for printing clarification"""
if not (hasattr(self, "session_service") and self.session_service): if not (hasattr(self, "session_service") and self.session_service):
return return
if isinstance(tool_output, Msg): if isinstance(tool_output, dict):
if tool_output.metadata and tool_output.metadata.get( if tool_output.get(
"require_clarification", "require_clarification",
False, False,
): ):
clarification_dict = { clarification_dict = {
"clarification_question": tool_output.metadata.get( "clarification_question": tool_output.get(
"clarification_question", "clarification_question",
"", "",
), ),
"clarification_options": tool_output.metadata.get( "clarification_options": tool_output.get(
"clarification_options", "clarification_options",
"", "",
), ),
@@ -144,7 +144,7 @@ async def generate_response_post_action_hook(
indent=4, indent=4,
), ),
role="assistant", role="assistant",
metadata=tool_output.metadata, metadata=tool_output,
) )
await self.print(msg, last=True) await self.print(msg, last=True)

View File

@@ -76,14 +76,15 @@ class WorkerResponse(BaseModel):
""" """
subtask_progress_summary: str = Field( subtask_progress_summary: str = Field(
..., default="",
description=WORKER_PROGRESS_SUMMARY, description=WORKER_PROGRESS_SUMMARY,
) )
generated_files: dict = Field( generated_files: dict = Field(
..., default=dict,
description=WORKER_FILE_COLLECTION_INSTRUCTION, description=WORKER_FILE_COLLECTION_INSTRUCTION,
) )
task_done: bool = Field( task_done: bool = Field(
..., default=True,
description="Whether task is done or it require addition effort", description="Whether the task is done or "
"it requires additional effort",
) )

View File

@@ -9,7 +9,9 @@ from .utils import (
) )
from .ds_toolkit import add_ds_specific_tool from .ds_toolkit import add_ds_specific_tool
from .prompt_selector import LLMPromptSelector from .prompt_selector import LLMPromptSelector
from .agent_hook import files_filter_pre_reply_hook from .agent_hook import (
files_filter_pre_reply_hook,
)
__all__ = [ __all__ = [
"ReportGenerator", "ReportGenerator",

View File

@@ -128,7 +128,7 @@ async def _planner_save_plan_with_session(
async def planner_save_post_action_state( async def planner_save_post_action_state(
self: MetaPlanner, self: MetaPlanner,
action_input: dict[str, Any], # pylint: disable=W0613 action_input: dict[str, Any], # pylint: disable=W0613
tool_output: Optional[Msg], # pylint: disable=W0613 tool_output: Optional[dict], # pylint: disable=W0613
) -> None: ) -> None:
"""Hook func for save state after action step""" """Hook func for save state after action step"""
await _update_and_save_state_with_session(self) await _update_and_save_state_with_session(self)

View File

@@ -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.inner import router as inner_router
from alias.server.api.v1.share import router as share_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.user import router as user_router
from alias.server.api.v1.monitor import router as monitor_router
router = APIRouter() router = APIRouter()
router.include_router(user_router) router.include_router(user_router)
@@ -19,3 +20,4 @@ router.include_router(chat_router)
router.include_router(file_router) router.include_router(file_router)
router.include_router(inner_router) router.include_router(inner_router)
router.include_router(share_router) router.include_router(share_router)
router.include_router(monitor_router)

View File

@@ -80,6 +80,7 @@ class ConversationService(BaseService[Conversation]):
base_url=settings.SANDBOX_URL, base_url=settings.SANDBOX_URL,
bearer_token=settings.SANDBOX_BEARER_TOKEN, bearer_token=settings.SANDBOX_BEARER_TOKEN,
) )
sandbox.__enter__()
conversation_data = Conversation( conversation_data = Conversation(
user_id=user_id, user_id=user_id,