Skip to content

Commit 69b5425

Browse files
authored
Merge pull request #241 from AnguseZhang/feat/ticket-auth
feat: Enhance job tracking and processing with URL functionality, improved error handling, and tool call management
2 parents c09183d + 6b423fc commit 69b5425

9 files changed

Lines changed: 474 additions & 257 deletions

File tree

agents/matmaster_agent/agent.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from agents.matmaster_agent.base_agents.io_agent import HandleFileUploadLlmAgent
1212
from agents.matmaster_agent.callback import (
1313
matmaster_check_job_status,
14+
matmaster_hallucination_retry,
1415
matmaster_prepare_state,
1516
matmaster_set_lang,
1617
)
@@ -110,6 +111,7 @@ def __init__(self, llm_config):
110111
prompt=MatMasterCheckTransferPrompt,
111112
target_agent_enum=MatMasterTargetAgentEnum,
112113
),
114+
matmaster_hallucination_retry,
113115
],
114116
)
115117

agents/matmaster_agent/base_agents/job_agent.py

Lines changed: 294 additions & 229 deletions
Large diffs are not rendered by default.

agents/matmaster_agent/callback.py

Lines changed: 55 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
from agents.matmaster_agent.locales import i18n
1616
from agents.matmaster_agent.model import UserContent
1717
from agents.matmaster_agent.prompt import get_user_content_lang
18-
from agents.matmaster_agent.style import get_job_complete_card
18+
from agents.matmaster_agent.style import get_job_complete_card, hallucination_card
1919
from agents.matmaster_agent.utils.job_utils import (
2020
get_job_status,
2121
get_running_jobs_detail,
@@ -77,6 +77,12 @@ async def matmaster_prepare_state(
7777
callback_context.state['new_query_job_status'] = callback_context.state.get(
7878
'new_query_job_status', {}
7979
)
80+
callback_context.state['hallucination'] = callback_context.state.get(
81+
'hallucination', False
82+
)
83+
callback_context.state['hallucination_agent'] = callback_context.state.get(
84+
'hallucination_agent', None
85+
)
8086

8187

8288
async def matmaster_set_lang(
@@ -93,6 +99,15 @@ async def matmaster_set_lang(
9399
logger.info(f"[{inspect.currentframe().f_code.co_name}] result = {result}")
94100
language = str(result.get('language', 'zh'))
95101
callback_context.state['target_language'] = language
102+
if callback_context.state['target_language'] in [
103+
'Chinese',
104+
'zh-CN',
105+
'简体中文',
106+
'Chinese (Simplified)',
107+
]:
108+
i18n.language = 'zh'
109+
else:
110+
i18n.language = 'en'
96111

97112

98113
# after_model_callback
@@ -113,16 +128,6 @@ async def matmaster_check_job_status(
113128
jobs_dict
114129
): # 确认当前有在运行中的任务
115130
running_job_ids = get_running_jobs_detail(jobs_dict) # 从 state 里面拿
116-
if callback_context.state['target_language'] in [
117-
'Chinese',
118-
'zh-CN',
119-
'简体中文',
120-
'Chinese (Simplified)',
121-
]:
122-
i18n.language = 'zh'
123-
else:
124-
i18n.language = 'en'
125-
126131
reset = False
127132
for origin_job_id, job_id, agent_name in running_job_ids:
128133
if not callback_context.state['last_llm_response_partial']:
@@ -149,9 +154,8 @@ async def matmaster_check_job_status(
149154
llm_response.content = None
150155
break
151156
if not reset:
152-
callback_context.state['special_llm_response'] = (
153-
True # 标记开始处理原来消息的非流式版本
154-
)
157+
# 标记开始处理原来消息的非流式版本
158+
callback_context.state['special_llm_response'] = True
155159
llm_response.content.parts = []
156160
reset = True
157161
function_call_id = f"call_{str(uuid.uuid4()).replace('-', '')[:24]}"
@@ -169,7 +173,42 @@ async def matmaster_check_job_status(
169173
)
170174
)
171175
callback_context.state['last_llm_response_partial'] = llm_response.partial
172-
return llm_response
176+
# return llm_response
173177

174178
callback_context.state['last_llm_response_partial'] = llm_response.partial
175-
return None
179+
return
180+
181+
182+
async def matmaster_hallucination_retry(
183+
callback_context: CallbackContext, llm_response: LlmResponse
184+
) -> Optional[LlmResponse]:
185+
hallucination_flag = callback_context.state['hallucination']
186+
hallucination_agent = callback_context.state['hallucination_agent']
187+
logger.info(
188+
f'[matmaster_hallucination_retry] hallucination_flag={hallucination_flag}, hallucination_agent={hallucination_agent}, i18n.language = {i18n.language}'
189+
)
190+
if not callback_context.state['hallucination']:
191+
return
192+
193+
if llm_response.partial: # 原来消息的流式版本置空 None
194+
llm_response.content = None
195+
return
196+
197+
# 标记开始处理原来消息的非流式版本
198+
callback_context.state['special_llm_response'] = True
199+
llm_response.content.parts = []
200+
201+
llm_response.content.parts.append(Part(text=hallucination_card(i18n=i18n)))
202+
function_call_id = f"added_{str(uuid.uuid4()).replace('-', '')[:24]}"
203+
llm_response.content.parts.append(
204+
Part(
205+
function_call=FunctionCall(
206+
id=function_call_id,
207+
name='transfer_to_agent',
208+
args={'agent_name': callback_context.state['hallucination_agent']},
209+
)
210+
)
211+
)
212+
callback_context.state['hallucination'] = False
213+
214+
return llm_response

agents/matmaster_agent/llm_config.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -83,14 +83,13 @@ def _init_model(
8383
model_name: str,
8484
**kwargs,
8585
):
86-
llm_kwargs = {'stream_options': {'include_usage': True}}
87-
if kwargs:
88-
llm_kwargs.update(kwargs)
8986
return LiteLlm(
9087
model=MODEL_MAPPING.get((provider_key, model_name), DEFAULT_MODEL),
91-
**llm_kwargs,
88+
**kwargs,
9289
)
9390

91+
llm_kwargs = {'stream_options': {'include_usage': True}}
92+
9493
self.gpt_4o_mini = _init_model(azure_provider, gpt_4o_mini)
9594
self.gpt_4o = _init_model(azure_provider, gpt_4o)
9695
self.gemini_2_0_flash = _init_model(litellm_provider, gemini_2_0_flash)
@@ -103,7 +102,7 @@ def _init_model(
103102
self.gpt_5 = _init_model(litellm_provider, gpt_5)
104103
self.gpt_5_nano = _init_model(litellm_provider, gpt_5_nano)
105104
self.gpt_5_mini = _init_model(litellm_provider, gpt_5_mini)
106-
self.gpt_5_chat = _init_model(litellm_provider, gpt_5_chat)
105+
self.gpt_5_chat = _init_model(litellm_provider, gpt_5_chat, **llm_kwargs)
107106

108107
# tracing
109108
self.opik_tracer = OpikTracer()

agents/matmaster_agent/locales.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,16 @@
66
'Job': 'Job',
77
'JobCompleted': 'Job Completed',
88
'ResultRetrieving': 'Result Retrieving',
9+
'JobSubmitHallucination': 'Job Submit Hallucination',
10+
'JobSubmitHallucinationAction': 'Detected a task submission hallucination, I will retry the submission with the same parameters.',
911
},
1012
'zh': {
1113
'JobStatus': '任务状态',
1214
'Job': '任务',
1315
'JobCompleted': '任务已完成',
1416
'ResultRetrieving': '结果获取中',
17+
'JobSubmitHallucination': '任务提交幻觉',
18+
'JobSubmitHallucinationAction': '检测到任务提交幻觉,我将使用相同参数重试提交。',
1519
},
1620
}
1721

agents/matmaster_agent/main.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,9 @@ async def agent_main() -> None:
6464
# user_input = "帮我用DPA优化这个结构:https://dp-storage-test2.oss-cn-zhangjiakou.aliyuncs.com/bohrium-test/110663/12791/store/7ba41529-5af4-4e38-a6fb-c569cd769dd9/outputs/structure_paths/structure_bulk.cif"
6565
# user_input = "帮我检索TiO2"
6666
# user_input = "请你为我搭建一个氯化钠的结构"
67-
user_input = '我想要一个bandgap 小于0.5ev的结构,空间群225,生成数量1'
67+
# user_input = '我想要一个bandgap 小于0.5ev的结构,空间群225,生成数量1'
6868
# user_input = '用openlam查找一个TiO2'
69+
user_input = '对 NbSe₂ 超导体进行声子谱计算并结合电子–声子耦合分析估算临界温度 Tc(从开源数据库获取初始结构并自行设定参数),并以 URL 导出声子谱与 e–ph 计算结果'
6970
print(f"🧑 用户:{user_input}")
7071

7172
# Create the initial content with user input

agents/matmaster_agent/prompt.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
- Upon user confirmation or applied parameters, execute the step using the sub-agent.
5050
6. **Result Handling**:
5151
- Present the execution result and a brief analysis.
52+
- If the result contains images in markdown format, display them to the user using proper markdown syntax.
5253
- Await user instruction: either proceed to the next step in the plan, adjust parameters, or modify the plan.
5354
5455
**Response Formatting:**
@@ -720,6 +721,44 @@ def gen_params_check_info_agent_instruction():
720721
"""
721722

722723

724+
def gen_tool_call_info_instruction():
725+
return """
726+
You are an AI agent that matches user requests to available tools. Your task is to analyze the user's query and return a JSON object with the following structure:
727+
{{
728+
"tool_name": "string",
729+
"tool_args": {{"param1_name": "value1", "param2_name": "value2"}},
730+
"missing_tool_args": ["param3_name", "param4_name"]
731+
}}
732+
733+
**Key Rules:**
734+
- The `tool_args` object should contain parameter names as keys and the actual values extracted from the user's request as values
735+
- For parameters where values cannot be extracted from the user's request, include the parameter name in the `missing_tool_args` list
736+
- If any parameter involves an input file, the parameter name should indicate it requires an HTTP URL (e.g., "file_url", "image_url")
737+
- For output file parameters, use appropriate names (e.g., "output_path", "result_file") - these will handle OSS URLs automatically
738+
- Only return the JSON object - do not execute any tools directly
739+
- Extract and include all available parameter values from the user's request in `tool_args`
740+
- List all missing required parameter names in the `missing_tool_args` array
741+
742+
**Example Response:**
743+
{{
744+
"tool_name": "image_processor",
745+
"tool_args": {{
746+
"image_url": "https://example.com/image.jpg",
747+
"operation": "resize",
748+
"width": 800
749+
}},
750+
"missing_tool_args": ["height", "output_format"]
751+
}}
752+
753+
**Constraints:**
754+
- Return only valid JSON - no additional text or explanations
755+
- Include all available parameter values from the user's request in `tool_args`
756+
- List all missing required parameter names in `missing_tool_args`
757+
- Match the tool precisely based on the user's request
758+
- If no suitable tool is found, return an empty object: {{}}
759+
"""
760+
761+
723762
SubmitRenderAgentDescription = 'Sends specific messages to the frontend for rendering dedicated task list components'
724763

725764
ResultCoreAgentDescription = (

agents/matmaster_agent/style.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,77 @@ def get_job_complete_card(i18n: I18N, job_id):
4040
">{i18n.t("ResultRetrieving")}...</div>
4141
</div>
4242
"""
43+
44+
45+
def hallucination_card(i18n: I18N):
46+
def _inner_css():
47+
return """
48+
<style>
49+
@keyframes pulse {
50+
0% { transform: scale(1); }
51+
50% { transform: scale(1.1); }
52+
100% { transform: scale(1); }
53+
}
54+
@keyframes progress {
55+
0% { transform: translateX(-100%); }
56+
100% { transform: translateX(250%); }
57+
}
58+
</style>
59+
</div>
60+
"""
61+
62+
return (
63+
f"""
64+
<div style="
65+
background: linear-gradient(135deg, #6a11cb 0%, #2575fc 100%);
66+
color: white;
67+
padding: 24px 32px;
68+
border-radius: 12px;
69+
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.15);
70+
max-width: 500px;
71+
width: 100%;
72+
position: relative;
73+
overflow: hidden;
74+
transition: transform 0.3s ease, box-shadow 0.3s ease;
75+
margin: 20px auto;
76+
">
77+
<div style="
78+
position: relative;
79+
z-index: 1;
80+
display: flex;
81+
align-items: center;
82+
">
83+
<div style="
84+
font-size: 28px;
85+
margin-right: 16px;
86+
animation: pulse 2s infinite;
87+
">🔄</div>
88+
<div style="flex: 1;">
89+
<div style="
90+
font-size: 18px;
91+
font-weight: 600;
92+
margin-bottom: 4px;
93+
">{i18n.t("JobSubmitHallucination")}</div>
94+
<div style="
95+
font-size: 14px;
96+
opacity: 0.9;
97+
">{i18n.t("JobSubmitHallucinationAction")}</div>
98+
</div>
99+
</div>
100+
<div style="
101+
height: 4px;
102+
background: rgba(255, 255, 255, 0.3);
103+
border-radius: 2px;
104+
margin-top: 16px;
105+
overflow: hidden;
106+
">
107+
<div style="
108+
height: 100%;
109+
width: 60%;
110+
background: white;
111+
border-radius: 2px;
112+
animation: progress 2s ease-in-out infinite;
113+
"></div>
114+
</div>"""
115+
+ _inner_css()
116+
)

agents/matmaster_agent/utils/event_utils.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,6 @@ def is_text(event: Event):
4242
return has_part(event) and event.content.parts[0].text
4343

4444

45-
def is_text_and_not_bohrium(event: Event):
46-
return is_text(event) and not event.content.parts[0].text.startswith(
47-
'<bohrium-chat-msg>'
48-
)
49-
50-
5145
def is_function_call(event: Event) -> bool:
5246
"""检查事件是否包含函数调用"""
5347
return has_part(event) and any(part.function_call for part in event.content.parts)

0 commit comments

Comments
 (0)