diff --git a/agents/matmaster_agent/base_agents/job_agent.py b/agents/matmaster_agent/base_agents/job_agent.py index 8fcff93a..330bdb2b 100644 --- a/agents/matmaster_agent/base_agents/job_agent.py +++ b/agents/matmaster_agent/base_agents/job_agent.py @@ -8,7 +8,7 @@ from google.adk.agents import LlmAgent, SequentialAgent from google.adk.agents.invocation_context import InvocationContext from google.adk.events import Event, EventActions -from pydantic import Field, BaseModel +from pydantic import Field from agents.matmaster_agent.base_agents.callback import check_before_tool_callback_effect, default_before_tool_callback, \ catch_before_tool_callback_error, check_job_create, inject_username_ticket, inject_ak_projectId, \ @@ -32,7 +32,7 @@ get_BohriumStorage, get_DFlowExecutor, OpenAPIJobAPI, ) -from agents.matmaster_agent.model import BohrJobInfo, DFlowJobInfo +from agents.matmaster_agent.model import BohrJobInfo, DFlowJobInfo, ParamsCheckComplete from agents.matmaster_agent.prompt import ( ResultCoreAgentDescription, SubmitRenderAgentDescription, gen_submit_core_agent_description, gen_submit_core_agent_instruction, @@ -217,12 +217,6 @@ async def _run_async_impl(self, ctx: InvocationContext) -> AsyncGenerator[Event, yield error_event -class ParamsCheckComplete(BaseModel): - flag: bool - reason: str - analyzed_message: str - - class ParamsCheckCompletedAgent(LlmAgent): pass @@ -637,7 +631,7 @@ async def _run_async_impl(self, ctx: InvocationContext) -> AsyncGenerator[Event, ): # Only Query Job Result pass else: - cherry_pick_parts = cherry_pick_events(ctx)[:5] + cherry_pick_parts = cherry_pick_events(ctx)[-5:] context_messages = '\n'.join([f'<{item[0].title()}> said: \n{item[1]}\n' for item in cherry_pick_parts]) logger.info(f"[BaseAsyncJobAgent] context_messages = {context_messages}") @@ -647,18 +641,18 @@ async def _run_async_impl(self, ctx: InvocationContext) -> AsyncGenerator[Event, params_check_completed_json: dict = json.loads(response.choices[0].message.content) params_check_completed = params_check_completed_json['flag'] params_check_reason = params_check_completed_json['reason'] - params_check_msg = params_check_completed_json['analyzed_message'] + params_check_msg = params_check_completed_json['analyzed_messages'] + + # 包装成function_call,来避免在历史记录中展示;同时模型可以在上下文中感知 + for params_check_reason_event in context_function_event(ctx, self.name, + 'system_params_check_result', + {'complete': params_check_completed, + 'reason': params_check_reason, + 'analyzed_messages': params_check_msg}, + ModelRole): + yield params_check_reason_event if not params_check_completed: - # Tell User Why Params Check Uncompleted - # 包装成function_call,来避免在历史记录中展示;同时模型可以在上下文中感知 - for params_check_reason_event in context_function_event(ctx, self.name, - 'system_params_check_block_reason', - {'reason': params_check_reason, - 'analyzed_message': params_check_msg}, - ModelRole): - yield params_check_reason_event - # Call ParamsCheckInfoAgent to generate params needing check async for params_check_info_event in self.params_check_info_agent.run_async(ctx): yield params_check_info_event diff --git a/agents/matmaster_agent/callback.py b/agents/matmaster_agent/callback.py index e5d7794f..8e925e85 100644 --- a/agents/matmaster_agent/callback.py +++ b/agents/matmaster_agent/callback.py @@ -131,13 +131,15 @@ async def matmaster_check_transfer(callback_context: CallbackContext, llm_respon result: dict = json.loads(response.choices[0].message.content) is_transfer = bool(result.get('is_transfer', False)) target_agent = str(result.get('target_agent', '')) - + reason = str(result.get('reason', '')) + logger.info(f"[matmaster_check_transfer] target_agent = {target_agent}, is_transfer = {is_transfer}" + f"response_text = {llm_response.content.parts[0].text}, reason = {reason}") if ( is_transfer and not has_function_call(llm_response) ): - logger.warning(f"[matmaster_check_transfer] target_agent = {target_agent}") - function_call_id = f"call_{str(uuid.uuid4()).replace('-', '')[:24]}" + logger.warning(f"[matmaster_check_transfer] add `transfer_to_agent`") + function_call_id = f"added_{str(uuid.uuid4()).replace('-', '')[:24]}" llm_response.content.parts.append(Part(function_call=FunctionCall(id=function_call_id, name='transfer_to_agent', args={'agent_name': target_agent}))) diff --git a/agents/matmaster_agent/model.py b/agents/matmaster_agent/model.py index 87a933ff..76955540 100644 --- a/agents/matmaster_agent/model.py +++ b/agents/matmaster_agent/model.py @@ -91,9 +91,16 @@ class TargetAgentEnum(str, Enum): TrajAnalysisAgent = TrajAnalysisAgentName +class ParamsCheckComplete(BaseModel): + flag: bool + reason: str + analyzed_messages: List[str] + + class TransferCheck(BaseModel): is_transfer: bool target_agent: TargetAgentEnum + reason: str class UserContent(BaseModel): diff --git a/agents/matmaster_agent/prompt.py b/agents/matmaster_agent/prompt.py index c43953cd..f1dc3373 100644 --- a/agents/matmaster_agent/prompt.py +++ b/agents/matmaster_agent/prompt.py @@ -668,48 +668,35 @@ def gen_result_agent_description(): def gen_params_check_completed_agent_instruction(): return """ -Your task is to determine if the parameters requiring user confirmation have been fully presented and a confirmation is being requested in that message. -Analyze the messages from `user` and `model` listed below (only listed Latest 5 messages): +Your task is to determine if the parameters requiring user confirmation have been fully presented and a confirmation has been confirmed in the `context messages`. +Analyze the `context_messages` from [User] and [Model] listed below (only listed Latest 5 messages): +Context Messages (Including User and Model Conversation. The most recent conversation is at the bottom): +------------------ {context_messages} +------------------ Your output MUST be a valid JSON object with the following structure: {{ "flag": , - "reason": , // *Present reason if flag is False, else return empty string* - "analyzed_message": // *Quote the specific message snippet that was analyzed to make this determination.* + "reason": , // *A concise explanation of the reasoning behind the judgment, covering both positive and negative evidence found in the context messages. Return empty string only if there is absolutely no relevant content to analyze.* + "analyzed_message": List[] // *Quote the key messages that were analyzed to make this determination.* }} Return `flag: true` ONLY IF ALL of the following conditions are met: -1. The message explicitly and finally lists all parameters that need user confirmation (e.g., element, structure type, dimensions). -2. The message's intent is to conclude the parameter collection phase and advance the conversation to the next step (typically, awaiting a "yes" or "no" response from the user to proceed with an action). -3. The message does not indicate that the parameter discussion is still ongoing (e.g., lacks phrases like "also need," "next, please provide," "what is the..."). +1. The context messages explicitly and finally list all parameters that user confirmed (e.g., element, structure type, dimensions). +2. The context messages's intent is to conclude the parameter collection phase and advance the conversation to the next step. +3. The context messages does not indicate that the parameter discussion is still ongoing (e.g., lacks phrases like "also need," "next, please provide," "what is the..."). Return `flag: false` in ANY of these cases: -1. The message does not mention any specific parameters to confirm. -2. The message is asking for or soliciting new parameter information (e.g., "What element would you like?", "Please provide the lattice constant."). -3. The message states or implies that parameter collection is not yet finished and further questions will follow. +1. The context messages don't mention any specific parameters to confirm. +2. The context messages are asking for or soliciting new parameter information (e.g., "What element would you like?", "Please provide the lattice constant."). +3. The context messages state or imply that parameter collection is not yet finished and further questions will follow. 4. There are currently no parameters awaiting user confirmation. - * For any of these cases, the "reason" field must be populated with a concise explanation based on the violated condition(s).* **语言要求 (Language Requirement):** 在输出JSON时,请观察对话上下文使用的主要语言。如果上下文主要是中文,那么`reason`字段必须用中文书写。如果上下文主要是英文或其他语言,则使用相应的语言。请确保语言选择与对话上下文保持一致。 -**Critical Guidance:** The act of clearly listing parameters and explicitly asking for confirmation (e.g., "Please confirm the following parameters:...") is considered the completion of the parameter presentation task. Therefore, return `true` for the message where that request is made, NOT after the user has confirmed. Look for the most recent message where parameters are presented for confirmation, even if it's not the very last message. - -**Examples:** -- Message: "Please confirm the following parameters to build the FCC copper crystal: Element: Copper (Cu), Structure: FCC, using default lattice parameters. Please confirm if this is correct?" - - **Analysis:** Parameters are explicitly listed (Cu, FCC), and a confirmation is requested to proceed. Collection is concluded. - - **Output:** {{"flag": true, "reason": "", "analyzed_message": "Please confirm the following parameters to build the FCC copper crystal: Element: Copper (Cu), Structure: FCC, using default lattice parameters. Please confirm if this is correct?"}} - -- Message: "To build the crystal, what element should I use?" - - **Analysis:** This is a request for a new parameter, not a request for confirmation of existing ones. (Violates Condition 2 for 'true' / Matches Condition 2 for 'false') - - **Output (英文上下文):** {{"flag": false, "reason": "Message is soliciting new parameter information ('what element') rather than requesting confirmation.", "analyzed_message": "To build the crystal, what element should I use?"}} - - **Output (中文上下文):** {{"flag": false, "reason": "消息正在征求新的参数信息('使用什么元素'),而不是请求确认。", "analyzed_message": "To build the crystal, what element should I use?"}} - -- Message: "Element is set to Copper. Now, what is the desired lattice constant?" - - **Analysis:** One parameter is noted, but the conversation is actively moving to collect the next parameter. Collection is not concluded. (Violates Condition 1 and 3 for 'true' / Matches Condition 3 for 'false') - - **Output (英文上下文):** {{"flag": false, "reason": "Parameter collection is not finished; the message is asking for the next parameter ('lattice constant').", "analyzed_message": "Element is set to Copper. Now, what is the desired lattice constant?"}} - - **Output (中文上下文):** {{"flag": false, "reason": "参数收集未完成;消息正在询问下一个参数('晶格常数')。", "analyzed_message": "Element is set to Copper. Now, what is the desired lattice constant?"}} +**Critical Guidance:** The act of clearly listing parameters and explicitly confirmed is considered the completion of the parameter presentation task. Therefore, return `true` for the message where that request is made, NOT after the user has confirmed. Based on the rules above, output a JSON object. """ @@ -755,15 +742,25 @@ def get_transfer_check_prompt(): Provide your evaluation in the following JSON format: {{ "is_transfer": , - "target_agent": "xxx agent" (if transfer detected) or null (if no transfer) + "target_agent": "xxx agent" (if transfer detected) or null (if no transfer), + "reason": // *A concise explanation of the reasoning behind the judgment, covering both positive and negative evidence found in the response text. Return empty string only if there is absolutely no relevant content to analyze.* }} Examples for reference: -- Case1 (false): "使用结构生成智能体(structure_generate_agent)根据用户要求创建 FCC Cu 的块体结构" - only mentions agent, no transfer action -- Case2 (true): "正在转移到structure_generate_agent进行结构生成" - explicit transfer action with target agent -- Case3 (true): "I will now use the structure_generate_agent to create the bulk structure" - immediate action with target agent -- Case4 (false): "Next I will generate the Pt bulk structure" - no agent transfer mentioned -- Case5 (true): `{{"agent_name":"traj_analysis_agent"}}` - explicit JSON object instructing transfer +- Case1 (false): "使用结构生成智能体(structure_generate_agent)根据用户要求创建 FCC Cu 的块体结构" + -> Reason: "Only mentions the agent's function but lacks any explicit transfer verbs or immediate action indicators." + +- Case2 (true): "正在转移到structure_generate_agent进行结构生成" + -> Reason: "Contains explicit transfer phrase '正在转移到' (transferring to) followed by a clear target agent name." + +- Case3 (true): "I will now use the structure_generate_agent to create the bulk structure" + -> Reason: "Uses immediate action indicator 'I will now use' followed by a specific agent name, demonstrating transfer intent." + +- Case4 (false): "Next I will generate the Pt bulk structure" + -> Reason: "Describes a future action but does not mention any agent or transfer mechanism." + +- Case5 (true): `{{"agent_name":"traj_analysis_agent"}}` + -> Reason: "Standalone JSON object with an 'agent_name' key is an explicit programmatic instruction to transfer." """ diff --git a/agents/matmaster_agent/utils/event_utils.py b/agents/matmaster_agent/utils/event_utils.py index f4c1e9c2..e9fe232c 100644 --- a/agents/matmaster_agent/utils/event_utils.py +++ b/agents/matmaster_agent/utils/event_utils.py @@ -1,3 +1,4 @@ +import logging import traceback import uuid from typing import Iterable @@ -10,6 +11,8 @@ from agents.matmaster_agent.constant import ModelRole from agents.matmaster_agent.utils.helper_func import update_session_state +logger = logging.getLogger(__name__) + # event check funcs def has_part(event: Event): @@ -141,7 +144,7 @@ def context_function_response_event(ctx: InvocationContext, author: str, functio def context_function_event(ctx: InvocationContext, author: str, function_call_name: str, response: Optional[dict], role: str, args: Optional[dict] = None): - function_call_id = f"call_{str(uuid.uuid4()).replace('-', '')[:24]}" + function_call_id = f"added_{str(uuid.uuid4()).replace('-', '')[:24]}" yield context_function_call_event(ctx, author, function_call_id, function_call_name, role, args) yield context_function_response_event(ctx, author, function_call_id, function_call_name, response, role) @@ -152,6 +155,7 @@ def context_multipart2function_event(ctx: InvocationContext, author: str, event: yield from context_function_event(ctx, author, function_call_name, {'msg': part.text}, ModelRole) elif part.function_call: + logger.warning(f"[context_multipart2function_event] function_name = {part.function_call.name}") yield context_function_call_event(ctx, author, function_call_id=part.function_call.id, function_call_name=part.function_call.name, role=ModelRole, args=part.function_call.args)