Skip to content

Commit 41c13c8

Browse files
authored
Merge pull request #181 from AnguseZhang/dev/zhouh
feat: implement internationalized job status notifications with styled JobCompleteCard and refactor MatMaster agent for improved localization
2 parents 9a39a96 + 1e537de commit 41c13c8

12 files changed

Lines changed: 192 additions & 75 deletions

File tree

agents/matmaster_agent/agent.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,18 @@
1414
from agents.matmaster_agent.MrDice_agent.agent import init_MrDice_agent
1515
from agents.matmaster_agent.apex_agent.agent import init_apex_agent
1616
from agents.matmaster_agent.base_agents.io_agent import HandleFileUploadLlmAgent
17-
from agents.matmaster_agent.callback import matmaster_prepare_state, matmaster_check_transfer, matmaster_set_lang, \
18-
matmaster_check_job_status
17+
from agents.matmaster_agent.callback import matmaster_prepare_state, matmaster_set_lang, matmaster_check_job_status
1918
from agents.matmaster_agent.chembrain_agent.agent import init_chembrain_agent
2019
from agents.matmaster_agent.constant import MATMASTER_AGENT_NAME, ModelRole
2120
from agents.matmaster_agent.document_parser_agent.agent import init_document_parser_agent
2221
from agents.matmaster_agent.llm_config import MatMasterLlmConfig
22+
from agents.matmaster_agent.model import MatMasterTargetAgentEnum
2323
from agents.matmaster_agent.organic_reaction_agent.agent import init_organic_reaction_agent
2424
from agents.matmaster_agent.perovskite_agent.agent import init_perovskite_agent
2525
from agents.matmaster_agent.piloteye_electro_agent.agent import init_piloteye_electro_agent
26-
from agents.matmaster_agent.prompt import AgentDescription, AgentInstruction, GlobalInstruction
26+
from agents.matmaster_agent.prompt import AgentDescription, AgentInstruction, GlobalInstruction, \
27+
MatMasterCheckTransferPrompt
28+
from agents.matmaster_agent.public.callback import check_transfer
2729
from agents.matmaster_agent.ssebrain_agent.agent import init_ssebrain_agent
2830
from agents.matmaster_agent.structure_generate_agent.agent import init_structure_generate_agent
2931
from agents.matmaster_agent.superconductor_agent.agent import init_superconductor_agent
@@ -82,8 +84,9 @@ def __init__(self, llm_config):
8284
instruction=AgentInstruction,
8385
description=AgentDescription,
8486
before_agent_callback=[matmaster_prepare_state, matmaster_set_lang],
85-
after_model_callback=[matmaster_check_job_status, matmaster_check_transfer],
86-
)
87+
after_model_callback=[matmaster_check_job_status,
88+
check_transfer(prompt=MatMasterCheckTransferPrompt,
89+
target_agent_enum=MatMasterTargetAgentEnum)])
8790

8891
@override
8992
async def _run_async_impl(self, ctx: InvocationContext) -> AsyncGenerator[Event, None]:

agents/matmaster_agent/callback.py

Lines changed: 9 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import inspect
12
import json
23
import logging
34
import uuid
@@ -12,10 +13,11 @@
1213

1314
from agents.matmaster_agent.base_agents.callback import _get_ak
1415
from agents.matmaster_agent.constant import FRONTEND_STATE_KEY
15-
from agents.matmaster_agent.model import TransferCheck, UserContent
16-
from agents.matmaster_agent.prompt import get_transfer_check_prompt, get_user_content_lang
16+
from agents.matmaster_agent.locales import i18n
17+
from agents.matmaster_agent.model import UserContent
18+
from agents.matmaster_agent.prompt import get_user_content_lang
19+
from agents.matmaster_agent.style import get_job_complete_card
1720
from agents.matmaster_agent.utils.job_utils import get_job_status, has_job_running, get_running_jobs_detail
18-
from agents.matmaster_agent.utils.llm_response_utils import has_function_call
1921

2022
logger = logging.getLogger(__name__)
2123

@@ -51,7 +53,7 @@ async def matmaster_set_lang(callback_context: CallbackContext) -> Optional[type
5153
response = litellm.completion(model='azure/gpt-4o', messages=[{'role': 'user', 'content': prompt}],
5254
response_format=UserContent)
5355
result: dict = json.loads(response.choices[0].message.content)
54-
logger.info(f"[matmaster_prepare_state] user_content = {result}")
56+
logger.info(f"[{inspect.currentframe().f_code.co_name}] result = {result}")
5557
language = str(result.get('language', 'zh'))
5658
callback_context.state['target_language'] = language
5759

@@ -74,10 +76,9 @@ async def matmaster_check_job_status(callback_context: CallbackContext, llm_resp
7476
running_job_ids = get_running_jobs_detail(jobs_dict) # 从 state 里面拿
7577
access_key = _get_ak(callback_context) # 从 state 或环境变量里面拿
7678
if callback_context.state['target_language'] in ['Chinese', 'zh-CN', '简体中文', 'Chinese (Simplified)']:
77-
job_complete_intro = '检测到任务 <{job_id}> 已完成,我将立刻转移至对应的 Agent 去获取任务结果。'
79+
i18n.language = 'zh'
7880
else:
79-
job_complete_intro = ('Job <{job_id}> has been detected as completed. '
80-
'I will immediately transfer to the corresponding agent to retrieve the job results.')
81+
i18n.language = 'en'
8182

8283
reset = False
8384
for origin_job_id, job_id, job_query_url, agent_name in running_job_ids:
@@ -100,7 +101,7 @@ async def matmaster_check_job_status(callback_context: CallbackContext, llm_resp
100101
reset = True
101102
function_call_id = f"call_{str(uuid.uuid4()).replace('-', '')[:24]}"
102103
callback_context.state['origin_job_id'] = origin_job_id
103-
llm_response.content.parts.append(Part(text=job_complete_intro.format(job_id=job_id)))
104+
llm_response.content.parts.append(Part(text=get_job_complete_card(i18n=i18n, job_id=job_id)))
104105
llm_response.content.parts.append(Part(function_call=FunctionCall(id=function_call_id,
105106
name='transfer_to_agent',
106107
args={'agent_name': agent_name})))
@@ -109,40 +110,3 @@ async def matmaster_check_job_status(callback_context: CallbackContext, llm_resp
109110

110111
callback_context.state['last_llm_response_partial'] = llm_response.partial
111112
return None
112-
113-
114-
async def matmaster_check_transfer(callback_context: CallbackContext, llm_response: LlmResponse) -> Optional[
115-
LlmResponse]:
116-
# 检查响应是否有效
117-
if not (
118-
llm_response and
119-
not llm_response.partial and
120-
llm_response.content and
121-
llm_response.content.parts and
122-
len(llm_response.content.parts) and
123-
llm_response.content.parts[0].text
124-
):
125-
return None
126-
127-
prompt = get_transfer_check_prompt().format(response_text=llm_response.content.parts[0].text)
128-
response = litellm.completion(model='azure/gpt-4o', messages=[{'role': 'user', 'content': prompt}],
129-
response_format=TransferCheck)
130-
131-
result: dict = json.loads(response.choices[0].message.content)
132-
is_transfer = bool(result.get('is_transfer', False))
133-
target_agent = str(result.get('target_agent', ''))
134-
reason = str(result.get('reason', ''))
135-
logger.info(f"[matmaster_check_transfer] target_agent = {target_agent}, is_transfer = {is_transfer}"
136-
f"response_text = {llm_response.content.parts[0].text}, reason = {reason}")
137-
if (
138-
is_transfer and
139-
not has_function_call(llm_response)
140-
):
141-
logger.warning(f"[matmaster_check_transfer] add `transfer_to_agent`")
142-
function_call_id = f"added_{str(uuid.uuid4()).replace('-', '')[:24]}"
143-
llm_response.content.parts.append(Part(function_call=FunctionCall(id=function_call_id, name='transfer_to_agent',
144-
args={'agent_name': target_agent})))
145-
146-
return llm_response
147-
148-
return None

agents/matmaster_agent/locales.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
from toolsy.i8n import I18N
2+
3+
translations = {
4+
'en': {
5+
'JobStatus': 'Job Status',
6+
'Job': 'Job',
7+
'JobCompleted': 'Job Completed',
8+
'ResultRetrieving': 'Result Retrieving'
9+
},
10+
'zh': {
11+
'JobStatus': '任务状态',
12+
'Job': '任务',
13+
'JobCompleted': '任务已完成',
14+
'ResultRetrieving': '结果获取中'
15+
}
16+
}
17+
18+
i18n = I18N(translations=translations)

agents/matmaster_agent/model.py

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,13 @@ class DFlowJobInfo(BaseModel):
7171
job_in_ctx: bool = False
7272

7373

74-
class TargetAgentEnum(str, Enum):
74+
class ParamsCheckComplete(BaseModel):
75+
flag: bool
76+
reason: str
77+
analyzed_messages: List[str]
78+
79+
80+
class MatMasterTargetAgentEnum(str, Enum):
7581
ABACUSAgent = ABACUS_AGENT_NAME
7682
APEXAgent = ApexAgentName
7783
ChemBrainAgent = CHEMBRAIN_AGENT_NAME
@@ -91,17 +97,5 @@ class TargetAgentEnum(str, Enum):
9197
TrajAnalysisAgent = TrajAnalysisAgentName
9298

9399

94-
class ParamsCheckComplete(BaseModel):
95-
flag: bool
96-
reason: str
97-
analyzed_messages: List[str]
98-
99-
100-
class TransferCheck(BaseModel):
101-
is_transfer: bool
102-
target_agent: TargetAgentEnum
103-
reason: str
104-
105-
106100
class UserContent(BaseModel):
107101
language: str

agents/matmaster_agent/prompt.py

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -710,10 +710,8 @@ def gen_params_check_info_agent_instruction():
710710
ResultCoreAgentDescription = 'Provides real-time task status updates and result forwarding to UI'
711711
TransferAgentDescription = 'Transfer to proper agent to answer user query'
712712

713-
714713
# LLM-Helper Prompt
715-
def get_transfer_check_prompt():
716-
return """
714+
MatMasterCheckTransferPrompt = """
717715
You are an expert judge tasked with evaluating whether the previous LLM's response contains a clear and explicit request or instruction to transfer the conversation to a specific agent (e.g., 'xxx agent').
718716
Analyze the provided RESPONSE TEXT to determine if it explicitly indicates a transfer action.
719717
@@ -801,13 +799,30 @@ def get_params_check_info_prompt():
801799

802800
def get_user_content_lang():
803801
return """
804-
You are a professional assistant responsible for analysing language of user_content.
802+
You are a professional linguistic analyst. Your task is to identify the primary language used in the user content provided.
805803
806804
User Content:
807805
{user_content}
808806
809-
Provide your analysis in the following JSON format:
807+
Analyze the text and determine the most likely language from the following predefined options:
808+
- English
809+
- Chinese
810+
- Spanish
811+
- French
812+
- German
813+
- Japanese
814+
- Korean
815+
- Russian
816+
- Arabic
817+
- Portuguese
818+
- Italian
819+
- Dutch
820+
- Other
821+
822+
If the language does not clearly match any of the above options or is a mix of multiple languages, classify it as "Other".
823+
824+
Provide your analysis in the following strict JSON format:
810825
{{
811-
"language": <string>
826+
"language": "<string>"
812827
}}
813828
"""

agents/matmaster_agent/public/__init__.py

Whitespace-only changes.
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import json
2+
import logging
3+
import uuid
4+
from enum import Enum
5+
from typing import Optional, Callable, Type
6+
7+
import litellm
8+
from google.adk.agents.callback_context import CallbackContext
9+
from google.adk.agents.llm_agent import AfterModelCallback
10+
from google.adk.models import LlmResponse
11+
from google.genai.types import Part, FunctionCall
12+
13+
from agents.matmaster_agent.utils.llm_response_utils import has_function_call
14+
from agents.matmaster_agent.utils.model_utils import create_transfer_check_model
15+
16+
logger = logging.getLogger(__name__)
17+
18+
19+
def check_transfer(prompt: str, target_agent_enum: Type[Enum]) -> AfterModelCallback:
20+
async def wrapper(callback_context: CallbackContext, llm_response: LlmResponse) -> Optional[
21+
LlmResponse]:
22+
# 检查响应是否有效
23+
if not (
24+
llm_response and
25+
not llm_response.partial and
26+
llm_response.content and
27+
llm_response.content.parts and
28+
len(llm_response.content.parts) and
29+
llm_response.content.parts[0].text
30+
):
31+
return None
32+
33+
llm_prompt = prompt.format(response_text=llm_response.content.parts[0].text)
34+
response = litellm.completion(model='azure/gpt-4o', messages=[{'role': 'user', 'content': llm_prompt}],
35+
response_format=create_transfer_check_model(target_agent_enum))
36+
37+
result: dict = json.loads(response.choices[0].message.content)
38+
is_transfer = bool(result.get('is_transfer', False))
39+
target_agent = str(result.get('target_agent', ''))
40+
reason = str(result.get('reason', ''))
41+
symbol_name = f"[{callback_context.agent_name.replace('_agent', '')}_check_transfer]"
42+
logger.info(f"{symbol_name} target_agent = {target_agent}, is_transfer = {is_transfer}, "
43+
f"response_text = {llm_response.content.parts[0].text}, reason = {reason}")
44+
if (
45+
is_transfer and
46+
not has_function_call(llm_response)
47+
):
48+
logger.warning(f"{symbol_name} add `transfer_to_agent`")
49+
function_call_id = f"added_{str(uuid.uuid4()).replace('-', '')[:24]}"
50+
llm_response.content.parts.append(
51+
Part(function_call=FunctionCall(id=function_call_id, name='transfer_to_agent',
52+
args={'agent_name': target_agent})))
53+
54+
return llm_response
55+
56+
return None
57+
58+
return wrapper

agents/matmaster_agent/structure_generate_agent/agent.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ def __init__(self, llm_config):
5353
dflow_flag=False,
5454
supervisor_agent=MATMASTER_AGENT_NAME,
5555
sync_tools=[
56-
'build_bulk_structure_by_template',
56+
# 'build_bulk_structure_by_template',
5757
'build_bulk_structure_by_wyckoff',
5858
'make_supercell_structure',
5959
'build_molecule_structure_from_g2database',

agents/matmaster_agent/style.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
from toolsy.i8n import I18N
2+
3+
4+
def get_job_complete_card(i18n: I18N, job_id):
5+
return f"""
6+
<div style="
7+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
8+
border-radius: 12px;
9+
padding: 20px;
10+
margin: 16px 0;
11+
color: white;
12+
box-shadow: 0 8px 20px rgba(102, 126, 234, 0.3);
13+
">
14+
<div style="display: flex; align-items: center; gap: 12px; margin-bottom: 8px;">
15+
<div style="
16+
background: rgba(255, 255, 255, 0.2);
17+
padding: 6px 12px;
18+
border-radius: 20px;
19+
backdrop-filter: blur(10px);
20+
font-size: 12px;
21+
font-weight: bold;
22+
">🚀 {i18n.t("JobStatus")}</div>
23+
<div style="
24+
background: rgba(255, 255, 255, 0.1);
25+
padding: 4px 10px;
26+
border-radius: 6px;
27+
font-family: monospace;
28+
font-size: 12px;
29+
">{i18n.t("Job")}ID:{job_id}</div>
30+
</div>
31+
<div style="
32+
font-size: 16px;
33+
font-weight: 500;
34+
margin-top: 8px;
35+
">✅ {i18n.t("JobCompleted")}</div>
36+
<div style="
37+
font-size: 14px;
38+
opacity: 0.9;
39+
margin-top: 6px;
40+
">{i18n.t("ResultRetrieving")}...</div>
41+
</div>
42+
"""
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
from pydantic import create_model, BaseModel
2+
3+
4+
def create_transfer_check_model(agent_type):
5+
"""动态创建具有特定 agent 类型的 TransferCheck 模型"""
6+
return create_model(
7+
'DynamicTransferCheck',
8+
is_transfer=(bool, ...),
9+
target_agent=(agent_type, ...),
10+
reason=(str, ...),
11+
__base__=BaseModel
12+
)

0 commit comments

Comments
 (0)