mirror of
https://github.com/zadam/trilium.git
synced 2026-06-26 21:00:49 +02:00
feat(llm): report tool call errors
This commit is contained in:
@@ -13,7 +13,7 @@ export interface StreamCallbacks {
|
||||
onChunk: (text: string) => void;
|
||||
onThinking?: (text: string) => void;
|
||||
onToolUse?: (toolName: string, input: Record<string, unknown>) => void;
|
||||
onToolResult?: (toolName: string, result: string) => void;
|
||||
onToolResult?: (toolName: string, result: string, isError?: boolean) => void;
|
||||
onCitation?: (citation: LlmCitation) => void;
|
||||
onUsage?: (usage: LlmUsage) => void;
|
||||
onError: (error: string) => void;
|
||||
@@ -78,7 +78,7 @@ export async function streamChatCompletion(
|
||||
callbacks.onToolUse?.(data.toolName, data.toolInput);
|
||||
break;
|
||||
case "tool_result":
|
||||
callbacks.onToolResult?.(data.toolName, data.result);
|
||||
callbacks.onToolResult?.(data.toolName, data.result, data.isError);
|
||||
break;
|
||||
case "citation":
|
||||
if (data.citation) {
|
||||
|
||||
@@ -1627,6 +1627,8 @@
|
||||
"tool_calls": "{{count}} tool call(s)",
|
||||
"input": "Input",
|
||||
"result": "Result",
|
||||
"error": "Error",
|
||||
"tool_error": "failed",
|
||||
"tokens_used": "{{prompt}} prompt + {{completion}} completion = {{total}} tokens",
|
||||
"tokens_used_with_cost": "{{prompt}} prompt + {{completion}} completion = {{total}} tokens (~${{cost}})",
|
||||
"tokens_used_with_model": "{{model}}: {{prompt}} prompt + {{completion}} completion = {{total}} tokens",
|
||||
|
||||
@@ -18,11 +18,17 @@ interface Props {
|
||||
}
|
||||
|
||||
function ToolCallCard({ toolCall }: { toolCall: ToolCall }) {
|
||||
const classes = [
|
||||
"llm-chat-tool-call-inline",
|
||||
toolCall.isError && "llm-chat-tool-call-error"
|
||||
].filter(Boolean).join(" ");
|
||||
|
||||
return (
|
||||
<details className="llm-chat-tool-call-inline">
|
||||
<details className={classes}>
|
||||
<summary className="llm-chat-tool-call-inline-summary">
|
||||
<span className="bx bx-wrench" />
|
||||
<span className={toolCall.isError ? "bx bx-error-circle" : "bx bx-wrench"} />
|
||||
{toolCall.toolName}
|
||||
{toolCall.isError && <span className="llm-chat-tool-call-error-badge">{t("llm_chat.tool_error")}</span>}
|
||||
</summary>
|
||||
<div className="llm-chat-tool-call-inline-body">
|
||||
<div className="llm-chat-tool-call-input">
|
||||
@@ -30,8 +36,8 @@ function ToolCallCard({ toolCall }: { toolCall: ToolCall }) {
|
||||
<pre>{JSON.stringify(toolCall.input, null, 2)}</pre>
|
||||
</div>
|
||||
{toolCall.result && (
|
||||
<div className="llm-chat-tool-call-result">
|
||||
<strong>{t("llm_chat.result")}:</strong>
|
||||
<div className={`llm-chat-tool-call-result ${toolCall.isError ? "llm-chat-tool-call-result-error" : ""}`}>
|
||||
<strong>{toolCall.isError ? t("llm_chat.error") : t("llm_chat.result")}:</strong>
|
||||
<pre>{(() => {
|
||||
if (typeof toolCall.result === "string" && (toolCall.result.startsWith("{") || toolCall.result.startsWith("["))) {
|
||||
try {
|
||||
|
||||
@@ -588,6 +588,27 @@
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
/* Tool call error styling */
|
||||
.llm-chat-tool-call-error {
|
||||
border-left-color: var(--danger-color, #dc3545);
|
||||
}
|
||||
|
||||
.llm-chat-tool-call-error .llm-chat-tool-call-inline-summary {
|
||||
color: var(--danger-color, #dc3545);
|
||||
}
|
||||
|
||||
.llm-chat-tool-call-error-badge {
|
||||
font-size: 0.75rem;
|
||||
font-weight: 400;
|
||||
font-family: var(--main-font-family);
|
||||
color: var(--danger-color, #dc3545);
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.llm-chat-tool-call-result-error pre {
|
||||
color: var(--danger-color, #dc3545);
|
||||
}
|
||||
|
||||
/* Token usage display */
|
||||
.llm-chat-usage {
|
||||
display: flex;
|
||||
|
||||
@@ -7,6 +7,7 @@ export interface ToolCall {
|
||||
toolName: string;
|
||||
input: Record<string, unknown>;
|
||||
result?: string;
|
||||
isError?: boolean;
|
||||
}
|
||||
|
||||
/** A block of text content (rendered as Markdown for assistant messages). */
|
||||
|
||||
@@ -270,12 +270,13 @@ export function useLlmChat(
|
||||
}
|
||||
});
|
||||
},
|
||||
onToolResult: (toolName, result) => {
|
||||
onToolResult: (toolName, result, isError) => {
|
||||
// Find the most recent tool_call block for this tool without a result
|
||||
for (let i = contentBlocks.length - 1; i >= 0; i--) {
|
||||
const block = contentBlocks[i];
|
||||
if (block.type === "tool_call" && block.toolCall.toolName === toolName && !block.toolCall.result) {
|
||||
block.toolCall.result = result;
|
||||
block.toolCall.isError = isError;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,15 +49,19 @@ export async function* streamToChunks(result: StreamResult, options: StreamOptio
|
||||
};
|
||||
break;
|
||||
|
||||
case "tool-result":
|
||||
case "tool-result": {
|
||||
const output = part.output;
|
||||
const isError = typeof output === "object" && output !== null && "error" in output;
|
||||
yield {
|
||||
type: "tool_result",
|
||||
toolName: part.toolName,
|
||||
result: typeof part.output === "string"
|
||||
? part.output
|
||||
: JSON.stringify(part.output)
|
||||
result: typeof output === "string"
|
||||
? output
|
||||
: JSON.stringify(output),
|
||||
isError
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
||||
case "source":
|
||||
// Citation from web search (only URL sources have url property)
|
||||
|
||||
@@ -94,7 +94,7 @@ export type LlmStreamChunk =
|
||||
| { type: "text"; content: string }
|
||||
| { type: "thinking"; content: string }
|
||||
| { type: "tool_use"; toolName: string; toolInput: Record<string, unknown> }
|
||||
| { type: "tool_result"; toolName: string; result: string }
|
||||
| { type: "tool_result"; toolName: string; result: string; isError?: boolean }
|
||||
| { type: "citation"; citation: LlmCitation }
|
||||
| { type: "usage"; usage: LlmUsage }
|
||||
| { type: "error"; error: string }
|
||||
|
||||
Reference in New Issue
Block a user