diff --git a/assistant-agent-autoconfigure/pom.xml b/assistant-agent-autoconfigure/pom.xml index bec4d82e..6622d09f 100644 --- a/assistant-agent-autoconfigure/pom.xml +++ b/assistant-agent-autoconfigure/pom.xml @@ -6,7 +6,7 @@ com.alibaba.agent.assistant assistant-agent - 0.2.1 + 0.2.2 assistant-agent-autoconfigure diff --git a/assistant-agent-autoconfigure/src/main/java/com/alibaba/assistant/agent/autoconfigure/CodeactAgent.java b/assistant-agent-autoconfigure/src/main/java/com/alibaba/assistant/agent/autoconfigure/CodeactAgent.java index 11e3ed5e..acc81f09 100644 --- a/assistant-agent-autoconfigure/src/main/java/com/alibaba/assistant/agent/autoconfigure/CodeactAgent.java +++ b/assistant-agent-autoconfigure/src/main/java/com/alibaba/assistant/agent/autoconfigure/CodeactAgent.java @@ -15,6 +15,7 @@ */ package com.alibaba.assistant.agent.autoconfigure; +import com.alibaba.assistant.agent.autoconfigure.hook.CodeactToolSignatureAgentHook; import com.alibaba.assistant.agent.autoconfigure.hook.CodeactToolsStateInitHook; import com.alibaba.assistant.agent.common.enums.Language; import com.alibaba.assistant.agent.common.tools.CodeactTool; @@ -163,6 +164,7 @@ public static class CodeactAgentBuilder extends Builder { private RuntimeEnvironmentManager environmentManager; private GraalCodeExecutor executor; private boolean enableInitialCodeGen = true; + private boolean enableToolSignatureInjection = true; private boolean allowIO = false; private boolean allowNativeAccess = false; private long executionTimeoutMs = 30000; @@ -244,6 +246,22 @@ public CodeactAgentBuilder enableInitialCodeGen(boolean enable) { return this; } + /** + * Enable/disable automatic CodeactTool signature injection into the prompt. + * + *

When enabled (default), a {@link CodeactToolSignatureAgentHook} is automatically + * registered, which injects Python class/function stubs for all registered CodeactTools + * into the messages before the Agent starts. This allows the LLM to correctly reference + * these tools when writing code in {@code write_code}. + * + *

Disable this if you provide your own tool signature injection mechanism + * (e.g., a custom PromptContributor). + */ + public CodeactAgentBuilder enableToolSignatureInjection(boolean enable) { + this.enableToolSignatureInjection = enable; + return this; + } + /** * Allow IO operations in GraalVM (security) */ @@ -578,6 +596,12 @@ public CodeactAgent build() { this.hooks.add(new CodeactToolsStateInitHook(this.codeactToolRegistry)); logger.info("CodeactAgentBuilder#build - reason=自动注册CodeactToolsStateInitHook"); + // 自动注册 CodeactToolSignatureAgentHook,将工具的 Python 签名注入到 Prompt 中 + if (this.enableToolSignatureInjection) { + this.hooks.add(new CodeactToolSignatureAgentHook(this.codeactToolRegistry, this.language)); + logger.info("CodeactAgentBuilder#build - reason=自动注册CodeactToolSignatureAgentHook"); + } + // Initialize CodeContext if not provided if (this.codeContext == null) { this.codeContext = new CodeContext(this.language); diff --git a/assistant-agent-autoconfigure/src/main/java/com/alibaba/assistant/agent/autoconfigure/hook/CodeactToolSignatureAgentHook.java b/assistant-agent-autoconfigure/src/main/java/com/alibaba/assistant/agent/autoconfigure/hook/CodeactToolSignatureAgentHook.java new file mode 100644 index 00000000..cb193247 --- /dev/null +++ b/assistant-agent-autoconfigure/src/main/java/com/alibaba/assistant/agent/autoconfigure/hook/CodeactToolSignatureAgentHook.java @@ -0,0 +1,167 @@ +/* + * Copyright 2024-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.assistant.agent.autoconfigure.hook; + +import com.alibaba.assistant.agent.common.constant.HookPriorityConstants; +import com.alibaba.assistant.agent.common.enums.Language; +import com.alibaba.assistant.agent.core.tool.CodeactToolRegistry; +import com.alibaba.cloud.ai.graph.OverAllState; +import com.alibaba.cloud.ai.graph.RunnableConfig; +import com.alibaba.cloud.ai.graph.agent.hook.AgentHook; +import com.alibaba.cloud.ai.graph.agent.hook.HookPosition; +import com.alibaba.cloud.ai.graph.agent.hook.HookPositions; +import com.alibaba.cloud.ai.graph.agent.hook.JumpTo; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.ai.chat.messages.AssistantMessage; +import org.springframework.ai.chat.messages.Message; +import org.springframework.ai.chat.messages.ToolResponseMessage; + +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; + +/** + * CodeactTool 签名注入 Hook + * + *

在 BEFORE_AGENT 阶段执行,将 {@link CodeactToolRegistry} 中所有注册工具的 + * Python 签名(class stub / function stub)注入到 messages 中,使 React 阶段的 + * LLM 在调用 {@code write_code} 编写 Python 代码时,能够正确地使用这些 CodeactTool。 + * + *

React 阶段的 LLM 需要同时具备工具选择和代码编写的能力,因此需要将工具的完整签名信息提前注入到 Prompt 中。 + * + *

注入方式采用 {@link AssistantMessage} + {@link ToolResponseMessage} 配对, + * 与 {@link CodeactToolsStateInitHook} 类似,由 {@code CodeactAgent.Builder} 自动注册。 + * + *

优先级 {@link HookPriorityConstants#CODEACT_TOOL_SIGNATURE_HOOK}(8), + * 在 {@link CodeactToolsStateInitHook}(5)之后、经验注入(20)之前执行。 + * + * @author Assistant Agent Team + * @since 1.0.0 + * @see CodeactToolsStateInitHook + */ +@HookPositions(HookPosition.BEFORE_AGENT) +public class CodeactToolSignatureAgentHook extends AgentHook { + + private static final Logger log = LoggerFactory.getLogger(CodeactToolSignatureAgentHook.class); + + private static final String INJECTION_TOOL_NAME = "codeact_tool_signature_injection"; + + private final CodeactToolRegistry codeactToolRegistry; + + private final Language language; + + public CodeactToolSignatureAgentHook(CodeactToolRegistry codeactToolRegistry, Language language) { + this.codeactToolRegistry = codeactToolRegistry; + this.language = language; + } + + @Override + public String getName() { + return "CodeactToolSignatureAgentHook"; + } + + @Override + public int getOrder() { + return HookPriorityConstants.CODEACT_TOOL_SIGNATURE_HOOK; + } + + @Override + public List canJumpTo() { + return List.of(); + } + + @Override + @SuppressWarnings("unchecked") + public CompletableFuture> beforeAgent(OverAllState state, RunnableConfig config) { + log.info("CodeactToolSignatureAgentHook#beforeAgent - reason=开始注入CodeactTool签名到Prompt"); + + try { + if (codeactToolRegistry == null || codeactToolRegistry.getAllTools().isEmpty()) { + log.info("CodeactToolSignatureAgentHook#beforeAgent - reason=无已注册的CodeactTool,跳过签名注入"); + return CompletableFuture.completedFuture(Map.of()); + } + + // 检查是否已经注入过(防止多轮对话重复注入) + Optional messagesOpt = state.value("messages"); + if (messagesOpt.isPresent()) { + List messages = (List) messagesOpt.get(); + for (Message msg : messages) { + if (msg instanceof ToolResponseMessage toolMsg) { + for (ToolResponseMessage.ToolResponse response : toolMsg.getResponses()) { + if (INJECTION_TOOL_NAME.equals(response.name())) { + log.info("CodeactToolSignatureAgentHook#beforeAgent - reason=检测到已注入工具签名,跳过"); + return CompletableFuture.completedFuture(Map.of()); + } + } + } + } + } + + String structuredPrompt = codeactToolRegistry.generateStructuredToolPrompt(language); + if (structuredPrompt == null || structuredPrompt.isBlank()) { + log.info("CodeactToolSignatureAgentHook#beforeAgent - reason=生成的工具签名为空,跳过"); + return CompletableFuture.completedFuture(Map.of()); + } + + String content = buildInjectionContent(structuredPrompt); + + String toolCallId = "tool_sig_" + UUID.randomUUID().toString().substring(0, 8); + + AssistantMessage assistantMessage = AssistantMessage.builder() + .toolCalls(List.of(new AssistantMessage.ToolCall(toolCallId, "function", INJECTION_TOOL_NAME, "{}"))) + .build(); + + ToolResponseMessage toolResponseMessage = ToolResponseMessage.builder() + .responses( + List.of(new ToolResponseMessage.ToolResponse(toolCallId, INJECTION_TOOL_NAME, content))) + .build(); + + log.info( + "CodeactToolSignatureAgentHook#beforeAgent - reason=工具签名注入成功, toolCount={}, contentLength={}", + codeactToolRegistry.getAllTools().size(), content.length()); + + return CompletableFuture.completedFuture(Map.of("messages", List.of(assistantMessage, toolResponseMessage))); + + } + catch (Exception e) { + log.error("CodeactToolSignatureAgentHook#beforeAgent - reason=注入工具签名失败", e); + return CompletableFuture.completedFuture(Map.of()); + } + } + + private String buildInjectionContent(String structuredToolPrompt) { + StringBuilder sb = new StringBuilder(); + + sb.append("=== CodeactTool Definitions ===\n\n"); + sb.append("The following tool definitions are ONLY available inside the `code` parameter of `write_code`.\n"); + sb.append("Do NOT call them as React-phase function calls. "); + sb.append("Use them in your Python code via `instance.method(args)` or as global functions.\n\n"); + + sb.append(structuredToolPrompt); + + sb.append("\n=== Usage Guidelines ===\n"); + sb.append("- Write Python code calling these tools inside `write_code`, then run it with `execute_code`.\n"); + sb.append("- Use `try/except` to handle errors and return a result dict.\n"); + sb.append("- If the return type is unspecified (Any), avoid accessing fields directly; "); + sb.append("use `llm_tools.call_llm(source_data=result, ...)` to extract data if available.\n"); + + return sb.toString(); + } + +} diff --git a/assistant-agent-common/pom.xml b/assistant-agent-common/pom.xml index 48e3c8af..e23f0c34 100644 --- a/assistant-agent-common/pom.xml +++ b/assistant-agent-common/pom.xml @@ -6,7 +6,7 @@ com.alibaba.agent.assistant assistant-agent - 0.2.1 + 0.2.2 assistant-agent-common diff --git a/assistant-agent-common/src/main/java/com/alibaba/assistant/agent/common/constant/HookPriorityConstants.java b/assistant-agent-common/src/main/java/com/alibaba/assistant/agent/common/constant/HookPriorityConstants.java index d4675766..a53eba30 100644 --- a/assistant-agent-common/src/main/java/com/alibaba/assistant/agent/common/constant/HookPriorityConstants.java +++ b/assistant-agent-common/src/main/java/com/alibaba/assistant/agent/common/constant/HookPriorityConstants.java @@ -50,6 +50,14 @@ private HookPriorityConstants() { */ public static final int CODEACT_TOOLS_STATE_INIT_HOOK = 5; + /** + * CodeactTool 签名注入 Hook 优先级 + * + *

在 beforeAgent 阶段紧接 StateInit 之后执行,将 CodeactToolRegistry 中所有工具的 + * Python 签名注入到 messages,使 LLM 在 write_code 时能正确调用这些工具。 + */ + public static final int CODEACT_TOOL_SIGNATURE_HOOK = 8; + /** * React 经验 Hook 优先级 * diff --git a/assistant-agent-core/pom.xml b/assistant-agent-core/pom.xml index 21e3e9aa..89fd711a 100644 --- a/assistant-agent-core/pom.xml +++ b/assistant-agent-core/pom.xml @@ -6,7 +6,7 @@ com.alibaba.agent.assistant assistant-agent - 0.2.1 + 0.2.2 assistant-agent-core diff --git a/assistant-agent-evaluation/pom.xml b/assistant-agent-evaluation/pom.xml index 8b2e9e66..fa693944 100644 --- a/assistant-agent-evaluation/pom.xml +++ b/assistant-agent-evaluation/pom.xml @@ -6,7 +6,7 @@ com.alibaba.agent.assistant assistant-agent - 0.2.1 + 0.2.2 assistant-agent-evaluation diff --git a/assistant-agent-extensions/pom.xml b/assistant-agent-extensions/pom.xml index e75eb3e8..f7f2dc77 100644 --- a/assistant-agent-extensions/pom.xml +++ b/assistant-agent-extensions/pom.xml @@ -6,7 +6,7 @@ com.alibaba.agent.assistant assistant-agent - 0.2.1 + 0.2.2 assistant-agent-extensions diff --git a/assistant-agent-prompt-builder/pom.xml b/assistant-agent-prompt-builder/pom.xml index 975ddbf0..00efbb3b 100644 --- a/assistant-agent-prompt-builder/pom.xml +++ b/assistant-agent-prompt-builder/pom.xml @@ -6,7 +6,7 @@ com.alibaba.agent.assistant assistant-agent - 0.2.1 + 0.2.2 assistant-agent-prompt-builder diff --git a/assistant-agent-start/pom.xml b/assistant-agent-start/pom.xml index 72ebf43b..750be049 100644 --- a/assistant-agent-start/pom.xml +++ b/assistant-agent-start/pom.xml @@ -6,7 +6,7 @@ com.alibaba.agent.assistant assistant-agent - 0.2.1 + 0.2.2 assistant-agent-start diff --git a/pom.xml b/pom.xml index 6e5c2297..05c5982a 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 com.alibaba.agent.assistant assistant-agent - 0.2.1 + 0.2.2 pom