From 6c7186385df726c04ae1e67f450e66d9ecebe441 Mon Sep 17 00:00:00 2001 From: beform88 <2298266722@qq.com> Date: Fri, 26 Dec 2025 03:42:46 +0000 Subject: [PATCH 01/15] . --- .../flow_agents/execution_agent/agent.py | 81 +++++++++++-------- 1 file changed, 47 insertions(+), 34 deletions(-) diff --git a/agents/matmaster_agent/flow_agents/execution_agent/agent.py b/agents/matmaster_agent/flow_agents/execution_agent/agent.py index 05900ee8..288096a8 100644 --- a/agents/matmaster_agent/flow_agents/execution_agent/agent.py +++ b/agents/matmaster_agent/flow_agents/execution_agent/agent.py @@ -72,46 +72,59 @@ async def _run_events(self, ctx: InvocationContext) -> AsyncGenerator[Event, Non PlanStepStatusEnum.FAILED, PlanStepStatusEnum.SUBMITTED, ]: - if step['status'] != PlanStepStatusEnum.SUBMITTED: - update_plan = copy.deepcopy(ctx.session.state['plan']) - update_plan['steps'][index][ - 'status' - ] = PlanStepStatusEnum.PROCESS - yield update_state_event( - ctx, state_delta={'plan': update_plan, 'plan_index': index} + max_retries = 3 + retry_count = 0 + while retry_count <= max_retries: + if step['status'] != PlanStepStatusEnum.SUBMITTED: + update_plan = copy.deepcopy(ctx.session.state['plan']) + update_plan['steps'][index][ + 'status' + ] = PlanStepStatusEnum.PROCESS + yield update_state_event( + ctx, state_delta={'plan': update_plan, 'plan_index': index} + ) + for ( + materials_plan_function_call_event + ) in context_function_event( + ctx, + self.name, + 'materials_plan_function_call', + { + 'msg': f'According to the plan, I will call the `{step['tool_name']}`: {step['description']}' + }, + ModelRole, + ): + yield materials_plan_function_call_event + + logger.info( + f'{ctx.session.id} Before Run: plan_index = {ctx.session.state["plan_index"]}, plan = {ctx.session.state['plan']}' ) - for ( - materials_plan_function_call_event - ) in context_function_event( + for step_event in all_text_event( ctx, self.name, - 'materials_plan_function_call', - { - 'msg': f'According to the plan, I will call the `{step['tool_name']}`: {step['description']}' - }, + separate_card(f"{i18n.t('Step')} {index + 1}"), ModelRole, ): - yield materials_plan_function_call_event + yield step_event - logger.info( - f'{ctx.session.id} Before Run: plan_index = {ctx.session.state["plan_index"]}, plan = {ctx.session.state['plan']}' - ) - for step_event in all_text_event( - ctx, - self.name, - separate_card(f"{i18n.t('Step')} {index + 1}"), - ModelRole, - ): - yield step_event + async for event in target_agent.run_async(ctx): + yield event + logger.info( + f'{ctx.session.id} After Run: plan = {ctx.session.state['plan']}, {check_plan(ctx)}' + ) - async for event in target_agent.run_async(ctx): - yield event - logger.info( - f'{ctx.session.id} After Run: plan = {ctx.session.state['plan']}, {check_plan(ctx)}' - ) + current_steps = ctx.session.state['plan']['steps'] + if current_steps[index]['status'] == PlanStepStatusEnum.SUCCESS: + break # 成功,退出重试 + elif current_steps[index]['status'] == PlanStepStatusEnum.FAILED and retry_count < max_retries: + retry_count += 1 + logger.info(f'{ctx.session.id} Step {index + 1} failed, retrying {retry_count}/{max_retries}') + # 重置状态为 PROCESS 以便重试 + update_plan = copy.deepcopy(ctx.session.state['plan']) + update_plan['steps'][index]['status'] = PlanStepStatusEnum.PROCESS + yield update_state_event(ctx, state_delta={'plan': update_plan}) + else: + break # 超过重试次数或非失败状态,退出 - current_steps = ctx.session.state['plan']['steps'] - if ( - current_steps[index]['status'] != PlanStepStatusEnum.SUCCESS - ): # 如果上一步没成功,退出 + if current_steps[index]['status'] != PlanStepStatusEnum.SUCCESS: # 如果上一步没成功,退出 break From 5b6eb2e94fc118ab34bd5b95db844a698e3e93b2 Mon Sep 17 00:00:00 2001 From: beform88 <2298266722@qq.com> Date: Fri, 26 Dec 2025 08:20:03 +0000 Subject: [PATCH 02/15] . --- agents/matmaster_agent/sub_agents/tools.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/agents/matmaster_agent/sub_agents/tools.py b/agents/matmaster_agent/sub_agents/tools.py index c851905d..3ad4f330 100644 --- a/agents/matmaster_agent/sub_agents/tools.py +++ b/agents/matmaster_agent/sub_agents/tools.py @@ -1327,9 +1327,9 @@ 'description': ( 'What it does: Analyze electron microscope images for particles and morphology.\n' 'When to use: When you have TEM/SEM images to analyze.\n' - 'Prerequisites / Inputs: Electron microscope images.\n' + 'Prerequisites / Inputs: Electron microscope images(.tif, .tiff, .png, .jpeg, .jpg).\n' 'Outputs: Detected particles, morphology, geometric properties.\n' - 'Cannot do / Limits: Computer vision-based.\n' + 'Cannot do / Limits: Only support .tif, .tiff, .png, .jpeg, .jpg format files.\n' 'Cost / Notes: Medium.' ), }, From a892d3444d2c715fd94beccb6d7a38a04e78e6ea Mon Sep 17 00:00:00 2001 From: beform88 <2298266722@qq.com> Date: Fri, 26 Dec 2025 08:23:50 +0000 Subject: [PATCH 03/15] . --- agents/matmaster_agent/flow_agents/execution_agent/agent.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/agents/matmaster_agent/flow_agents/execution_agent/agent.py b/agents/matmaster_agent/flow_agents/execution_agent/agent.py index 288096a8..95d089ac 100644 --- a/agents/matmaster_agent/flow_agents/execution_agent/agent.py +++ b/agents/matmaster_agent/flow_agents/execution_agent/agent.py @@ -72,7 +72,7 @@ async def _run_events(self, ctx: InvocationContext) -> AsyncGenerator[Event, Non PlanStepStatusEnum.FAILED, PlanStepStatusEnum.SUBMITTED, ]: - max_retries = 3 + max_retries = 2 retry_count = 0 while retry_count <= max_retries: if step['status'] != PlanStepStatusEnum.SUBMITTED: From baa3009afbae8035d2b5c8814ac8326291eacc9b Mon Sep 17 00:00:00 2001 From: beform88 <2298266722@qq.com> Date: Fri, 26 Dec 2025 08:26:05 +0000 Subject: [PATCH 04/15] . --- .../flow_agents/execution_agent/agent.py | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/agents/matmaster_agent/flow_agents/execution_agent/agent.py b/agents/matmaster_agent/flow_agents/execution_agent/agent.py index 95d089ac..87108371 100644 --- a/agents/matmaster_agent/flow_agents/execution_agent/agent.py +++ b/agents/matmaster_agent/flow_agents/execution_agent/agent.py @@ -81,7 +81,8 @@ async def _run_events(self, ctx: InvocationContext) -> AsyncGenerator[Event, Non 'status' ] = PlanStepStatusEnum.PROCESS yield update_state_event( - ctx, state_delta={'plan': update_plan, 'plan_index': index} + ctx, + state_delta={'plan': update_plan, 'plan_index': index}, ) for ( materials_plan_function_call_event @@ -116,15 +117,26 @@ async def _run_events(self, ctx: InvocationContext) -> AsyncGenerator[Event, Non current_steps = ctx.session.state['plan']['steps'] if current_steps[index]['status'] == PlanStepStatusEnum.SUCCESS: break # 成功,退出重试 - elif current_steps[index]['status'] == PlanStepStatusEnum.FAILED and retry_count < max_retries: + elif ( + current_steps[index]['status'] == PlanStepStatusEnum.FAILED + and retry_count < max_retries + ): retry_count += 1 - logger.info(f'{ctx.session.id} Step {index + 1} failed, retrying {retry_count}/{max_retries}') + logger.info( + f'{ctx.session.id} Step {index + 1} failed, retrying {retry_count}/{max_retries}' + ) # 重置状态为 PROCESS 以便重试 update_plan = copy.deepcopy(ctx.session.state['plan']) - update_plan['steps'][index]['status'] = PlanStepStatusEnum.PROCESS - yield update_state_event(ctx, state_delta={'plan': update_plan}) + update_plan['steps'][index][ + 'status' + ] = PlanStepStatusEnum.PROCESS + yield update_state_event( + ctx, state_delta={'plan': update_plan} + ) else: break # 超过重试次数或非失败状态,退出 - if current_steps[index]['status'] != PlanStepStatusEnum.SUCCESS: # 如果上一步没成功,退出 + if ( + current_steps[index]['status'] != PlanStepStatusEnum.SUCCESS + ): # 如果上一步没成功,退出 break From db3f4cbf884acb93ae3405557a89a631a3dc4200 Mon Sep 17 00:00:00 2001 From: beform88 <2298266722@qq.com> Date: Wed, 31 Dec 2025 05:39:24 +0000 Subject: [PATCH 05/15] . --- .../flow_agents/execution_agent/agent.py | 95 +++++++++++++++---- .../step_validation_agent/__init__.py | 5 + .../step_validation_agent/agent.py | 30 ++++++ .../step_validation_agent/prompt.py | 41 ++++++++ .../step_validation_agent/schema.py | 7 ++ agents/matmaster_agent/locales.py | 2 + 6 files changed, 162 insertions(+), 18 deletions(-) create mode 100644 agents/matmaster_agent/flow_agents/step_validation_agent/__init__.py create mode 100644 agents/matmaster_agent/flow_agents/step_validation_agent/agent.py create mode 100644 agents/matmaster_agent/flow_agents/step_validation_agent/prompt.py create mode 100644 agents/matmaster_agent/flow_agents/step_validation_agent/schema.py diff --git a/agents/matmaster_agent/flow_agents/execution_agent/agent.py b/agents/matmaster_agent/flow_agents/execution_agent/agent.py index 87108371..895f2695 100644 --- a/agents/matmaster_agent/flow_agents/execution_agent/agent.py +++ b/agents/matmaster_agent/flow_agents/execution_agent/agent.py @@ -18,6 +18,12 @@ check_plan, get_agent_name, ) +from agents.matmaster_agent.flow_agents.step_validation_agent.agent import ( + StepValidationAgent, +) +from agents.matmaster_agent.flow_agents.step_validation_agent.prompt import ( + STEP_VALIDATION_INSTRUCTION, +) from agents.matmaster_agent.llm_config import MatMasterLlmConfig from agents.matmaster_agent.locales import i18n from agents.matmaster_agent.logger import PrefixFilter @@ -52,6 +58,9 @@ def after_init(self): MatMasterLlmConfig.opik_tracer.after_model_callback, ] + # Initialize validation agent + self._validation_agent = StepValidationAgent(state_key='step_validation') + return self @override @@ -100,10 +109,15 @@ async def _run_events(self, ctx: InvocationContext) -> AsyncGenerator[Event, Non logger.info( f'{ctx.session.id} Before Run: plan_index = {ctx.session.state["plan_index"]}, plan = {ctx.session.state['plan']}' ) + if retry_count != 0: + separate_card_info = 'ReExecuteSteps' + else: + separate_card_info = 'Step' + for step_event in all_text_event( ctx, self.name, - separate_card(f"{i18n.t('Step')} {index + 1}"), + separate_card(f"{i18n.t(separate_card_info)} {index + 1}"), ModelRole, ): yield step_event @@ -116,25 +130,70 @@ async def _run_events(self, ctx: InvocationContext) -> AsyncGenerator[Event, Non current_steps = ctx.session.state['plan']['steps'] if current_steps[index]['status'] == PlanStepStatusEnum.SUCCESS: - break # 成功,退出重试 - elif ( - current_steps[index]['status'] == PlanStepStatusEnum.FAILED - and retry_count < max_retries - ): + # 执行结果校验 + # 获取执行结果 - 从步骤状态或最近的事件中提取 + execution_result = current_steps[index].get('result', '') + if not execution_result: + # 尝试从会话状态获取最后的结果 + execution_result = ctx.session.state.get('last_execution_result', '') + + validation_instruction = f""" + 用户原始请求: {ctx.user_content.parts[0].text} + 当前步骤描述: {step['description']} + 工具名称: {step['tool_name']} + 步骤执行结果: {execution_result} + + 请根据以上信息判断,工具的参数配置及对应的执行结果是否严格满足用户原始需求。 + """ + self._validation_agent.instruction = STEP_VALIDATION_INSTRUCTION + validation_instruction + + async for validation_event in self._validation_agent.run_async(ctx): + yield validation_event + + validation_result = ctx.session.state.get('step_validation', {}) + is_valid = validation_result.get('is_valid', True) + validation_reason = validation_result.get('reason', '') + + if ((not is_valid) and retry_count < max_retries): + retry_count += 1 + logger.warning(f'{ctx.session.id} Step {index + 1} validation failed: {validation_reason}') + # 校验失败,标记为失败状态并准备重试 + update_plan = copy.deepcopy(ctx.session.state['plan']) + update_plan['steps'][index]['status'] = PlanStepStatusEnum.PROCESS + update_plan['steps'][index]['validation_failure_reason'] = validation_reason + original_description = step['description'] + update_plan['steps'][index]['description'] = f"{original_description}\n\n注意:上次执行因以下原因校验失败,请改进:{validation_reason}" + yield update_state_event(ctx, state_delta={'plan': update_plan}) + # 继续循环进行重试 + else: + # 校验成功,步骤完成 + break + elif (current_steps[index]['status'] == PlanStepStatusEnum.FAILED and retry_count < max_retries): + # 执行失败,检查是否可以重试 retry_count += 1 - logger.info( - f'{ctx.session.id} Step {index + 1} failed, retrying {retry_count}/{max_retries}' - ) - # 重置状态为 PROCESS 以便重试 - update_plan = copy.deepcopy(ctx.session.state['plan']) - update_plan['steps'][index][ - 'status' - ] = PlanStepStatusEnum.PROCESS - yield update_state_event( - ctx, state_delta={'plan': update_plan} - ) + validation_reason = current_steps[index].get('validation_failure_reason', '') + if validation_reason: + logger.info( + f'{ctx.session.id} Step {index + 1} failed due to validation, retrying {retry_count}/{max_retries}. Reason: {validation_reason}' + ) + # 在重试时更新步骤描述,包含校验失败的原因 + update_plan = copy.deepcopy(ctx.session.state['plan']) + original_description = step['description'] + update_plan['steps'][index]['description'] = f"{original_description}\n\n注意:上次执行因以下原因校验失败,请改进:{validation_reason}" + update_plan['steps'][index]['status'] = PlanStepStatusEnum.PROCESS + yield update_state_event(ctx, state_delta={'plan': update_plan}) + else: + logger.info( + f'{ctx.session.id} Step {index + 1} execution failed, retrying {retry_count}/{max_retries}' + ) + # 重置状态为 PROCESS 以便重试 + update_plan = copy.deepcopy(ctx.session.state['plan']) + update_plan['steps'][index]['status'] = PlanStepStatusEnum.PROCESS + yield update_state_event(ctx, state_delta={'plan': update_plan}) + else: - break # 超过重试次数或非失败状态,退出 + # 其他状态(SUBMITTED等),退出循环 + break if ( current_steps[index]['status'] != PlanStepStatusEnum.SUCCESS diff --git a/agents/matmaster_agent/flow_agents/step_validation_agent/__init__.py b/agents/matmaster_agent/flow_agents/step_validation_agent/__init__.py new file mode 100644 index 00000000..c98b60bc --- /dev/null +++ b/agents/matmaster_agent/flow_agents/step_validation_agent/__init__.py @@ -0,0 +1,5 @@ +from agents.matmaster_agent.flow_agents.step_validation_agent.agent import ( + StepValidationAgent, +) + +__all__ = ['StepValidationAgent'] \ No newline at end of file diff --git a/agents/matmaster_agent/flow_agents/step_validation_agent/agent.py b/agents/matmaster_agent/flow_agents/step_validation_agent/agent.py new file mode 100644 index 00000000..8e80cfcd --- /dev/null +++ b/agents/matmaster_agent/flow_agents/step_validation_agent/agent.py @@ -0,0 +1,30 @@ +import logging + +from agents.matmaster_agent.constant import MATMASTER_AGENT_NAME +from agents.matmaster_agent.core_agents.base_agents.schema_agent import ( + DisallowTransferAndContentLimitSchemaAgent, +) +from agents.matmaster_agent.flow_agents.step_validation_agent.prompt import ( + STEP_VALIDATION_INSTRUCTION, +) +from agents.matmaster_agent.flow_agents.step_validation_agent.schema import ( + StepValidationSchema, +) +from agents.matmaster_agent.llm_config import MatMasterLlmConfig +from agents.matmaster_agent.logger import PrefixFilter + +logger = logging.getLogger(__name__) +logger.addFilter(PrefixFilter(MATMASTER_AGENT_NAME)) +logger.setLevel(logging.INFO) + + +class StepValidationAgent(DisallowTransferAndContentLimitSchemaAgent): + def __init__(self, **kwargs): + super().__init__( + name='step_validation_agent', + model=MatMasterLlmConfig.tool_schema_model, + description='校验步骤执行结果是否合理', + instruction=STEP_VALIDATION_INSTRUCTION, + output_schema=StepValidationSchema, + **kwargs + ) \ No newline at end of file diff --git a/agents/matmaster_agent/flow_agents/step_validation_agent/prompt.py b/agents/matmaster_agent/flow_agents/step_validation_agent/prompt.py new file mode 100644 index 00000000..d0ccbbff --- /dev/null +++ b/agents/matmaster_agent/flow_agents/step_validation_agent/prompt.py @@ -0,0 +1,41 @@ +STEP_VALIDATION_INSTRUCTION = """ +You are a validation agent responsible for checking if the execution result of a step matches the user's requirements and basic chemical/materials science knowledge. + +Your task is to analyze: +1. The user's original request +2. The current step's description and purpose +3. The execution result/output +4. Basic chemical and materials science principles + +Based on this analysis, determine if the result is reasonable and matches expectations. + +# Validation Criteria: +1. **Relevance**: Does the result address the step's intended purpose? +2. **Accuracy**: Is the result consistent with basic chemical/materials science knowledge? +3. **Completeness**: Does the result provide the expected information/output? +4. **Reasonableness**: Are the values, predictions, or conclusions logical? + +# Output Format: +You must respond with a JSON object containing: +{ + "is_valid": boolean, // true if result matches requirements and knowledge, false otherwise + "reason": "string", // brief explanation of validation result + "confidence": "high|medium|low" // confidence level in the validation +} + +# Important Rules: +- If the result contains obvious errors (wrong chemical formulas, impossible physical properties, etc.), mark as invalid +- If the result is incomplete or doesn't address the step's purpose, mark as invalid +- If the result contradicts basic chemical principles, mark as invalid +- Only mark as valid if the result reasonably matches expectations +- Be conservative: when in doubt, mark as invalid to ensure quality + +# Examples of Invalid Results: +- Predicting a metal with negative melting point +- Chemical formula with wrong valence (e.g., NaCl3) +- Material property values that violate physical laws +- Results that don't relate to the step's described purpose +- Incomplete or missing expected outputs + +Output only the JSON object, no additional text. +""" \ No newline at end of file diff --git a/agents/matmaster_agent/flow_agents/step_validation_agent/schema.py b/agents/matmaster_agent/flow_agents/step_validation_agent/schema.py new file mode 100644 index 00000000..1b730b16 --- /dev/null +++ b/agents/matmaster_agent/flow_agents/step_validation_agent/schema.py @@ -0,0 +1,7 @@ +from pydantic import BaseModel + + +class StepValidationSchema(BaseModel): + is_valid: bool + reason: str + confidence: str # "high", "medium", "low" \ No newline at end of file diff --git a/agents/matmaster_agent/locales.py b/agents/matmaster_agent/locales.py index 11a249df..0b3d1479 100644 --- a/agents/matmaster_agent/locales.py +++ b/agents/matmaster_agent/locales.py @@ -18,6 +18,7 @@ 'RePlan': 'Re-plan with Different Tool(s)', 'MoreQuestions': 'You may also be interested in these questions:', 'Step': 'Step', + 'ReExecuteSteps':'Re-execute Step', 'PlanSummary': 'Plan Summary', 'NoFoundStructure': 'No eligible structures found.', 'WalletNoFee': 'Insufficient wallet balance', @@ -40,6 +41,7 @@ 'RePlan': '更换工具重新规划', 'MoreQuestions': '你或许还对这些问题感兴趣:', 'Step': '步骤', + 'ReExecuteSteps':'重试步骤', 'PlanSummary': '计划汇总概要', 'NoFoundStructure': '未找到符合条件的结构', 'WalletNoFee': '钱包余额不足', From ea629393ac16e24e0398bf3b21cf66af55745a23 Mon Sep 17 00:00:00 2001 From: beform88 <2298266722@qq.com> Date: Wed, 31 Dec 2025 05:40:55 +0000 Subject: [PATCH 06/15] . --- .../flow_agents/execution_agent/agent.py | 87 +++++++++++++------ .../step_validation_agent/__init__.py | 2 +- .../step_validation_agent/agent.py | 4 +- .../step_validation_agent/prompt.py | 2 +- .../step_validation_agent/schema.py | 2 +- agents/matmaster_agent/locales.py | 4 +- 6 files changed, 67 insertions(+), 34 deletions(-) diff --git a/agents/matmaster_agent/flow_agents/execution_agent/agent.py b/agents/matmaster_agent/flow_agents/execution_agent/agent.py index 895f2695..6d388eeb 100644 --- a/agents/matmaster_agent/flow_agents/execution_agent/agent.py +++ b/agents/matmaster_agent/flow_agents/execution_agent/agent.py @@ -13,17 +13,17 @@ ) from agents.matmaster_agent.flow_agents.constant import MATMASTER_SUPERVISOR_AGENT from agents.matmaster_agent.flow_agents.model import PlanStepStatusEnum -from agents.matmaster_agent.flow_agents.style import separate_card -from agents.matmaster_agent.flow_agents.utils import ( - check_plan, - get_agent_name, -) from agents.matmaster_agent.flow_agents.step_validation_agent.agent import ( StepValidationAgent, ) from agents.matmaster_agent.flow_agents.step_validation_agent.prompt import ( STEP_VALIDATION_INSTRUCTION, ) +from agents.matmaster_agent.flow_agents.style import separate_card +from agents.matmaster_agent.flow_agents.utils import ( + check_plan, + get_agent_name, +) from agents.matmaster_agent.llm_config import MatMasterLlmConfig from agents.matmaster_agent.locales import i18n from agents.matmaster_agent.logger import PrefixFilter @@ -135,43 +135,66 @@ async def _run_events(self, ctx: InvocationContext) -> AsyncGenerator[Event, Non execution_result = current_steps[index].get('result', '') if not execution_result: # 尝试从会话状态获取最后的结果 - execution_result = ctx.session.state.get('last_execution_result', '') - + execution_result = ctx.session.state.get( + 'last_execution_result', '' + ) + validation_instruction = f""" 用户原始请求: {ctx.user_content.parts[0].text} 当前步骤描述: {step['description']} 工具名称: {step['tool_name']} 步骤执行结果: {execution_result} - + 请根据以上信息判断,工具的参数配置及对应的执行结果是否严格满足用户原始需求。 """ - self._validation_agent.instruction = STEP_VALIDATION_INSTRUCTION + validation_instruction - - async for validation_event in self._validation_agent.run_async(ctx): + self._validation_agent.instruction = ( + STEP_VALIDATION_INSTRUCTION + validation_instruction + ) + + async for ( + validation_event + ) in self._validation_agent.run_async(ctx): yield validation_event - - validation_result = ctx.session.state.get('step_validation', {}) + + validation_result = ctx.session.state.get( + 'step_validation', {} + ) is_valid = validation_result.get('is_valid', True) validation_reason = validation_result.get('reason', '') - - if ((not is_valid) and retry_count < max_retries): + + if (not is_valid) and retry_count < max_retries: retry_count += 1 - logger.warning(f'{ctx.session.id} Step {index + 1} validation failed: {validation_reason}') + logger.warning( + f'{ctx.session.id} Step {index + 1} validation failed: {validation_reason}' + ) # 校验失败,标记为失败状态并准备重试 update_plan = copy.deepcopy(ctx.session.state['plan']) - update_plan['steps'][index]['status'] = PlanStepStatusEnum.PROCESS - update_plan['steps'][index]['validation_failure_reason'] = validation_reason + update_plan['steps'][index][ + 'status' + ] = PlanStepStatusEnum.PROCESS + update_plan['steps'][index][ + 'validation_failure_reason' + ] = validation_reason original_description = step['description'] - update_plan['steps'][index]['description'] = f"{original_description}\n\n注意:上次执行因以下原因校验失败,请改进:{validation_reason}" - yield update_state_event(ctx, state_delta={'plan': update_plan}) + update_plan['steps'][index][ + 'description' + ] = f"{original_description}\n\n注意:上次执行因以下原因校验失败,请改进:{validation_reason}" + yield update_state_event( + ctx, state_delta={'plan': update_plan} + ) # 继续循环进行重试 else: # 校验成功,步骤完成 break - elif (current_steps[index]['status'] == PlanStepStatusEnum.FAILED and retry_count < max_retries): + elif ( + current_steps[index]['status'] == PlanStepStatusEnum.FAILED + and retry_count < max_retries + ): # 执行失败,检查是否可以重试 retry_count += 1 - validation_reason = current_steps[index].get('validation_failure_reason', '') + validation_reason = current_steps[index].get( + 'validation_failure_reason', '' + ) if validation_reason: logger.info( f'{ctx.session.id} Step {index + 1} failed due to validation, retrying {retry_count}/{max_retries}. Reason: {validation_reason}' @@ -179,17 +202,27 @@ async def _run_events(self, ctx: InvocationContext) -> AsyncGenerator[Event, Non # 在重试时更新步骤描述,包含校验失败的原因 update_plan = copy.deepcopy(ctx.session.state['plan']) original_description = step['description'] - update_plan['steps'][index]['description'] = f"{original_description}\n\n注意:上次执行因以下原因校验失败,请改进:{validation_reason}" - update_plan['steps'][index]['status'] = PlanStepStatusEnum.PROCESS - yield update_state_event(ctx, state_delta={'plan': update_plan}) + update_plan['steps'][index][ + 'description' + ] = f"{original_description}\n\n注意:上次执行因以下原因校验失败,请改进:{validation_reason}" + update_plan['steps'][index][ + 'status' + ] = PlanStepStatusEnum.PROCESS + yield update_state_event( + ctx, state_delta={'plan': update_plan} + ) else: logger.info( f'{ctx.session.id} Step {index + 1} execution failed, retrying {retry_count}/{max_retries}' ) # 重置状态为 PROCESS 以便重试 update_plan = copy.deepcopy(ctx.session.state['plan']) - update_plan['steps'][index]['status'] = PlanStepStatusEnum.PROCESS - yield update_state_event(ctx, state_delta={'plan': update_plan}) + update_plan['steps'][index][ + 'status' + ] = PlanStepStatusEnum.PROCESS + yield update_state_event( + ctx, state_delta={'plan': update_plan} + ) else: # 其他状态(SUBMITTED等),退出循环 diff --git a/agents/matmaster_agent/flow_agents/step_validation_agent/__init__.py b/agents/matmaster_agent/flow_agents/step_validation_agent/__init__.py index c98b60bc..41de956a 100644 --- a/agents/matmaster_agent/flow_agents/step_validation_agent/__init__.py +++ b/agents/matmaster_agent/flow_agents/step_validation_agent/__init__.py @@ -2,4 +2,4 @@ StepValidationAgent, ) -__all__ = ['StepValidationAgent'] \ No newline at end of file +__all__ = ['StepValidationAgent'] diff --git a/agents/matmaster_agent/flow_agents/step_validation_agent/agent.py b/agents/matmaster_agent/flow_agents/step_validation_agent/agent.py index 8e80cfcd..245edfca 100644 --- a/agents/matmaster_agent/flow_agents/step_validation_agent/agent.py +++ b/agents/matmaster_agent/flow_agents/step_validation_agent/agent.py @@ -26,5 +26,5 @@ def __init__(self, **kwargs): description='校验步骤执行结果是否合理', instruction=STEP_VALIDATION_INSTRUCTION, output_schema=StepValidationSchema, - **kwargs - ) \ No newline at end of file + **kwargs, + ) diff --git a/agents/matmaster_agent/flow_agents/step_validation_agent/prompt.py b/agents/matmaster_agent/flow_agents/step_validation_agent/prompt.py index d0ccbbff..47a93c81 100644 --- a/agents/matmaster_agent/flow_agents/step_validation_agent/prompt.py +++ b/agents/matmaster_agent/flow_agents/step_validation_agent/prompt.py @@ -38,4 +38,4 @@ - Incomplete or missing expected outputs Output only the JSON object, no additional text. -""" \ No newline at end of file +""" diff --git a/agents/matmaster_agent/flow_agents/step_validation_agent/schema.py b/agents/matmaster_agent/flow_agents/step_validation_agent/schema.py index 1b730b16..20cd2b32 100644 --- a/agents/matmaster_agent/flow_agents/step_validation_agent/schema.py +++ b/agents/matmaster_agent/flow_agents/step_validation_agent/schema.py @@ -4,4 +4,4 @@ class StepValidationSchema(BaseModel): is_valid: bool reason: str - confidence: str # "high", "medium", "low" \ No newline at end of file + confidence: str # "high", "medium", "low" diff --git a/agents/matmaster_agent/locales.py b/agents/matmaster_agent/locales.py index 0b3d1479..532a22d8 100644 --- a/agents/matmaster_agent/locales.py +++ b/agents/matmaster_agent/locales.py @@ -18,7 +18,7 @@ 'RePlan': 'Re-plan with Different Tool(s)', 'MoreQuestions': 'You may also be interested in these questions:', 'Step': 'Step', - 'ReExecuteSteps':'Re-execute Step', + 'ReExecuteSteps': 'Re-execute Step', 'PlanSummary': 'Plan Summary', 'NoFoundStructure': 'No eligible structures found.', 'WalletNoFee': 'Insufficient wallet balance', @@ -41,7 +41,7 @@ 'RePlan': '更换工具重新规划', 'MoreQuestions': '你或许还对这些问题感兴趣:', 'Step': '步骤', - 'ReExecuteSteps':'重试步骤', + 'ReExecuteSteps': '重试步骤', 'PlanSummary': '计划汇总概要', 'NoFoundStructure': '未找到符合条件的结构', 'WalletNoFee': '钱包余额不足', From 30bf534d858d9a866d7e36d1f650bd4efff83493 Mon Sep 17 00:00:00 2001 From: beform88 <2298266722@qq.com> Date: Wed, 31 Dec 2025 06:40:15 +0000 Subject: [PATCH 07/15] . --- .../flow_agents/execution_agent/agent.py | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/agents/matmaster_agent/flow_agents/execution_agent/agent.py b/agents/matmaster_agent/flow_agents/execution_agent/agent.py index 6d388eeb..4d470145 100644 --- a/agents/matmaster_agent/flow_agents/execution_agent/agent.py +++ b/agents/matmaster_agent/flow_agents/execution_agent/agent.py @@ -19,7 +19,7 @@ from agents.matmaster_agent.flow_agents.step_validation_agent.prompt import ( STEP_VALIDATION_INSTRUCTION, ) -from agents.matmaster_agent.flow_agents.style import separate_card +from agents.matmaster_agent.flow_agents.style import separate_card, all_summary_card from agents.matmaster_agent.flow_agents.utils import ( check_plan, get_agent_name, @@ -167,6 +167,16 @@ async def _run_events(self, ctx: InvocationContext) -> AsyncGenerator[Event, Non logger.warning( f'{ctx.session.id} Step {index + 1} validation failed: {validation_reason}' ) + + # 向用户显示校验失败信息 + for validation_failed_event in all_text_event( + ctx, + self.name, + f"步骤 {index + 1} 结果校验失败:{validation_reason},正在准备重试...", + ModelRole, + ): + yield validation_failed_event + # 校验失败,标记为失败状态并准备重试 update_plan = copy.deepcopy(ctx.session.state['plan']) update_plan['steps'][index][ @@ -195,6 +205,21 @@ async def _run_events(self, ctx: InvocationContext) -> AsyncGenerator[Event, Non validation_reason = current_steps[index].get( 'validation_failure_reason', '' ) + + # 向用户显示重试信息 + if validation_reason: + retry_message = f"步骤 {index + 1} 执行失败(校验原因:{validation_reason}),正在准备重试..." + else: + retry_message = f"步骤 {index + 1} 执行失败,正在准备重试..." + + for retry_event in all_text_event( + ctx, + self.name, + retry_message, + ModelRole, + ): + yield retry_event + if validation_reason: logger.info( f'{ctx.session.id} Step {index + 1} failed due to validation, retrying {retry_count}/{max_retries}. Reason: {validation_reason}' From e18e7e3b9177f4012976e2c3044b49da960fe9db Mon Sep 17 00:00:00 2001 From: beform88 <2298266722@qq.com> Date: Wed, 31 Dec 2025 06:40:24 +0000 Subject: [PATCH 08/15] . --- agents/matmaster_agent/flow_agents/execution_agent/agent.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/agents/matmaster_agent/flow_agents/execution_agent/agent.py b/agents/matmaster_agent/flow_agents/execution_agent/agent.py index 4d470145..6915b1f7 100644 --- a/agents/matmaster_agent/flow_agents/execution_agent/agent.py +++ b/agents/matmaster_agent/flow_agents/execution_agent/agent.py @@ -19,7 +19,7 @@ from agents.matmaster_agent.flow_agents.step_validation_agent.prompt import ( STEP_VALIDATION_INSTRUCTION, ) -from agents.matmaster_agent.flow_agents.style import separate_card, all_summary_card +from agents.matmaster_agent.flow_agents.style import separate_card from agents.matmaster_agent.flow_agents.utils import ( check_plan, get_agent_name, From 8842f7e5ccd0a7bd2102609d0f4e8bd9f2d8ef5c Mon Sep 17 00:00:00 2001 From: beform88 <2298266722@qq.com> Date: Wed, 31 Dec 2025 06:40:49 +0000 Subject: [PATCH 09/15] . --- .../flow_agents/execution_agent/agent.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/agents/matmaster_agent/flow_agents/execution_agent/agent.py b/agents/matmaster_agent/flow_agents/execution_agent/agent.py index 6915b1f7..0a1b4049 100644 --- a/agents/matmaster_agent/flow_agents/execution_agent/agent.py +++ b/agents/matmaster_agent/flow_agents/execution_agent/agent.py @@ -167,7 +167,7 @@ async def _run_events(self, ctx: InvocationContext) -> AsyncGenerator[Event, Non logger.warning( f'{ctx.session.id} Step {index + 1} validation failed: {validation_reason}' ) - + # 向用户显示校验失败信息 for validation_failed_event in all_text_event( ctx, @@ -176,7 +176,7 @@ async def _run_events(self, ctx: InvocationContext) -> AsyncGenerator[Event, Non ModelRole, ): yield validation_failed_event - + # 校验失败,标记为失败状态并准备重试 update_plan = copy.deepcopy(ctx.session.state['plan']) update_plan['steps'][index][ @@ -205,13 +205,15 @@ async def _run_events(self, ctx: InvocationContext) -> AsyncGenerator[Event, Non validation_reason = current_steps[index].get( 'validation_failure_reason', '' ) - + # 向用户显示重试信息 if validation_reason: retry_message = f"步骤 {index + 1} 执行失败(校验原因:{validation_reason}),正在准备重试..." else: - retry_message = f"步骤 {index + 1} 执行失败,正在准备重试..." - + retry_message = ( + f"步骤 {index + 1} 执行失败,正在准备重试..." + ) + for retry_event in all_text_event( ctx, self.name, @@ -219,7 +221,7 @@ async def _run_events(self, ctx: InvocationContext) -> AsyncGenerator[Event, Non ModelRole, ): yield retry_event - + if validation_reason: logger.info( f'{ctx.session.id} Step {index + 1} failed due to validation, retrying {retry_count}/{max_retries}. Reason: {validation_reason}' From 6b1dd0f2502ce09e86f133dd9df11ba6ef7d7f0a Mon Sep 17 00:00:00 2001 From: beform88 <2298266722@qq.com> Date: Wed, 31 Dec 2025 09:18:22 +0000 Subject: [PATCH 10/15] . --- .../flow_agents/execution_agent/agent.py | 357 ++++++++++-------- .../flow_agents/scene_agent/model.py | 2 + agents/matmaster_agent/flow_agents/utils.py | 14 + agents/matmaster_agent/sub_agents/tools.py | 164 +++++++- 4 files changed, 373 insertions(+), 164 deletions(-) diff --git a/agents/matmaster_agent/flow_agents/execution_agent/agent.py b/agents/matmaster_agent/flow_agents/execution_agent/agent.py index 0a1b4049..159c4d85 100644 --- a/agents/matmaster_agent/flow_agents/execution_agent/agent.py +++ b/agents/matmaster_agent/flow_agents/execution_agent/agent.py @@ -22,6 +22,7 @@ from agents.matmaster_agent.flow_agents.style import separate_card from agents.matmaster_agent.flow_agents.utils import ( check_plan, + find_alternative_tool, get_agent_name, ) from agents.matmaster_agent.llm_config import MatMasterLlmConfig @@ -71,167 +72,239 @@ async def _run_events(self, ctx: InvocationContext) -> AsyncGenerator[Event, Non for index, step in enumerate(steps): if step.get('tool_name'): - target_agent = get_agent_name(step['tool_name'], self.sub_agents) - logger.info( - f'{ctx.session.id} tool_name = {step['tool_name']}, target_agent = {target_agent.name}' - ) - if step['status'] in [ - PlanStepStatusEnum.PLAN, - PlanStepStatusEnum.PROCESS, - PlanStepStatusEnum.FAILED, - PlanStepStatusEnum.SUBMITTED, - ]: - max_retries = 2 - retry_count = 0 - while retry_count <= max_retries: - if step['status'] != PlanStepStatusEnum.SUBMITTED: - update_plan = copy.deepcopy(ctx.session.state['plan']) - update_plan['steps'][index][ - 'status' - ] = PlanStepStatusEnum.PROCESS - yield update_state_event( - ctx, - state_delta={'plan': update_plan, 'plan_index': index}, + tried_tools = [step['tool_name']] + current_tool_name = step['tool_name'] + alternatives = find_alternative_tool(current_tool_name) + tool_attempt_success = False + + while not tool_attempt_success: + target_agent = get_agent_name(current_tool_name, self.sub_agents) + logger.info( + f'{ctx.session.id} tool_name = {current_tool_name}, target_agent = {target_agent.name}' + ) + if step['status'] in [ + PlanStepStatusEnum.PLAN, + PlanStepStatusEnum.PROCESS, + PlanStepStatusEnum.FAILED, + PlanStepStatusEnum.SUBMITTED, + ]: + max_retries = 2 + retry_count = 0 + while retry_count <= max_retries: + if step['status'] != PlanStepStatusEnum.SUBMITTED: + update_plan = copy.deepcopy(ctx.session.state['plan']) + update_plan['steps'][index][ + 'status' + ] = PlanStepStatusEnum.PROCESS + yield update_state_event( + ctx, + state_delta={ + 'plan': update_plan, + 'plan_index': index, + }, + ) + for ( + materials_plan_function_call_event + ) in context_function_event( + ctx, + self.name, + 'materials_plan_function_call', + { + 'msg': f'According to the plan, I will call the `{current_tool_name}`: {step['description']}' + }, + ModelRole, + ): + yield materials_plan_function_call_event + + logger.info( + f'{ctx.session.id} Before Run: plan_index = {ctx.session.state["plan_index"]}, plan = {ctx.session.state['plan']}' ) - for ( - materials_plan_function_call_event - ) in context_function_event( + if retry_count != 0 or separate_card_info != 'Step': + separate_card_info = 'ReExecuteSteps' + else: + separate_card_info = 'Step' + + for step_event in all_text_event( ctx, self.name, - 'materials_plan_function_call', - { - 'msg': f'According to the plan, I will call the `{step['tool_name']}`: {step['description']}' - }, + separate_card( + f"{i18n.t(separate_card_info)} {index + 1}" + ), ModelRole, ): - yield materials_plan_function_call_event + yield step_event - logger.info( - f'{ctx.session.id} Before Run: plan_index = {ctx.session.state["plan_index"]}, plan = {ctx.session.state['plan']}' - ) - if retry_count != 0: - separate_card_info = 'ReExecuteSteps' - else: - separate_card_info = 'Step' + async for event in target_agent.run_async(ctx): + yield event + logger.info( + f'{ctx.session.id} After Run: plan = {ctx.session.state['plan']}, {check_plan(ctx)}' + ) - for step_event in all_text_event( - ctx, - self.name, - separate_card(f"{i18n.t(separate_card_info)} {index + 1}"), - ModelRole, - ): - yield step_event + current_steps = ctx.session.state['plan']['steps'] + if ( + current_steps[index]['status'] + == PlanStepStatusEnum.SUCCESS + ): + # 执行结果校验 + # 获取执行结果 - 从步骤状态或最近的事件中提取 + execution_result = current_steps[index].get( + 'result', '' + ) + if not execution_result: + # 尝试从会话状态获取最后的结果 + execution_result = ctx.session.state.get( + 'last_execution_result', '' + ) - async for event in target_agent.run_async(ctx): - yield event - logger.info( - f'{ctx.session.id} After Run: plan = {ctx.session.state['plan']}, {check_plan(ctx)}' - ) + validation_instruction = f""" + 用户原始请求: {ctx.user_content.parts[0].text} + 当前步骤描述: {step['description']} + 工具名称: {current_tool_name} + 步骤执行结果: {execution_result} - current_steps = ctx.session.state['plan']['steps'] - if current_steps[index]['status'] == PlanStepStatusEnum.SUCCESS: - # 执行结果校验 - # 获取执行结果 - 从步骤状态或最近的事件中提取 - execution_result = current_steps[index].get('result', '') - if not execution_result: - # 尝试从会话状态获取最后的结果 - execution_result = ctx.session.state.get( - 'last_execution_result', '' + 请根据以上信息判断,工具的参数配置及对应的执行结果是否严格满足用户原始需求。 + """ + self._validation_agent.instruction = ( + STEP_VALIDATION_INSTRUCTION + validation_instruction ) - validation_instruction = f""" - 用户原始请求: {ctx.user_content.parts[0].text} - 当前步骤描述: {step['description']} - 工具名称: {step['tool_name']} - 步骤执行结果: {execution_result} + async for ( + validation_event + ) in self._validation_agent.run_async(ctx): + yield validation_event - 请根据以上信息判断,工具的参数配置及对应的执行结果是否严格满足用户原始需求。 - """ - self._validation_agent.instruction = ( - STEP_VALIDATION_INSTRUCTION + validation_instruction - ) + validation_result = ctx.session.state.get( + 'step_validation', {} + ) + is_valid = validation_result.get('is_valid', True) + validation_reason = validation_result.get('reason', '') - async for ( - validation_event - ) in self._validation_agent.run_async(ctx): - yield validation_event + if (not is_valid) and retry_count < max_retries: + retry_count += 1 + logger.warning( + f'{ctx.session.id} Step {index + 1} validation failed: {validation_reason}' + ) - validation_result = ctx.session.state.get( - 'step_validation', {} - ) - is_valid = validation_result.get('is_valid', True) - validation_reason = validation_result.get('reason', '') + # 向用户显示校验失败信息 + for validation_failed_event in all_text_event( + ctx, + self.name, + f"步骤 {index + 1} 结果与用户需求不符:{validation_reason},正在准备重试...", + ModelRole, + ): + yield validation_failed_event - if (not is_valid) and retry_count < max_retries: + # 校验失败,标记为失败状态并准备重试 + update_plan = copy.deepcopy( + ctx.session.state['plan'] + ) + update_plan['steps'][index][ + 'status' + ] = PlanStepStatusEnum.PROCESS + update_plan['steps'][index][ + 'validation_failure_reason' + ] = validation_reason + original_description = step['description'] + update_plan['steps'][index][ + 'description' + ] = f"{original_description}\n\n注意:上次执行因以下原因校验失败,请改进:{validation_reason}" + yield update_state_event( + ctx, state_delta={'plan': update_plan} + ) + # 继续循环进行重试 + else: + # 校验成功,步骤完成 + tool_attempt_success = True + break + elif ( + current_steps[index]['status'] + == PlanStepStatusEnum.FAILED + and retry_count < max_retries + ): + # 执行失败,检查是否可以重试 retry_count += 1 - logger.warning( - f'{ctx.session.id} Step {index + 1} validation failed: {validation_reason}' + validation_reason = current_steps[index].get( + 'validation_failure_reason', '' ) - # 向用户显示校验失败信息 - for validation_failed_event in all_text_event( + # 向用户显示重试信息 + if validation_reason: + retry_message = f"步骤 {index + 1} 执行失败(校验原因:{validation_reason}),正在准备重试..." + else: + retry_message = ( + f"步骤 {index + 1} 执行失败,正在准备重试..." + ) + + for retry_event in all_text_event( ctx, self.name, - f"步骤 {index + 1} 结果校验失败:{validation_reason},正在准备重试...", + retry_message, ModelRole, ): - yield validation_failed_event + yield retry_event - # 校验失败,标记为失败状态并准备重试 - update_plan = copy.deepcopy(ctx.session.state['plan']) - update_plan['steps'][index][ - 'status' - ] = PlanStepStatusEnum.PROCESS - update_plan['steps'][index][ - 'validation_failure_reason' - ] = validation_reason - original_description = step['description'] - update_plan['steps'][index][ - 'description' - ] = f"{original_description}\n\n注意:上次执行因以下原因校验失败,请改进:{validation_reason}" - yield update_state_event( - ctx, state_delta={'plan': update_plan} - ) - # 继续循环进行重试 - else: - # 校验成功,步骤完成 - break - elif ( - current_steps[index]['status'] == PlanStepStatusEnum.FAILED - and retry_count < max_retries - ): - # 执行失败,检查是否可以重试 - retry_count += 1 - validation_reason = current_steps[index].get( - 'validation_failure_reason', '' - ) + if validation_reason: + logger.info( + f'{ctx.session.id} Step {index + 1} failed due to validation, retrying {retry_count}/{max_retries}. Reason: {validation_reason}' + ) + # 在重试时更新步骤描述,包含校验失败的原因 + update_plan = copy.deepcopy( + ctx.session.state['plan'] + ) + original_description = step['description'] + update_plan['steps'][index][ + 'description' + ] = f"{original_description}\n\n注意:上次执行因以下原因校验失败,请改进:{validation_reason}" + update_plan['steps'][index][ + 'status' + ] = PlanStepStatusEnum.PROCESS + yield update_state_event( + ctx, state_delta={'plan': update_plan} + ) + else: + logger.info( + f'{ctx.session.id} Step {index + 1} execution failed, retrying {retry_count}/{max_retries}' + ) + # 重置状态为 PROCESS 以便重试 + update_plan = copy.deepcopy( + ctx.session.state['plan'] + ) + update_plan['steps'][index][ + 'status' + ] = PlanStepStatusEnum.PROCESS + yield update_state_event( + ctx, state_delta={'plan': update_plan} + ) - # 向用户显示重试信息 - if validation_reason: - retry_message = f"步骤 {index + 1} 执行失败(校验原因:{validation_reason}),正在准备重试..." else: - retry_message = ( - f"步骤 {index + 1} 执行失败,正在准备重试..." - ) + # 其他状态(SUBMITTED等),退出循环 + break - for retry_event in all_text_event( - ctx, - self.name, - retry_message, - ModelRole, - ): - yield retry_event + # 如果retry循环结束后仍未成功,尝试alternative工具 + if not tool_attempt_success: + available_alts = [ + alt for alt in alternatives if alt not in tried_tools + ] + if available_alts: + # 向用户显示工具替换信息 + for tool_replace_event in all_text_event( + ctx, + self.name, + f"步骤 {index + 1} 多次重试失败,已找到合适的替代工具:{step['tool_name']} → {available_alts}", + ModelRole, + ): + yield tool_replace_event - if validation_reason: + # 尝试替换工具 + next_tool = available_alts[0] + tried_tools.append(next_tool) + current_tool_name = next_tool logger.info( - f'{ctx.session.id} Step {index + 1} failed due to validation, retrying {retry_count}/{max_retries}. Reason: {validation_reason}' + f'{ctx.session.id} Switching to alternative tool: {next_tool} for step {index + 1}' ) - # 在重试时更新步骤描述,包含校验失败的原因 + # 更新plan中的tool_name和status update_plan = copy.deepcopy(ctx.session.state['plan']) - original_description = step['description'] - update_plan['steps'][index][ - 'description' - ] = f"{original_description}\n\n注意:上次执行因以下原因校验失败,请改进:{validation_reason}" + update_plan['steps'][index]['tool_name'] = next_tool update_plan['steps'][index][ 'status' ] = PlanStepStatusEnum.PROCESS @@ -239,23 +312,11 @@ async def _run_events(self, ctx: InvocationContext) -> AsyncGenerator[Event, Non ctx, state_delta={'plan': update_plan} ) else: + logger.info( - f'{ctx.session.id} Step {index + 1} execution failed, retrying {retry_count}/{max_retries}' - ) - # 重置状态为 PROCESS 以便重试 - update_plan = copy.deepcopy(ctx.session.state['plan']) - update_plan['steps'][index][ - 'status' - ] = PlanStepStatusEnum.PROCESS - yield update_state_event( - ctx, state_delta={'plan': update_plan} + f'{ctx.session.id} No more alternative tools for step {index + 1}' ) + break # 退出tool while - else: - # 其他状态(SUBMITTED等),退出循环 - break - - if ( - current_steps[index]['status'] != PlanStepStatusEnum.SUCCESS - ): # 如果上一步没成功,退出 - break + if not tool_attempt_success: + break # 如果没有成功,退出step for diff --git a/agents/matmaster_agent/flow_agents/scene_agent/model.py b/agents/matmaster_agent/flow_agents/scene_agent/model.py index 832d49b0..5739906b 100644 --- a/agents/matmaster_agent/flow_agents/scene_agent/model.py +++ b/agents/matmaster_agent/flow_agents/scene_agent/model.py @@ -62,6 +62,8 @@ class SceneEnum(DescriptiveEnum): SURFACE_ENERGY = ('surface_energy', '') STACKING_FAULT_ENERGY = ('stacking_fault_energy', '') EOS = ('eos', '') + Electron_Localization_Function = ('electron_localization_function', '') + Nudged_Elastic_Band = ('nudged_elastic_band', '') PHONON = ('phonon', '') SCF = ('scf', 'Self-Consistent Field') BAND = ('band', '') diff --git a/agents/matmaster_agent/flow_agents/utils.py b/agents/matmaster_agent/flow_agents/utils.py index 4f2b0c8c..6e92bac0 100644 --- a/agents/matmaster_agent/flow_agents/utils.py +++ b/agents/matmaster_agent/flow_agents/utils.py @@ -132,3 +132,17 @@ def should_bypass_confirmation(ctx: InvocationContext) -> bool: return True return False + + +def find_alternative_tool(current_tool_name: str) -> Optional[str]: + """Find an alternative tool for the current tool. + + Priority order: + 1. Pre-defined alternatives in ALL_TOOLS + """ + if current_tool_name not in ALL_TOOLS: + return None + + current_tool_info = ALL_TOOLS[current_tool_name] + + return current_tool_info.get('alternative', []) diff --git a/agents/matmaster_agent/sub_agents/tools.py b/agents/matmaster_agent/sub_agents/tools.py index f426147e..d464e9c0 100644 --- a/agents/matmaster_agent/sub_agents/tools.py +++ b/agents/matmaster_agent/sub_agents/tools.py @@ -175,6 +175,7 @@ 'Cannot do / Limits: Only non-charged vacancy of metal atoms; requires supercell for calculation.\n' 'Cost / Notes: DFT calculation cost; supports relaxation before calculation.' ), + 'alternative': ['apex_calculate_vacancy'], }, 'abacus_phonon_dispersion': { 'belonging_agent': ABACUS_AGENT_NAME, @@ -187,6 +188,7 @@ 'Cannot do / Limits: Requires DFT; may need supercell for accuracy.\n' 'Cost / Notes: DFT calculation cost; supports relaxation before calculation.' ), + 'alternative': ['apex_calculate_phonon', 'calculate_phonon'], }, 'abacus_cal_band': { 'belonging_agent': ABACUS_AGENT_NAME, @@ -199,6 +201,7 @@ 'Cannot do / Limits: DFT-based; supports PYATB or ABACUS nscf.\n' 'Cost / Notes: DFT calculation cost; supports relaxation before calculation.' ), + 'alternative': [], }, 'abacus_calculation_scf': { 'belonging_agent': ABACUS_AGENT_NAME, @@ -211,6 +214,7 @@ 'Cannot do / Limits: Basic SCF; no relaxation or other properties.\n' 'Cost / Notes: DFT calculation cost.' ), + 'alternative': [], }, 'abacus_dos_run': { 'belonging_agent': ABACUS_AGENT_NAME, @@ -223,6 +227,7 @@ 'Cannot do / Limits: DFT-based; requires relaxation support.\n' 'Cost / Notes: DFT calculation cost; supports relaxation before calculation.' ), + 'alternative': [], }, 'abacus_badercharge_run': { 'belonging_agent': ABACUS_AGENT_NAME, @@ -235,6 +240,7 @@ 'Cannot do / Limits: DFT-based; requires charge density calculation.\n' 'Cost / Notes: DFT calculation cost; supports relaxation before calculation.' ), + 'alternative': [], }, 'abacus_do_relax': { 'belonging_agent': ABACUS_AGENT_NAME, @@ -247,6 +253,7 @@ 'Cannot do / Limits: DFT-based relaxation.\n' 'Cost / Notes: DFT calculation cost.' ), + 'alternative': ['apex_optimize_structure', 'optimize_structure'], }, 'abacus_cal_work_function': { 'belonging_agent': ABACUS_AGENT_NAME, @@ -259,6 +266,7 @@ 'Cannot do / Limits: For slabs and 2D materials; polar slabs have two work functions.\n' 'Cost / Notes: DFT calculation cost; supports relaxation before calculation.' ), + 'alternative': [], }, 'abacus_run_md': { 'belonging_agent': ABACUS_AGENT_NAME, @@ -271,10 +279,11 @@ 'Cannot do / Limits: DFT-based MD; expensive for long simulations.\n' 'Cost / Notes: High DFT cost; supports relaxation before calculation.' ), + 'alternative': ['run_molecular_dynamics'], }, 'abacus_cal_elf': { 'belonging_agent': ABACUS_AGENT_NAME, - 'scene': [SceneEnum.ABACUS], + 'scene': [SceneEnum.ABACUS, SceneEnum.Electron_Localization_Function], 'description': ( 'What it does: Calculate electron localization function using DFT.\n' 'When to use: When you need ELF for bonding analysis.\n' @@ -283,6 +292,7 @@ 'Cannot do / Limits: DFT-based.\n' 'Cost / Notes: DFT calculation cost; supports relaxation before calculation.' ), + 'alternative': [], }, 'abacus_eos': { 'belonging_agent': ABACUS_AGENT_NAME, @@ -295,6 +305,7 @@ 'Cannot do / Limits: DFT-based.\n' 'Cost / Notes: DFT calculation cost; supports relaxation before calculation.' ), + 'alternative': ['apex_calculate_eos'], }, 'abacus_cal_elastic': { 'belonging_agent': ABACUS_AGENT_NAME, @@ -307,6 +318,7 @@ 'Cannot do / Limits: DFT-based.\n' 'Cost / Notes: DFT calculation cost; supports relaxation before calculation.' ), + 'alternative': ['apex_calculate_elastic', 'calculate_elastic_constants'], }, 'apex_calculate_vacancy': { 'belonging_agent': ApexAgentName, @@ -319,6 +331,7 @@ 'Cannot do / Limits: DFT-based.\n' 'Cost / Notes: DFT calculation cost.' ), + 'alternative': ['abacus_vacancy_formation_energy'], }, 'apex_optimize_structure': { 'belonging_agent': ApexAgentName, @@ -331,6 +344,7 @@ 'Cannot do / Limits: DFT-based.\n' 'Cost / Notes: DFT calculation cost.' ), + 'alternative': ['abacus_do_relax', 'optimize_structure'], }, 'apex_calculate_interstitial': { 'belonging_agent': ApexAgentName, @@ -343,6 +357,7 @@ 'Cannot do / Limits: DFT-based.\n' 'Cost / Notes: DFT calculation cost.' ), + 'alternative': [], }, 'apex_calculate_elastic': { 'belonging_agent': ApexAgentName, @@ -355,6 +370,7 @@ 'Cannot do / Limits: DFT-based.\n' 'Cost / Notes: DFT calculation cost.' ), + 'alternative': ['abacus_cal_elastic', 'calculate_elastic_constants'], }, 'apex_calculate_surface': { 'belonging_agent': ApexAgentName, @@ -367,6 +383,7 @@ 'Cannot do / Limits: Cannot build slab structures; DFT-based.\n' 'Cost / Notes: DFT calculation cost.' ), + 'alternative': [], }, 'apex_calculate_eos': { 'belonging_agent': ApexAgentName, @@ -379,6 +396,7 @@ 'Cannot do / Limits: DFT-based.\n' 'Cost / Notes: DFT calculation cost.' ), + 'alternative': ['abacus_eos'], }, 'apex_calculate_phonon': { 'belonging_agent': ApexAgentName, @@ -391,6 +409,7 @@ 'Cannot do / Limits: DFT-based.\n' 'Cost / Notes: DFT calculation cost.' ), + 'alternative': ['abacus_phonon_dispersion', 'calculate_phonon'], }, 'apex_calculate_gamma': { 'belonging_agent': ApexAgentName, @@ -403,6 +422,7 @@ 'Cannot do / Limits: DFT-based.\n' 'Cost / Notes: DFT calculation cost.' ), + 'alternative': [], }, 'get_target_info': { 'belonging_agent': UniELFAgentName, @@ -415,6 +435,7 @@ 'Cannot do / Limits: Specific to Uni-ELF system.\n' 'Cost / Notes: Low.' ), + 'alternative': [], }, 'unielf_inference': { 'belonging_agent': UniELFAgentName, @@ -427,6 +448,7 @@ 'Cannot do / Limits: Supports mixtures and pseudo-formulations.\n' 'Cost / Notes: Medium.' ), + 'alternative': [], 'summary_prompt': ( 'Summarize the Uni-ELF inference results based on the output:\n' '1. Report the url to the full results CSV file (`result_csv`).\n' @@ -464,6 +486,7 @@ 'Cannot do / Limits: Specific to perovskite database.\n' 'Cost / Notes: Low.' ), + 'alternative': [], }, 'sql_database_mcp': { 'belonging_agent': PerovskiteAgentName, @@ -476,6 +499,7 @@ 'Cannot do / Limits: Limited to first k rows.\n' 'Cost / Notes: Medium.' ), + 'alternative': [], }, 'Unimol_Predict_Perovskite_Additive': { 'belonging_agent': PerovskiteAgentName, @@ -488,6 +512,7 @@ 'Cannot do / Limits: Specific to perovskite additives.\n' 'Cost / Notes: Medium.' ), + 'alternative': [], }, # 'validate_smiles': { # 'belonging_agent': CHEMBRAIN_AGENT_NAME, @@ -505,6 +530,7 @@ 'Cannot do / Limits: Cannot build doping structures; requires proxy model.\n' 'Cost / Notes: Medium.' ), + 'alternative': [], }, 'run_doe_task': { 'belonging_agent': DOE_AGENT_NAME, @@ -517,6 +543,7 @@ 'Cannot do / Limits: Limited to supported algorithms.\n' 'Cost / Notes: Medium.' ), + 'alternative': [], }, 'extract_material_data_from_pdf': { 'belonging_agent': DocumentParserAgentName, @@ -529,6 +556,7 @@ 'Cannot do / Limits: Cannot retrieve from internet; local PDFs only.\n' 'Cost / Notes: Low.' ), + 'alternative': ['file_parse', 'extract_material_data_from_webpage'], 'bypass_confirmation': True, }, 'optimize_structure': { @@ -543,6 +571,7 @@ 'Cost / Notes: Low relative to DFT.' ), 'args_setting': f"{DPA_MODEL_BRANCH_SELECTION}", + 'alternative': ['apex_optimize_structure', 'abacus_do_relax'], }, 'run_molecular_dynamics': { 'belonging_agent': DPACalulator_AGENT_NAME, @@ -556,6 +585,7 @@ 'Cost / Notes: Medium; scales with system size and steps.' ), 'args_setting': f"{DPA_MODEL_BRANCH_SELECTION}", + 'alternative': ['abacus_run_md'], }, 'calculate_phonon': { 'belonging_agent': DPACalulator_AGENT_NAME, @@ -569,6 +599,7 @@ 'Cost / Notes: High; scales with supercell size.' ), 'args_setting': f"{DPA_MODEL_BRANCH_SELECTION}", + 'alternative': ['abacus_phonon_dispersion', 'apex_calculate_phonon'], }, 'calculate_elastic_constants': { 'belonging_agent': DPACalulator_AGENT_NAME, @@ -582,10 +613,11 @@ 'Cost / Notes: Medium; scales with system size and deformation settings.' ), 'args_setting': f"{DPA_MODEL_BRANCH_SELECTION}", + 'alternative': ['abacus_cal_elastic', 'apex_calculate_elastic'], }, 'run_neb': { 'belonging_agent': DPACalulator_AGENT_NAME, - 'scene': [SceneEnum.DPA], + 'scene': [SceneEnum.DPA, SceneEnum.Nudged_Elastic_Band], 'description': ( 'What it does: Run a Nudged Elastic Band (NEB) calculation with a machine-learning potential to estimate minimum energy path and barrier.\n' 'When to use: You have initial/final states (and optionally an initial guess path) and need a fast barrier estimate.\n' @@ -595,6 +627,7 @@ 'Cost / Notes: High relative to single relax; cost scales with number of images and system size.' ), 'args_setting': f"{DPA_MODEL_BRANCH_SELECTION}", + 'alternative': [], }, 'finetune_dpa_model': { 'belonging_agent': FinetuneDPAAgentName, @@ -608,6 +641,7 @@ 'Cost / Notes: High.' ), 'args_setting': 'Do NOT omit parameters that have default values. If the user does not provide a value, you MUST use the default value defined in the input parameters and include that field in the tool call. Only parameters without defaults are truly required and must be filled from user input.', + 'alternative': [], }, 'HEA_params_calculator': { 'belonging_agent': HEA_assistant_AgentName, @@ -620,6 +654,7 @@ 'Cannot do / Limits: Specific to HEA.\n' 'Cost / Notes: Low.' ), + 'alternative': [], }, 'HEA_predictor': { 'belonging_agent': HEA_assistant_AgentName, @@ -632,6 +667,7 @@ 'Cannot do / Limits: Uses pre-trained ML model.\n' 'Cost / Notes: Low.' ), + 'alternative': [], }, 'HEA_comps_generator': { 'belonging_agent': HEA_assistant_AgentName, @@ -644,6 +680,7 @@ 'Cannot do / Limits: Adjusts one element at a time.\n' 'Cost / Notes: Low.' ), + 'alternative': [], }, 'HEA_data_extract': { 'belonging_agent': HEA_assistant_AgentName, @@ -656,6 +693,7 @@ 'Cannot do / Limits: PDF format only.\n' 'Cost / Notes: Low.' ), + 'alternative': [], }, 'HEA_paper_search': { 'belonging_agent': HEA_assistant_AgentName, @@ -668,6 +706,11 @@ 'Cannot do / Limits: arXiv only.\n' 'Cost / Notes: Medium.' ), + 'alternative': [ + 'query_heakb_literature', + 'search-papers-enhanced', + 'web-search', + ], }, 'HEA_bi_phase_Calc': { 'belonging_agent': HEA_assistant_AgentName, @@ -680,6 +723,7 @@ 'Cannot do / Limits: Binary pairs only.\n' 'Cost / Notes: Medium.' ), + 'alternative': [], }, 'generate_binary_phase_diagram': { 'belonging_agent': HEACALCULATOR_AGENT_NAME, @@ -692,12 +736,14 @@ 'Cannot do / Limits: If no dataset/computation route is available, the tool will return an error; results depend on data coverage and model assumptions.\n' 'Cost / Notes: Medium; faster with cached datasets.' ), + 'alternative': [], }, 'query_heakb_literature': { 'belonging_agent': HEA_KB_AGENT_NAME, 'scene': [SceneEnum.HIGH_ENTROPY_ALLOY], 'description': HEAKbAgentToolDescription, 'args_setting': f"{HEAKbAgentArgsSetting}", + 'alternative': ['HEA_paper_search', 'search-papers-enhanced', 'web-search'], 'summary_prompt': HEAKbAgentSummaryPrompt, }, 'query_ssekb_literature': { @@ -705,6 +751,7 @@ 'scene': [SceneEnum.Solid_State_Electrolyte], 'description': SSEKbAgentToolDescription, 'args_setting': f"{SSEKbAgentArgsSetting}", + 'alternative': ['search-papers-enhanced', 'web-search'], 'summary_prompt': SSEKbAgentSummaryPrompt, }, 'query_polymerkb_literature': { @@ -712,6 +759,7 @@ 'scene': [SceneEnum.POLYMER], 'description': POLYMERKbAgentToolDescription, 'args_setting': f"{POLYMERKbAgentArgsSetting}", + 'alternative': ['search-papers-enhanced', 'web-search'], 'summary_prompt': POLYMERKbAgentSummaryPrompt, }, 'query_steelkb_literature': { @@ -719,6 +767,7 @@ 'scene': [SceneEnum.STEEL], 'description': STEELKbAgentToolDescription, 'args_setting': f"{STEELKbAgentArgsSetting}", + 'alternative': ['search-papers-enhanced', 'web-search'], 'summary_prompt': STEELKbAgentSummaryPrompt, }, 'predict_tensile_strength': { @@ -726,6 +775,7 @@ 'scene': [SceneEnum.STEEL], 'description': STEELPredictAgentToolDescription, 'args_setting': f"{STEELPredictAgentArgsSetting}", + 'alternative': ['search-papers-enhanced', 'web-search'], 'summary_prompt': STEELPredictAgentSummaryPrompt, }, 'fetch_structures_with_filter': { @@ -733,6 +783,11 @@ 'scene': [SceneEnum.DATABASE_SEARCH], 'description': OptimadeFilterToolDescription, 'args_setting': f"{OptimadeAgentArgsSetting}", + 'alternative': [ + 'fetch_bohrium_crystals', + 'fetch_openlam_structures', + 'web-search', + ], 'summary_prompt': OptimadeAgentSummaryPrompt, }, 'fetch_structures_with_spg': { @@ -740,6 +795,11 @@ 'scene': [SceneEnum.DATABASE_SEARCH], 'description': OptimadeSpgToolDescription, 'args_setting': f"{OptimadeAgentArgsSetting}", + 'alternative': [ + 'fetch_bohrium_crystals', + 'fetch_structures_with_filter', + 'web-search', + ], 'summary_prompt': OptimadeAgentSummaryPrompt, }, 'fetch_structures_with_bandgap': { @@ -747,6 +807,11 @@ 'scene': [SceneEnum.DATABASE_SEARCH], 'description': OptimadeBandgapToolDescription, 'args_setting': f"{OptimadeAgentArgsSetting}", + 'alternative': [ + 'fetch_bohrium_crystals', + 'fetch_structures_with_filter', + 'web-search', + ], 'summary_prompt': OptimadeAgentSummaryPrompt, }, 'fetch_bohrium_crystals': { @@ -754,6 +819,11 @@ 'scene': [SceneEnum.DATABASE_SEARCH], 'description': BohriumPublicAgentToolDescription, 'args_setting': f"{BohriumPublicAgentArgsSetting}", + 'alternative': [ + 'fetch_structures_with_filter', + 'web-search', + 'fetch_openlam_structures', + ], 'summary_prompt': BohriumPublicAgentSummaryPrompt, }, 'fetch_openlam_structures': { @@ -761,6 +831,11 @@ 'scene': [SceneEnum.DATABASE_SEARCH], 'description': OpenlamAgentToolDescription, 'args_setting': f"{OpenlamAgentArgsSetting}", + 'alternative': [ + 'fetch_structures_with_filter', + 'web-search', + 'fetch_openlam_structures', + ], 'summary_prompt': OpenlamAgentSummaryPrompt, }, 'fetch_mofs_sql': { @@ -768,6 +843,7 @@ 'scene': [SceneEnum.DATABASE_SEARCH], 'description': MofdbAgentToolDescription, 'args_setting': f"{MofdbAgentArgsSetting}", + 'alternative': ['web-search'], 'summary_prompt': MofdbAgentSummaryPrompt, }, 'calculate_reaction_profile': { @@ -781,6 +857,7 @@ 'Cannot do / Limits: Specific to reactions.\n' 'Cost / Notes: Medium.' ), + 'alternative': [], }, 'run_piloteye': { 'belonging_agent': PILOTEYE_ELECTRO_AGENT_NAME, @@ -793,6 +870,7 @@ 'Cannot do / Limits: Built-in molecule library; complete workflow.\n' 'Cost / Notes: High.' ), + 'alternative': [], }, # 'deep_research_agent': { # 'belonging_agent': SSEBRAIN_AGENT_NAME, @@ -816,6 +894,7 @@ 'Cost / Notes: Medium.' ), 'args_setting': 'Parameter guidance: n_tot=10–30 gives reasonable diversity without excessive cost. Elements must be from the supported list (H–Bi, Ac–Pu). Output is a set of POSCAR files; downstream relaxation is strongly recommended.', + 'alternative': [], }, 'generate_crystalformer_structures': { 'belonging_agent': StructureGenerateAgentName, @@ -829,6 +908,7 @@ 'Cost / Notes: High; uses generative model.' ), 'args_setting': 'Parameter guidance: Supported properties: bandgap (eV), shear_modulus, bulk_modulus (both log₁₀ GPa), superconducting ambient_pressure/high_pressure (K), sound (m/s). For target_type="minimize", use small target (e.g., 0.1) and low alpha (0.01); for "equal", "greater", "less", use alpha=1.0. mc_steps=500 balances convergence and speed; increase to 2000 for high-accuracy targets. sample_num=20–100 recommended; distribute across space groups if random_spacegroup_num>0. Critical: Space group must be explicitly specified by the user — no defaults or auto-inference.', + 'alternative': [], }, 'make_supercell_structure': { 'belonging_agent': StructureGenerateAgentName, @@ -842,6 +922,7 @@ 'Cost / Notes: Low.' ), 'args_setting': "Parameter guidance: Primarily follow user's instrucution. If not specified, firstly get structure information to understand the raw lattice. An ideal supercell for computation is isotropic. For example, the raw lattice is (4 A, 10 A, 12 A, 90 deg, 90 deg, 90 deg), the supercell should be 5 × 2 × 2. 30-50 angstrom is often appropriate for simulations. Avoid overly large cells unless needed for long-range interactions.", + 'alternative': [], 'bypass_confirmation': True, }, 'build_bulk_structure_by_template': { @@ -856,6 +937,11 @@ 'Cost / Notes: Low.' ), 'args_setting': f'Parameter guidance: Lattice constant requirements due to symmetry constraints: sc/fcc/bcc/diamond/rocksalt/cesiumchloride/zincblende/fluorite → only a; hcp/wurtzite → a and c; orthorhombic/monoclinic → a, b, c. Set conventional=True by default unless primitive cell is explicitly required. For elements, use element symbols; for compounds, use chemical formula (e.g., "NaCl"). {STRUCTURE_BUILDING_SAVENAME}', + 'alternative': [ + 'fetch_bohrium_crystals', + 'fetch_structures_with_filter', + 'build_bulk_structure_by_wyckoff', + ], 'bypass_confirmation': True, }, 'build_surface_slab': { @@ -870,6 +956,7 @@ 'Cost / Notes: Low.' ), 'args_setting': f'Parameter guidance: Prefer slab_size_mode="layers" with slab_size_value=4–6 for stability; or "thickness" with ≥12 Å for electronic convergence. Use vacuum=15–20 Å to minimize spurious interactions. For polar surfaces or systems with strong dipoles, increase vacuum to ensure the electrostatic potential flattens in the vacuum region. Enable repair=True for covalent materials (e.g., drug-like molecule crystals, oragnic-inorganic hybrids, MOFs); Set false for regular sphrical-like inorganic crystals. Gets slow if set True. Default termination="auto" usually selects the most stoichiometric termination. {STRUCTURE_BUILDING_SAVENAME}', + 'alternative': [], 'bypass_confirmation': True, }, 'build_surface_adsorbate': { @@ -884,6 +971,7 @@ 'Cost / Notes: Low.' ), 'args_setting': f'Parameter guidance: height=2.0 Å is typical for physisorption; reduce to 1.5–1.8 Å for chemisorption (e.g., CO on Pt). For high-symmetry sites, use string keywords ("ontop", "fcc", "hcp"); for custom placement, supply [x, y] fractional coordinates. {STRUCTURE_BUILDING_SAVENAME}', + 'alternative': [], 'bypass_confirmation': True, }, 'build_surface_interface': { @@ -898,6 +986,7 @@ 'Cost / Notes: Low.' ), 'args_setting': f'Parameter guidance: Keep max_strain=0.05 (5%) for physical relevance; relax only if intentional strain engineering is intended. Try combinding make_supercell and get_structural_info to obtain the appropriate size of the two slabs. interface_distance=2.5 Å is safe for van der Waals gaps; reduce to 1.8–2.0 Å for covalent bonding (e.g., heterostructures with orbital overlap). {STRUCTURE_BUILDING_SAVENAME}', + 'alternative': [], 'bypass_confirmation': True, }, 'add_cell_for_molecules': { @@ -912,6 +1001,7 @@ 'Cost / Notes: Low.' ), 'args_setting': f'Parameter guidance: For non-periodic system aiming to run calculations with periodic boundary conditions required (e.g., DFT calculations with ABACUS), use add_cell_for_molecules to put the system in a large cell. Default cell [10, 10, 10] Å and vacuum = 5 Å are suitable for most gas-phase molecules; increase to ≥15 Å and ≥8 Å vacuum for polar or diffuse systems (e.g., anions, excited states). {STRUCTURE_BUILDING_SAVENAME}', + 'alternative': [], 'bypass_confirmation': True, }, 'build_bulk_structure_by_wyckoff': { @@ -926,6 +1016,11 @@ 'Cost / Notes: Low.' ), 'args_setting': f'Parameter guidance: Space Group: Integer (e.g., 225) or Symbol (e.g., "Fm-3m"). Wyckoff Consistency: The provided coordinates must mathematically belong to the specific Wyckoff position (e.g., if using position 4a at (0,0,0), do not input (0.5, 0.5, 0) just because it\'s in the same unit cell; only input the canonical generator). Lattice: Angles in degrees, lengths in Å. Fractional Coordinates: Must be in [0, 1). Strictly Use the Asymmetric Unit: You must provide only the generating coordinates for each Wyckoff orbit. Do NOT Pre-calculate Symmetry: The function will automatically apply all space group operators to your input. If you manually input coordinates that are already symmetry-equivalent (e.g., providing both (x, y, z) and (-x, -y, -z) in a centrosymmetric structure), the function will generate them again, causing catastrophic atom overlapping. Redundancy Rule: Before adding a coordinate, check if it can be generated from an existing input coordinate via any operator in the Space Group. If yes, discard it. One Wyckoff letter = One coordinate triplet input. {STRUCTURE_BUILDING_SAVENAME}', + 'alternative': [ + 'fetch_bohrium_crystals', + 'fetch_structures_with_filter', + 'build_bulk_structure_by_template', + ], 'bypass_confirmation': True, }, 'build_molecule_structures_from_smiles': { @@ -940,6 +1035,7 @@ 'Cost / Notes: Low.' ), 'args_setting': f'{STRUCTURE_BUILDING_SAVENAME}', + 'alternative': [], 'bypass_confirmation': True, }, 'make_doped_structure': { @@ -954,6 +1050,7 @@ 'Cost / Notes: Low.' ), 'args_setting': f'Parameter guidance: Fractions are applied per-site; actual doping % may differ slightly in small cells — recommend ≥2×2×2 supercells for <10% doping. Covalent ions (ammonium, formamidinium, etc.) are supported via built-in library; specify by name (e.g., "ammonium"). {STRUCTURE_BUILDING_SAVENAME}', + 'alternative': [], 'bypass_confirmation': True, }, 'make_amorphous_structure': { @@ -968,6 +1065,7 @@ 'Cost / Notes: Medium.' ), 'args_setting': f'Parameter guidance: Input Constraint: Specify exactly two of: box_size, density, molecule_numbers. The third is derived. Density Regimes (CRITICAL): Solids/Liquids: Target ~0.9–1.2 g/cm³ (e.g., water ~1.0, polymers ~1.1). Gases/Vapors: Target orders of magnitude lower (e.g., ~0.001–0.002 g/cm³ for STP gases). Warning: Do not apply default liquid densities to gas inputs. If simulating a specific pressure, pre-calculate the required number of molecules N for the given Box Volume V (using Ideal Gas Law), then fix box_size and molecule_numbers. Composition: Use composition for multi-component mixtures; otherwise equal molar ratios are assumed. Packing Geometry: Box Size: For gases, ensure the box is large enough (usually >15 Å) to minimize unphysical periodic self-interactions, even if the density is low. {STRUCTURE_BUILDING_SAVENAME}', + 'alternative': [], 'bypass_confirmation': True, }, 'get_structure_info': { @@ -982,6 +1080,7 @@ 'Cost / Notes: Low.' ), 'args_setting': '', + 'alternative': ['file_parse'], 'bypass_confirmation': True, }, 'get_molecule_info': { @@ -996,6 +1095,7 @@ 'Cost / Notes: Low.' ), 'args_setting': '', + 'alternative': ['file_parse'], 'bypass_confirmation': True, }, 'add_hydrogens': { @@ -1018,6 +1118,7 @@ 'bond_lengths: Optional. Override default H-X bond lengths. ' 'output_file: Required. Path to save the hydrogenated structure.' ), + 'alternative': [], 'bypass_confirmation': True, }, 'run_superconductor_optimization': { @@ -1031,6 +1132,7 @@ 'Cannot do / Limits: Only geometry optimization.\n' 'Cost / Notes: High (DPA calculations).' ), + 'alternative': [], }, 'calculate_superconductor_enthalpy': { 'belonging_agent': SuperconductorAgentName, @@ -1043,6 +1145,7 @@ 'Cannot do / Limits: Superconductor-specific only.\n' 'Cost / Notes: High (DPA calculations).' ), + 'alternative': ['predict_superconductor_Tc'], }, 'predict_superconductor_Tc': { 'belonging_agent': SuperconductorAgentName, @@ -1055,6 +1158,7 @@ 'Cannot do / Limits: DPA model only.\n' 'Cost / Notes: High (ML predictions).' ), + 'alternative': ['calculate_superconductor_enthalpy'], }, 'screen_superconductor': { 'belonging_agent': SuperconductorAgentName, @@ -1067,6 +1171,7 @@ 'Cannot do / Limits: Multiple candidates only.\n' 'Cost / Notes: High (batch DPA).' ), + 'alternative': [], }, 'predict_thermoelectric_properties': { 'belonging_agent': ThermoelectricAgentName, @@ -1079,6 +1184,7 @@ 'Cannot do / Limits: No thermal conductivity.\n' 'Cost / Notes: High (DPA predictions).' ), + 'alternative': [], }, 'run_pressure_optimization': { 'belonging_agent': ThermoelectricAgentName, @@ -1091,6 +1197,7 @@ 'Cannot do / Limits: Thermoelectric-specific.\n' 'Cost / Notes: High (DPA calculations).' ), + 'alternative': [], }, 'calculate_thermoele_enthalp': { 'belonging_agent': ThermoelectricAgentName, @@ -1103,6 +1210,7 @@ 'Cannot do / Limits: Thermoelectric-specific.\n' 'Cost / Notes: High (DPA calculations).' ), + 'alternative': [], }, 'screen_thermoelectric_candidate': { 'belonging_agent': ThermoelectricAgentName, @@ -1115,6 +1223,7 @@ 'Cannot do / Limits: Requires multiple inputs.\n' 'Cost / Notes: High (batch DPA).' ), + 'alternative': [], }, 'traj_analysis_diffusion': { 'belonging_agent': TrajAnalysisAgentName, @@ -1127,6 +1236,7 @@ 'Cannot do / Limits: Post-MD analysis only.\n' 'Cost / Notes: Medium.' ), + 'alternative': [], }, 'traj_analysis_rdf': { 'belonging_agent': TrajAnalysisAgentName, @@ -1139,6 +1249,7 @@ 'Cannot do / Limits: Post-MD analysis only.\n' 'Cost / Notes: Medium.' ), + 'alternative': [], }, 'traj_analysis_solvation': { 'belonging_agent': TrajAnalysisAgentName, @@ -1151,6 +1262,7 @@ 'Cannot do / Limits: Post-MD analysis only.\n' 'Cost / Notes: Medium.' ), + 'alternative': [], }, 'traj_analysis_bond': { 'belonging_agent': TrajAnalysisAgentName, @@ -1163,6 +1275,7 @@ 'Cannot do / Limits: Post-MD analysis only.\n' 'Cost / Notes: Medium.' ), + 'alternative': [], }, 'traj_analysis_react': { 'belonging_agent': TrajAnalysisAgentName, @@ -1175,6 +1288,7 @@ 'Cannot do / Limits: Post-MD analysis only.\n' 'Cost / Notes: Medium.' ), + 'alternative': [], }, 'visualize_data_from_file': { 'belonging_agent': VisualizerAgentName, @@ -1187,6 +1301,7 @@ 'Cannot do / Limits: Data files only.\n' 'Cost / Notes: Low.' ), + 'alternative': [], 'bypass_confirmation': True, }, 'visualize_data_from_prompt': { @@ -1199,6 +1314,7 @@ 'Cannot do / Limits: Plot requests with valid data only.\n' 'Cost / Notes: Low.' ), + 'alternative': [], 'bypass_confirmation': True, }, 'convert_lammps_structural_format': { @@ -1212,6 +1328,7 @@ 'Cannot do / Limits: Format conversion only.\n' 'Cost / Notes: Low.' ), + 'alternative': [], }, 'run_lammps': { 'belonging_agent': LAMMPS_AGENT_NAME, @@ -1224,6 +1341,7 @@ 'Cannot do / Limits: Requires LAMMPS format.\n' 'Cost / Notes: High (simulation time).' ), + 'alternative': [], }, 'orchestrate_lammps_input': { 'belonging_agent': LAMMPS_AGENT_NAME, @@ -1236,6 +1354,7 @@ 'Cannot do / Limits: Script generation only.\n' 'Cost / Notes: Low.' ), + 'alternative': [], }, 'search-papers-enhanced': { 'belonging_agent': SCIENCE_NAVIGATOR_AGENT_NAME, @@ -1254,9 +1373,25 @@ Must be aware of these prior knowledge: - {ALIAS_SEARCH_PROMPT} """, + 'alternative': ['web-search'], 'summary_prompt': PAPER_SEARCH_AGENT_INSTRUCTION, 'bypass_confirmation': True, }, + 'web-search': { + 'belonging_agent': SCIENCE_NAVIGATOR_AGENT_NAME, + 'scene': [SceneEnum.UNIVERSAL], + 'description': ( + 'What it does: Perform web searches for what, why, how questions.\n' + 'When to use: For concise factual or explanatory lookups.\n' + 'Prerequisites / Inputs: Search query.\n' + 'Outputs: URL, title, snippet.\n' + 'Cannot do / Limits: No command-type queries; follow up with extract_info_from_webpage.\n' + 'Cost / Notes: Low.' + ), + 'alternative': [], + 'summary_prompt': WEB_SEARCH_AGENT_INSTRUCTION, + 'bypass_confirmation': True, + }, 'build_convex_hull': { 'belonging_agent': ConvexHullAgentName, 'scene': [SceneEnum.CONVEXHULL], @@ -1268,6 +1403,7 @@ 'Cannot do / Limits: General materials only.\n' 'Cost / Notes: High (DPA calculations).' ), + 'alternative': [], }, 'NMR_search_tool': { 'belonging_agent': NMR_AGENT_NAME, @@ -1280,6 +1416,7 @@ 'Cannot do / Limits: Not a definitive identification; results depend on database coverage and feature quality.\n' 'Cost / Notes: Medium; tighter tolerances increase runtime and reduce recall.' ), + 'alternative': [], }, 'NMR_predict_tool': { 'belonging_agent': NMR_AGENT_NAME, @@ -1292,6 +1429,7 @@ 'Cannot do / Limits: 1H and 13C only.\n' 'Cost / Notes: Medium.' ), + 'alternative': [], }, 'NMR_reverse_predict_tool': { 'belonging_agent': NMR_AGENT_NAME, @@ -1304,6 +1442,7 @@ 'Cannot do / Limits: Based on NMR features.\n' 'Cost / Notes: Medium.' ), + 'alternative': [], }, 'extract_info_from_webpage': { 'belonging_agent': SCIENCE_NAVIGATOR_AGENT_NAME, @@ -1316,23 +1455,10 @@ 'Cannot do / Limits: Only return text and do not support generating files in any format.\n' 'Cost / Notes: Low.' ), + 'alternative': [], 'summary_prompt': WEBPAGE_PARSING_AGENT_INSTRUCTION, 'bypass_confirmation': True, }, - 'web-search': { - 'belonging_agent': SCIENCE_NAVIGATOR_AGENT_NAME, - 'scene': [SceneEnum.UNIVERSAL], - 'description': ( - 'What it does: Perform web searches for what, why, how questions.\n' - 'When to use: For concise factual or explanatory lookups.\n' - 'Prerequisites / Inputs: Search query.\n' - 'Outputs: URL, title, snippet.\n' - 'Cannot do / Limits: No command-type queries; follow up with extract_info_from_webpage.\n' - 'Cost / Notes: Low.' - ), - 'summary_prompt': WEB_SEARCH_AGENT_INSTRUCTION, - 'bypass_confirmation': True, - }, 'xrd_parse_file': { 'belonging_agent': XRD_AGENT_NAME, 'scene': [SceneEnum.XRD], @@ -1344,6 +1470,7 @@ 'Cannot do / Limits: Not support .raw and .mdi format files.\n' 'Cost / Notes: Low.' ), + 'alternative': [], }, 'xrd_phase_identification': { 'belonging_agent': XRD_AGENT_NAME, @@ -1356,6 +1483,7 @@ 'Cannot do / Limits: Requires processed data.\n' 'Cost / Notes: Medium.' ), + 'alternative': [], }, 'get_electron_microscope_recognize': { 'belonging_agent': Electron_Microscope_AGENT_NAME, @@ -1368,6 +1496,7 @@ 'Cannot do / Limits: Only support .tif, .tiff, .png, .jpeg, .jpg format files.\n' 'Cost / Notes: Medium.' ), + 'alternative': [], }, 'llm_tool': { 'belonging_agent': TOOL_AGENT_NAME, @@ -1380,6 +1509,7 @@ 'Cannot do / Limits: General purpose.\n' 'Cost / Notes: Low.' ), + 'alternative': [], 'bypass_confirmation': True, }, 'physical_adsorption_echart_data': { @@ -1393,6 +1523,7 @@ 'Cannot do / Limits: Specific to physical adsorption.\n' 'Cost / Notes: Low.' ), + 'alternative': [], }, 'file_parse': { 'belonging_agent': FILE_PARSE_AGENT_NAME, @@ -1405,6 +1536,7 @@ 'Cannot do / Limits: Only return text and do not support generating files in any format.\n' 'Cost / Notes: Low.' ), + 'alternative': [], 'bypass_confirmation': True, }, } From 31c100ba7dcdd9d4d613fbfcb4dabc3e829d6f32 Mon Sep 17 00:00:00 2001 From: beform88 <2298266722@qq.com> Date: Wed, 31 Dec 2025 09:19:38 +0000 Subject: [PATCH 11/15] . --- agents/matmaster_agent/flow_agents/execution_agent/agent.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/agents/matmaster_agent/flow_agents/execution_agent/agent.py b/agents/matmaster_agent/flow_agents/execution_agent/agent.py index 159c4d85..3231f336 100644 --- a/agents/matmaster_agent/flow_agents/execution_agent/agent.py +++ b/agents/matmaster_agent/flow_agents/execution_agent/agent.py @@ -119,10 +119,10 @@ async def _run_events(self, ctx: InvocationContext) -> AsyncGenerator[Event, Non logger.info( f'{ctx.session.id} Before Run: plan_index = {ctx.session.state["plan_index"]}, plan = {ctx.session.state['plan']}' ) - if retry_count != 0 or separate_card_info != 'Step': + if retry_count != 0: separate_card_info = 'ReExecuteSteps' else: - separate_card_info = 'Step' + separate_card_info = 'Step' for step_event in all_text_event( ctx, From b04f747bba59bf95ba13ab23554a9262877a8d60 Mon Sep 17 00:00:00 2001 From: beform88 <2298266722@qq.com> Date: Wed, 31 Dec 2025 09:19:58 +0000 Subject: [PATCH 12/15] . --- agents/matmaster_agent/flow_agents/execution_agent/agent.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/agents/matmaster_agent/flow_agents/execution_agent/agent.py b/agents/matmaster_agent/flow_agents/execution_agent/agent.py index 3231f336..74e933f0 100644 --- a/agents/matmaster_agent/flow_agents/execution_agent/agent.py +++ b/agents/matmaster_agent/flow_agents/execution_agent/agent.py @@ -122,7 +122,7 @@ async def _run_events(self, ctx: InvocationContext) -> AsyncGenerator[Event, Non if retry_count != 0: separate_card_info = 'ReExecuteSteps' else: - separate_card_info = 'Step' + separate_card_info = 'Step' for step_event in all_text_event( ctx, From 0942edd6bbb59386d5c22552c1e6bc03b129a1e4 Mon Sep 17 00:00:00 2001 From: beform88 <2298266722@qq.com> Date: Wed, 31 Dec 2025 09:32:08 +0000 Subject: [PATCH 13/15] . --- agents/matmaster_agent/flow_agents/execution_agent/agent.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/agents/matmaster_agent/flow_agents/execution_agent/agent.py b/agents/matmaster_agent/flow_agents/execution_agent/agent.py index 74e933f0..36b7742f 100644 --- a/agents/matmaster_agent/flow_agents/execution_agent/agent.py +++ b/agents/matmaster_agent/flow_agents/execution_agent/agent.py @@ -290,7 +290,7 @@ async def _run_events(self, ctx: InvocationContext) -> AsyncGenerator[Event, Non for tool_replace_event in all_text_event( ctx, self.name, - f"步骤 {index + 1} 多次重试失败,已找到合适的替代工具:{step['tool_name']} → {available_alts}", + f"步骤 {index + 1} 多次重试失败,已找到合适的替代工具:{step['tool_name']} → {available_alts[0]}", ModelRole, ): yield tool_replace_event From c3cf641815387f3bad4753344a31ec0f7b7d0d05 Mon Sep 17 00:00:00 2001 From: beform88 <2298266722@qq.com> Date: Wed, 31 Dec 2025 10:05:35 +0000 Subject: [PATCH 14/15] . --- .../flow_agents/execution_agent/agent.py | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/agents/matmaster_agent/flow_agents/execution_agent/agent.py b/agents/matmaster_agent/flow_agents/execution_agent/agent.py index 36b7742f..a57b405c 100644 --- a/agents/matmaster_agent/flow_agents/execution_agent/agent.py +++ b/agents/matmaster_agent/flow_agents/execution_agent/agent.py @@ -189,7 +189,7 @@ async def _run_events(self, ctx: InvocationContext) -> AsyncGenerator[Event, Non for validation_failed_event in all_text_event( ctx, self.name, - f"步骤 {index + 1} 结果与用户需求不符:{validation_reason},正在准备重试...", + f"步骤 {index + 1} 结果校验未通过:{validation_reason},正在准备重试...", ModelRole, ): yield validation_failed_event @@ -221,19 +221,12 @@ async def _run_events(self, ctx: InvocationContext) -> AsyncGenerator[Event, Non == PlanStepStatusEnum.FAILED and retry_count < max_retries ): - # 执行失败,检查是否可以重试 retry_count += 1 - validation_reason = current_steps[index].get( - 'validation_failure_reason', '' - ) # 向用户显示重试信息 - if validation_reason: - retry_message = f"步骤 {index + 1} 执行失败(校验原因:{validation_reason}),正在准备重试..." - else: - retry_message = ( - f"步骤 {index + 1} 执行失败,正在准备重试..." - ) + retry_message = ( + f"步骤 {index + 1} 执行失败,正在准备重试..." + ) for retry_event in all_text_event( ctx, @@ -308,6 +301,8 @@ async def _run_events(self, ctx: InvocationContext) -> AsyncGenerator[Event, Non update_plan['steps'][index][ 'status' ] = PlanStepStatusEnum.PROCESS + original_description = step['description'].split('\n\n注意:')[0] # 移除之前的失败原因 + update_plan['steps'][index]['description'] = original_description yield update_state_event( ctx, state_delta={'plan': update_plan} ) From c039795c5de4a7af3c5c273eda252222fdec561c Mon Sep 17 00:00:00 2001 From: beform88 <2298266722@qq.com> Date: Wed, 31 Dec 2025 10:21:26 +0000 Subject: [PATCH 15/15] . --- .../flow_agents/execution_agent/agent.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/agents/matmaster_agent/flow_agents/execution_agent/agent.py b/agents/matmaster_agent/flow_agents/execution_agent/agent.py index a57b405c..5d019214 100644 --- a/agents/matmaster_agent/flow_agents/execution_agent/agent.py +++ b/agents/matmaster_agent/flow_agents/execution_agent/agent.py @@ -301,8 +301,14 @@ async def _run_events(self, ctx: InvocationContext) -> AsyncGenerator[Event, Non update_plan['steps'][index][ 'status' ] = PlanStepStatusEnum.PROCESS - original_description = step['description'].split('\n\n注意:')[0] # 移除之前的失败原因 - update_plan['steps'][index]['description'] = original_description + original_description = step['description'].split( + '\n\n注意:' + )[ + 0 + ] # 移除之前的失败原因 + update_plan['steps'][index][ + 'description' + ] = original_description yield update_state_event( ctx, state_delta={'plan': update_plan} )