Skip to content

Commit d91a7ef

Browse files
committed
Refactoring EmailSearchWorkflow and EmailSearchAgent to adapt to the latest version of Agenscope
1 parent 15a2cd8 commit d91a7ef

File tree

5 files changed

+82
-90
lines changed

5 files changed

+82
-90
lines changed

docs/sphinx_doc/source/tutorial/example_search_email.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Email Search Workflow
22

33

4-
This example shows a multi-turn email search workflow, inspired by [ART](https://openpipe.ai/blog/art-e-mail-agent?refresh=1756431423904). We implement a ReAct Agent and define tools for email search. Note that this example rewquires installing `AgentScope==0.1.6`.
4+
This example shows a multi-turn email search workflow, inspired by [ART](https://openpipe.ai/blog/art-e-mail-agent?refresh=1756431423904). We implement a ReAct Agent and define tools for email search. Note that this example rewquires installing [AgentScope](https://github.com/agentscope-ai/agentscope).
55

66
## Core Components
77

docs/sphinx_doc/source_zh/tutorial/example_search_email.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# 邮件搜索例子
22

3-
这个示例展示了一个多轮邮件搜索工作流,内容参考自 [ART](https://openpipe.ai/blog/art-e-mail-agent?refresh=1756431423904)。我们实现了一个 ReAct Agent,并定义了用于邮件搜索的工具。注意:此示例需要安装 `AgentScope==0.1.6`
3+
这个示例展示了一个多轮邮件搜索工作流,内容参考自 [ART](https://openpipe.ai/blog/art-e-mail-agent?refresh=1756431423904)。我们实现了一个 ReAct Agent,并定义了用于邮件搜索的工具。注意:此示例需要安装 [AgentScope](https://github.com/agentscope-ai/agentscope)
44

55
## 核心组件
66

trinity/common/workflows/agentscope/react/react_agent.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from agentscope.formatter import OpenAIChatFormatter
66
from agentscope.message import Msg
77
from agentscope.model import OpenAIChatModel
8+
from agentscope.tool import Toolkit
89
from pydantic import BaseModel
910

1011

@@ -16,6 +17,7 @@ def __init__(
1617
system_prompt: str,
1718
generate_kwargs: dict,
1819
response_structure: Type[BaseModel],
20+
toolkit: Toolkit | None = None,
1921
):
2022
"""Initialize the AgentScope ReAct agent with specified tools and model.
2123
@@ -41,6 +43,7 @@ def __init__(
4143
formatter=OpenAIChatFormatter(),
4244
# we enable agentscope's meta tool to allow agent to call tools dynamically without pre-registration
4345
enable_meta_tool=True,
46+
toolkit=toolkit,
4447
)
4548
self.response_structure = response_structure
4649

Lines changed: 53 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,39 @@
1+
import json
12
from dataclasses import asdict
23
from datetime import datetime, timedelta
34
from typing import Any
45

6+
from agentscope.message import TextBlock
7+
from agentscope.tool import Toolkit, ToolResponse
8+
9+
from trinity.common.workflows.agentscope.react.react_agent import AgentScopeReActAgent
510
from trinity.common.workflows.envs.email_searcher.utils import (
611
read_email_tool,
712
search_emails_tool,
813
)
914

10-
BaseAgentClass = object
11-
try:
12-
from agentscope.agents import ReActAgentV2
13-
14-
BaseAgentClass = ReActAgentV2 # type: ignore[misc]
15-
except ImportError as e:
16-
error_message = f"AgentScope is not installed. Please install the agentscope framework first before running the workflow. Error: {str(e)}"
17-
pass
18-
1915

20-
class EmailSearchAgent(BaseAgentClass):
16+
class EmailSearchAgent(AgentScopeReActAgent):
2117
"""
2218
A customized ReAct agent with pre-defined tools for email search and reading.
2319
Ref: https://github.com/OpenPipe/ART/blob/main/dev/art-e/art_e/rollout.py#L260
2420
"""
2521

2622
def __init__(self, *args, **kwargs):
27-
super().__init__(*args, **kwargs)
28-
29-
self.service_toolkit.add(self.search_emails)
30-
self.service_toolkit.add(self.read_email)
23+
self.message_id_list = []
24+
self.ever_read_message_ids = []
25+
toolkit = Toolkit()
26+
toolkit.register_tool_function(self.search_emails)
27+
toolkit.register_tool_function(self.read_email)
28+
super().__init__(*args, toolkit=toolkit, **kwargs)
3129

3230
def search_emails(
3331
self,
3432
inbox_address: str,
3533
query_date: str,
3634
keywords: list[str],
3735
**kwargs: Any,
38-
):
36+
) -> ToolResponse:
3937
"""
4038
Search the user's email inbox for emails that match the given keywords.
4139
@@ -49,7 +47,6 @@ def search_emails(
4947
The status field indicates whether the tool call was successful.
5048
The content field contains a list of SearchResult objects with message_id and snippet. If no emails are found, it returns an empty list.
5149
"""
52-
from agentscope.service import ServiceExecStatus, ServiceResponse
5350

5451
try:
5552
next_day = (datetime.strptime(query_date, "%Y-%m-%d") + timedelta(days=1)).strftime(
@@ -59,14 +56,26 @@ def search_emails(
5956

6057
self.message_id_list.extend([r.message_id for r in res])
6158

62-
return ServiceResponse(
63-
status=ServiceExecStatus.SUCCESS,
64-
content=[asdict(r) for r in res],
59+
return ToolResponse(
60+
content=[
61+
TextBlock(
62+
type="text",
63+
text=json.dumps([asdict(r) for r in res]),
64+
),
65+
],
6566
)
66-
except Exception:
67-
return ServiceResponse(
68-
status=ServiceExecStatus.ERROR,
69-
content=[],
67+
except Exception as e:
68+
print(f"Error in tool: {e}")
69+
import traceback
70+
71+
traceback.print_exc()
72+
return ToolResponse(
73+
content=[
74+
TextBlock(
75+
type="text",
76+
text="Error: Failed to search emails.",
77+
),
78+
],
7079
)
7180

7281
def read_email(self, message_id: str, **kwargs: Any):
@@ -80,25 +89,36 @@ def read_email(self, message_id: str, **kwargs: Any):
8089
The status field indicates whether the tool call was successful.
8190
The content field contains the email content or an error message if the email is not found.
8291
"""
83-
from agentscope.service import ServiceExecStatus, ServiceResponse
8492

8593
try:
8694
email_content = read_email_tool(message_id)
8795

8896
self.ever_read_message_ids.append(message_id)
8997

9098
if email_content is None:
91-
return ServiceResponse(
92-
status=ServiceExecStatus.ERROR,
93-
content={"error": "Email not found"},
99+
return ToolResponse(
100+
content=[
101+
TextBlock(
102+
type="text",
103+
text="Error: Email not found.",
104+
),
105+
],
94106
)
95107
else:
96-
return ServiceResponse(
97-
status=ServiceExecStatus.SUCCESS,
98-
content=email_content.model_dump(),
108+
return ToolResponse(
109+
content=[
110+
TextBlock(
111+
type="text",
112+
text=json.dumps(email_content.model_dump()),
113+
),
114+
],
99115
)
100116
except Exception:
101-
return ServiceResponse(
102-
status=ServiceExecStatus.ERROR,
103-
content={"error": "Timeout"},
117+
return ToolResponse(
118+
content=[
119+
TextBlock(
120+
type="text",
121+
text="Error: Timeout to read email.",
122+
),
123+
],
104124
)

trinity/common/workflows/envs/email_searcher/workflow.py

Lines changed: 24 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
import openai
66

77
from trinity.common.models.model import ModelWrapper
8-
from trinity.common.workflows.envs.email_searcher.react_agent import EmailSearchAgent
98
from trinity.common.workflows.envs.email_searcher.utils import (
109
AnswerModel,
1110
FinalRubric,
@@ -35,44 +34,15 @@ def __init__(
3534
model: ModelWrapper,
3635
auxiliary_models: Optional[List[openai.OpenAI]] = None,
3736
):
38-
try:
39-
import agentscope
40-
from agentscope.service import ServiceToolkit
41-
except ImportError as e:
42-
error_message = f"AgentScope is not installed. Please install the agentscope framework first before running the workflow. Error: {str(e)}"
43-
self.logger.error(error_message)
44-
raise ImportError(error_message)
45-
4637
# get openai client from model
47-
self.openai_client = model.get_openai_client()
38+
self.openai_client = model.get_openai_async_client()
4839
self.model_name = self.openai_client.model_path
4940
super().__init__(
5041
task=task,
5142
model=model,
5243
auxiliary_models=auxiliary_models,
5344
)
5445

55-
temperature = self.rollout_args.get("temperature", 1.0)
56-
max_tokens = 4096
57-
58-
agentscope.init(
59-
model_configs=[
60-
{
61-
"model_type": "openai_chat",
62-
"config_name": "react_model",
63-
"model_name": self.model_name,
64-
"api_key": "EMPTY",
65-
"generate_args": {
66-
"temperature": temperature,
67-
"max_tokens": max_tokens,
68-
},
69-
"use_openai_formatter": True,
70-
}
71-
],
72-
disable_saving=True,
73-
)
74-
75-
self.toolkit = ServiceToolkit()
7646
self.reset(task)
7747

7848
@property
@@ -83,6 +53,10 @@ def repeatable(self) -> bool:
8353
def resettable(self):
8454
return True
8555

56+
@property
57+
def asynchronous(self):
58+
return True
59+
8660
def reset(self, task: Task):
8761
self.query = QueryModel.model_validate(task.raw_task)
8862
self.task_desc = task.task_desc # question
@@ -99,39 +73,34 @@ def reset(self, task: Task):
9973
query_date=self.query.query_date,
10074
)
10175

102-
self.agent = EmailSearchAgent(
103-
name="react_agent",
104-
sys_prompt=self.system_prompt,
105-
model_config_name="react_model",
106-
service_toolkit=self.toolkit,
107-
max_iters=self.max_turns,
108-
verbose=False,
109-
)
110-
# we set the openai client to the agent's model
111-
self.agent.model.client = self.openai_client
112-
self.agent.message_id_list = []
113-
self.agent.ever_read_message_ids = []
114-
115-
def run(self):
116-
# make sure that we have the correct import
11776
try:
118-
from agentscope.message import Msg
77+
from trinity.common.workflows.envs.email_searcher.react_agent import (
78+
EmailSearchAgent,
79+
)
11980
except ImportError as e:
12081
error_message = f"AgentScope is not installed. Please install the agentscope framework first before running the workflow. Error: {str(e)}"
12182
self.logger.error(error_message)
12283
raise ImportError(error_message)
12384

124-
# provide the task to the react agent
125-
msg = Msg("user", self.task_desc, role="user")
85+
self.agent = EmailSearchAgent(
86+
model_name=self.openai_client.model_path,
87+
openai_client=self.openai_client,
88+
system_prompt=self.system_prompt,
89+
generate_kwargs={
90+
"temperature": self.rollout_args.get("temperature", 1.0),
91+
"max_tokens": self.rollout_args.get("max_tokens", 4096),
92+
},
93+
response_structure=AnswerModel,
94+
)
12695

127-
response = self.agent.reply(
128-
msg,
129-
structured_model=AnswerModel,
96+
async def run_async(self):
97+
response = await self.agent.reply(
98+
self.task_desc,
13099
)
131-
if response.metadata is None:
132-
answer_and_sources = {"answer": response.content, "sources": []}
100+
if response is None:
101+
answer_and_sources = {"answer": "", "sources": []}
133102
else:
134-
answer_and_sources = response.metadata
103+
answer_and_sources = response
135104

136105
experiences = self.model.extract_experience_from_history(clear_history=True)
137106
self.actual_turns = len(

0 commit comments

Comments
 (0)