-
Notifications
You must be signed in to change notification settings - Fork 1.4k
fix(aistio): rename task.submit_result tool to satisfy OpenAI function-name rules #3151
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -29,7 +29,7 @@ | |
| /** Captures intent. Only the adapter is allowed to commit the physical AgentTask lifecycle. */ | ||
| public final class AgentTaskOutcomeTool { | ||
| @Tool( | ||
| name = "task.submit_result", | ||
| name = "task_submit_result", | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Compatibility of the rename for already-persisted sessions. Could you confirm the behaviour on a resumed session with pre-rename history? If it does fail, the options are a read-side rewrite when loading legacy sessions (map the old name to the new one), or stating explicitly in the changelog that in-flight AgentTasks must be re-dispatched rather than resumed. |
||
| description = | ||
| "Submit the actual outcome of this AgentTask, then end your turn. succeeded" | ||
| + " requires the full deliverable in result, not a plan or promise. waiting" | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -132,7 +132,7 @@ private void execute(AgentTaskAssignment assignment) { | |
| + " available CollaborationClient actions are registered as tools with" | ||
| + " the exact names shown in availableActions. The adapter owns" | ||
| + " task.complete and task.fail; do not call them. Before returning," | ||
| + " call task.submit_result with an explicit business outcome and the" | ||
| + " call task_submit_result with an explicit business outcome and the" | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Warning] This is exactly the line where the dotted-name problem survives the rename: Today the first two are prohibitions ("do not call them") so no request payload is generated for them, which is why the PR is enough to unblock the reported failure — but the text still teaches the model a name shape that cannot be called, and the same block says the collaboration tools are registered "with the exact names shown in availableActions", i.e. control-plane supplied. Two questions:
Optional hardening that would make the class of bug impossible: validate every name at registration time in the core toolkit, e.g. throw/warn when a tool name does not match
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is the line where the dotted-name problem survives the rename: the model is still told about
|
||
| + " actual deliverable. A promise to do work later is not completion." | ||
| + " Check the tool capabilities before delegating: spawning a subagent" | ||
| + " does not add missing web access." | ||
|
|
@@ -259,7 +259,7 @@ private AgentTaskOutcome runToOutcome( | |
| next = | ||
| message( | ||
| "The turn ended without a business outcome. Do the remaining work," | ||
| + " or call task.submit_result with blocked and the concrete" | ||
| + " or call task_submit_result with blocked and the concrete" | ||
| + " missing capability. Do not submit a plan or waiting promise" | ||
| + " as successful research."); | ||
| continue; | ||
|
|
@@ -433,7 +433,7 @@ private void registerCollaborationTools( | |
| Set<String> availableActions) { | ||
| Object toolkit = runtimeAgent.getToolkit(); | ||
| synchronized (toolkit) { | ||
| if (!runtimeAgent.getToolkit().getToolNames().contains("task.submit_result")) { | ||
| if (!runtimeAgent.getToolkit().getToolNames().contains("task_submit_result")) { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Warning] Compatibility check for the rename. The tool name is part of what gets persisted in agent session/memory state and replayed on recovery: a session recorded before the upgrade contains a If a transition is needed, the usual cheap pattern is to keep a hidden alias for one release: runtimeAgent.getToolkit().registerTool(new AgentTaskOutcomeTool()); // task_submit_result
runtimeAgent.getToolkit().registerAlias("task.submit_result", AgentTaskOutcomeTool.TOOL_NAME); // deprecated, one release(if the toolkit has no alias support, documenting "restart in-flight AgentTasks after upgrading" in the PR description is enough — the repo has no versioned-upgrade note for extension tool names that I can find.)
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The registered name is now the same literal repeated in 6 places across two files (this guard, the Suggested follow-up (no need to block this PR on it): public static final String SUBMIT_RESULT_TOOL = "task_submit_result";...and build the prompt sentences with that constant (or a |
||
| runtimeAgent.getToolkit().registerTool(new AgentTaskOutcomeTool()); | ||
| } | ||
| for (JsonNode definition : | ||
|
|
@@ -466,12 +466,12 @@ static String roleInstructions(JsonNode envelope, List<String> inputIds) { | |
| return " You are a Team worker, not its coordinator. Do not create or accept child" | ||
| + " Issues and do not call run.node.complete, run.node.fail, or run.replan." | ||
| + " Complete only the assigned work and submit its result using" | ||
| + " task.submit_result; the adapter will complete this AgentTask."; | ||
| + " task_submit_result; the adapter will complete this AgentTask."; | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Info] Nit: these two prompt rewrites now break mid-word ( |
||
| } | ||
| if (inputIds.isEmpty()) { | ||
| return " You are the Team leader's initial task. If you delegate child work, return" | ||
| + " immediately after issue.child.create succeeds by calling" | ||
| + " task.submit_result with waiting, a reason and the returned AgentTask" | ||
| + " task_submit_result with waiting, a reason and the returned AgentTask" | ||
| + " IDs; do not wait through local session/task tools and do not call" | ||
| + " run.node.complete yet. The control plane will deliver a fresh leader" | ||
| + " follow-up when a worker result arrives. If no work is delegated, call" | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -78,6 +78,54 @@ private HarnessAgentTaskStarter starter() throws Exception { | |
| return new HarnessAgentTaskStarter(() -> agent, client); | ||
| } | ||
|
|
||
| @Test | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice addition — the second dispatch does exercise the "already registered" branch of To assert the actual invariant, something like this is cheap: verify(agent.getToolkit(), org.mockito.Mockito.never())
.registerTool(org.mockito.ArgumentMatchers.any(AgentTaskOutcomeTool.class));
// or: assertEquals(1, countOf(agent, AgentTaskOutcomeTool.class))Also consider a small factory for the 12-argument |
||
| void sameToolkitRegistersOutcomeToolOnlyOnce() throws Exception { | ||
| var starter = starter(); | ||
| when(agent.call(any(Msg.class), any(RuntimeContext.class))) | ||
| .thenAnswer( | ||
| invocation -> { | ||
| RuntimeContext ctx = invocation.getArgument(1); | ||
| ctx.get(AgentTaskOutcome.State.class) | ||
| .submit( | ||
| new AgentTaskOutcome( | ||
| "succeeded", "delivered", "", List.of())); | ||
| return Mono.just( | ||
| Msg.builder() | ||
| .role(MsgRole.ASSISTANT) | ||
| .textContent("done") | ||
| .build()); | ||
| }); | ||
| starter.start(assignment).block(); | ||
| // A second dispatch on the same toolkit must hit the "already registered" branch of | ||
| // registerCollaborationTools and still complete normally. | ||
| AgentTaskAssignment second = | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Info] Nit: the 12-argument |
||
| new AgentTaskAssignment( | ||
| "attempt-2", | ||
| "task", | ||
| "run", | ||
| "node", | ||
| 1, | ||
| "dispatch", | ||
| "", | ||
| "secret-token", | ||
| "attempt-secret", | ||
| "assigned-session", | ||
| new byte[0], | ||
| 1); | ||
| starter.start(second).block(); | ||
| verify(agent, times(2)).call(any(Msg.class), any(RuntimeContext.class)); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Info] The test name says "OnlyOnce", but nothing here asserts "only once" — |
||
| verify(client, times(2)) | ||
| .finish( | ||
| eq("task"), | ||
| eq("secret-token"), | ||
| eq(4L), | ||
| eq("succeeded"), | ||
| eq(""), | ||
| eq("delivered"), | ||
| eq(List.of()), | ||
| eq(List.of())); | ||
| } | ||
|
|
||
| @Test | ||
| void plainWaitingPromiseCannotBecomeSuccessfulBusinessCompletion() throws Exception { | ||
| var starter = starter(); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[Warning] The registered name and the prompt/guard references are now the same string literal repeated in 5 places across two files (
HarnessAgentTaskStarterrole instructions, the "call task_submit_result" nudge, the retry message and thegetToolNames().contains("task_submit_result")guard). Any future rename has to touch all of them, and a partial update fails silently: the registration would succeed while the guard re-registers a duplicate tool, or the model would be told to call a name that is not registered.Could you hoist it into a constant and reference it everywhere?
and in the adapter:
if (!runtimeAgent.getToolkit().getToolNames().contains(AgentTaskOutcomeTool.TOOL_NAME)). Annotation values must be compile-time constants, which astatic final Stringliteral satisfies.