Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
935db41
[Agent Builder] stream reasoning events
pgayvallet Apr 29, 2026
5feca09
update researcher instructions
pgayvallet Apr 29, 2026
c7e7aa0
Merge remote-tracking branch 'upstream/main' into ab-xxx-remove-answe…
pgayvallet Apr 29, 2026
e4ebac7
feat(agent_builder): add cycle-limit fallback message helper
pgayvallet Apr 29, 2026
ea1de2f
feat(agent_builder): reserve prepareFallbackAnswer step constant
pgayvallet Apr 29, 2026
8175f4a
refactor(agent_builder): thread structuredOutput into convertGraphEvents
pgayvallet Apr 29, 2026
5c18d29
refactor(agent_builder): emit messageEvent from finalize step
pgayvallet Apr 29, 2026
14a7d1f
feat(agent_builder): emit backdated thinkingCompleteEvent for non-str…
pgayvallet Apr 29, 2026
7e329f2
refactor(agent_builder): make finalize read mainActions in non-struct…
pgayvallet Apr 29, 2026
f9fee53
feat(agent_builder): replace answer step with direct finalize in non-…
pgayvallet Apr 29, 2026
ba54582
refactor(agent_builder): drop processAnswerResponse and tighten struc…
pgayvallet Apr 29, 2026
66b07e2
refactor(agent_builder): remove AnswerAction type and helpers
pgayvallet Apr 29, 2026
b75217d
refactor(agent_builder): drop non-structured answer prompt and factor…
pgayvallet Apr 29, 2026
59608d1
Merge remote-tracking branch 'upstream/main' into ab-xxx-remove-answe…
pgayvallet May 19, 2026
7790b53
Changes from node scripts/check
kibanamachine May 19, 2026
965bfca
fix unit tests
pgayvallet May 19, 2026
14b35a5
adapt scout tests due to removal (1)
pgayvallet May 19, 2026
0b8640b
adapt scout tests due to removal (2)
pgayvallet May 19, 2026
f36b38c
reset messageId between calls
pgayvallet May 20, 2026
af49cf8
remove dummy default message - throw instead
pgayvallet May 20, 2026
ae707dc
Merge remote-tracking branch 'upstream/main' into ab-xxx-remove-answe…
pgayvallet May 29, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ export enum AgentExecutionErrorCode {
invalidState = 'invalid_state',
/** connector returned an HTTP error (4xx, 5xx) - status propagated to client */
connectorError = 'connector_error',
/** agent did not produce a final answer within its cycle budget */
cycleLimitExceeded = 'cycle_limit_exceeded',
}

export interface ToolNotFoundErrorMeta {
Expand Down Expand Up @@ -50,6 +52,7 @@ interface ExecutionErrorMetaMap {
[AgentExecutionErrorCode.invalidState]: {};
[AgentExecutionErrorCode.emptyResponse]: {};
[AgentExecutionErrorCode.connectorError]: ConnectorErrorMeta;
[AgentExecutionErrorCode.cycleLimitExceeded]: {};
}

export type ExecutionErrorMetaOf<ErrCode extends AgentExecutionErrorCode> =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,23 @@ describe('toolToLangchain', () => {
expect(langchainTool.description).toEqual('desc');
expect(langchainTool.responseFormat).toEqual('content_and_artifact');

const toolKeys = Object.keys((langchainTool.schema as any).shape);
expect(toolKeys.sort()).toEqual(['foo']);
});

it('adds reasoning param when specified', async () => {
const tool = createTool('toolA', {
description: 'desc',
getSchema: () => z.object({ foo: z.string() }),
});

const langchainTool = await toolToLangchain({
tool,
toolId: tool.id,
logger,
addReasoningParam: true,
});

const toolKeys = Object.keys((langchainTool.schema as any).shape);
expect(toolKeys.sort()).toEqual(['_reasoning', 'foo']);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ export const toolToLangchain = async ({
toolId,
logger,
sendEvent,
addReasoningParam = true,
addReasoningParam = false,
}: {
tool: ExecutableTool;
toolId?: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import type { AIMessageChunk, BaseMessage, ToolMessage } from '@langchain/core/m
import { isToolMessage } from '@langchain/core/messages';
import {
extractTextContent,
extractToolCalls,
extractToolCallsWithReasoning,
} from '@kbn/agent-builder-genai-utils/langchain';
import { createAgentExecutionError } from '@kbn/agent-builder-common/base/errors';
Expand All @@ -22,15 +21,13 @@ import type {
AgentErrorAction,
ExecuteToolAction,
ToolPromptAction,
AnswerAction,
StructuredAnswerAction,
} from './actions';
import {
toolCallAction,
handoverAction,
executeToolAction,
toolPromptAction,
answerAction,
errorAction,
structuredAnswerAction,
} from './actions';
Expand Down Expand Up @@ -121,56 +118,20 @@ export const processToolNodeResponse = (
return actions;
};

export const processAnswerResponse = (message: AIMessageChunk): AnswerAction | AgentErrorAction => {
// The answering agent should not call tools. Some models/providers can still emit tool calls
// unexpectedly, so we treat that as a recoverable error and retry with an explicit tool-result
// error message in the prompt history.
if (message.tool_calls?.length) {
const [firstToolCall] = extractToolCalls(message);
const toolName = firstToolCall?.toolName ?? 'unknown';
const toolArgs = firstToolCall?.args ?? {};

return errorAction(
createAgentExecutionError(
`Answer agent attempted to call tool "${toolName}"`,
AgentExecutionErrorCode.toolNotFound,
{ toolName, toolArgs }
)
);
}

const textContent = extractTextContent(message);
if (textContent) {
return answerAction(extractTextContent(message));
} else {
export const processStructuredAnswerResponse = (
response: unknown
): StructuredAnswerAction | AgentErrorAction => {
try {
if (response && typeof response === 'object') {
return structuredAnswerAction(response);
}
return errorAction(
createAgentExecutionError(
'agent returned an empty response',
'agent returned an invalid structured response',
AgentExecutionErrorCode.emptyResponse,
{}
)
);
}
};

export const processStructuredAnswerResponse = (
response: unknown
): StructuredAnswerAction | AnswerAction | AgentErrorAction => {
try {
if (response && typeof response === 'object') {
const action = structuredAnswerAction(response);
return action;
} else if (typeof response === 'string') {
return answerAction(response);
} else {
return errorAction(
createAgentExecutionError(
'agent returned an invalid structured response',
AgentExecutionErrorCode.emptyResponse,
{}
)
);
}
} catch (error) {
return errorAction(
createAgentExecutionError(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ export enum AgentActionType {
ExecuteTool = 'execute_tool',
ToolPrompt = 'tool_prompt',
HandOver = 'hand_over',
Answer = 'answer',
StructuredAnswer = 'structured_answer',
BackgroundExecutionComplete = 'background_execution_complete',
}
Expand Down Expand Up @@ -82,17 +81,12 @@ export type ResearchAgentAction =

// answer phase actions

export interface AnswerAction {
type: AgentActionType.Answer;
message: string;
}

export interface StructuredAnswerAction {
type: AgentActionType.StructuredAnswer;
data: object;
}

export type AnswerAgentAction = AnswerAction | StructuredAnswerAction | AgentErrorAction;
export type AnswerAgentAction = StructuredAnswerAction | AgentErrorAction;

// all possible actions for the agent flow

Expand Down Expand Up @@ -120,10 +114,6 @@ export function isHandoverAction(action: AgentAction): action is HandoverAction
return action.type === AgentActionType.HandOver;
}

export function isAnswerAction(action: AgentAction): action is AnswerAction {
return action.type === AgentActionType.Answer;
}

export function isStructuredAnswerAction(action: AgentAction): action is StructuredAnswerAction {
return action.type === AgentActionType.StructuredAnswer;
}
Expand Down Expand Up @@ -190,13 +180,6 @@ export function handoverAction(message: string, forceful: boolean = false): Hand
};
}

export function answerAction(message: string): AnswerAction {
return {
type: AgentActionType.Answer,
message,
};
}

export function structuredAnswerAction(data: object): StructuredAnswerAction {
return {
type: AgentActionType.StructuredAnswer,
Expand Down
Loading
Loading