refactor: update agentscope[full] to 1.0.11 (#92)
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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`
|
||||
|
||||
#### 默认登录凭据
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
"<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
|
||||
# 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
|
||||
|
||||
@@ -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",
|
||||
"<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)
|
||||
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,
|
||||
)
|
||||
|
||||
@@ -107,6 +107,8 @@ class DeepResearchAgent(AliasAgentBase):
|
||||
x,
|
||||
x.get("task_type", "general"),
|
||||
)
|
||||
if x
|
||||
else None
|
||||
),
|
||||
)
|
||||
self.node_level_report = node_level_report
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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",
|
||||
)
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user