refac
This commit is contained in:
1 parent
4807866a1c
commit
f3f76095d1
4 files changed
+183
-40
No files matched your search
@@ -142,6 +142,48 @@ logging.basicConfig(stream=sys.stdout, level=GLOBAL_LOG_LEVEL)
|
|||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def _is_tool_result_error(value: Any) -> bool:
|
||||||
|
if isinstance(value, str):
|
||||||
|
text = value.strip().lower()
|
||||||
|
if (
|
||||||
|
text.startswith('error:')
|
||||||
|
or text.startswith('exception:')
|
||||||
|
or text.startswith('traceback')
|
||||||
|
or text.startswith('http error!')
|
||||||
|
):
|
||||||
|
return True
|
||||||
|
|
||||||
|
parsed = value
|
||||||
|
while isinstance(parsed, str):
|
||||||
|
try:
|
||||||
|
parsed = JSONCodec.loads(parsed)
|
||||||
|
except (JSONCodec.JSONDecodeError, TypeError, ValueError):
|
||||||
|
break
|
||||||
|
|
||||||
|
if not isinstance(parsed, dict):
|
||||||
|
return False
|
||||||
|
|
||||||
|
error = parsed.get('error')
|
||||||
|
if isinstance(error, str):
|
||||||
|
has_error = bool(error.strip())
|
||||||
|
else:
|
||||||
|
has_error = isinstance(error, (dict, list)) and bool(error)
|
||||||
|
if has_error:
|
||||||
|
return True
|
||||||
|
|
||||||
|
status = parsed.get('status')
|
||||||
|
if isinstance(status, str) and status.strip().lower() in {'error', 'failed'}:
|
||||||
|
return True
|
||||||
|
|
||||||
|
if parsed.get('success') is False or parsed.get('ok') is False:
|
||||||
|
message = parsed.get('message')
|
||||||
|
return has_error or (
|
||||||
|
bool(message.strip()) if isinstance(message, str) else isinstance(message, (dict, list)) and bool(message)
|
||||||
|
)
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
async def publish_chat_finished_event(
|
async def publish_chat_finished_event(
|
||||||
request: Request, user: UserModel, metadata: dict, title: str, content: str, output: list | None = None
|
request: Request, user: UserModel, metadata: dict, title: str, content: str, output: list | None = None
|
||||||
):
|
):
|
||||||
@@ -1299,7 +1341,7 @@ async def chat_completion_tools_handler(
|
|||||||
tool_result = await tool_function(**tool_function_params)
|
tool_result = await tool_function(**tool_function_params)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
tool_result = str(e)
|
tool_result = {'error': str(e)}
|
||||||
|
|
||||||
tool_result, tool_result_files, tool_result_embeds = await process_tool_result(
|
tool_result, tool_result_files, tool_result_embeds = await process_tool_result(
|
||||||
request,
|
request,
|
||||||
@@ -3076,7 +3118,7 @@ async def execute_tool_call_for_output(request, form_data, user, metadata, event
|
|||||||
)
|
)
|
||||||
result = await function(**params)
|
result = await function(**params)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
result = str(e)
|
result = {'error': str(e)}
|
||||||
|
|
||||||
result, files, embeds = await process_tool_result(
|
result, files, embeds = await process_tool_result(
|
||||||
request,
|
request,
|
||||||
@@ -3098,28 +3140,6 @@ async def execute_tool_call_for_output(request, form_data, user, metadata, event
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def append_tool_result_output(output: list[dict], result: dict) -> None:
|
|
||||||
output_parts = [{'type': 'input_text', 'text': result.get('content', '')}]
|
|
||||||
display_files = []
|
|
||||||
for file_item in result.get('files', []):
|
|
||||||
if file_item.get('type') == 'image' and file_item.get('url', '').startswith('data:'):
|
|
||||||
output_parts.append({'type': 'input_image', 'image_url': file_item['url']})
|
|
||||||
else:
|
|
||||||
display_files.append(file_item)
|
|
||||||
|
|
||||||
output.append(
|
|
||||||
{
|
|
||||||
'type': 'function_call_output',
|
|
||||||
'id': output_id('fco'),
|
|
||||||
'call_id': result.get('tool_call_id', ''),
|
|
||||||
'output': output_parts,
|
|
||||||
'status': 'completed',
|
|
||||||
**({'files': display_files} if display_files else {}),
|
|
||||||
**({'embeds': result.get('embeds')} if result.get('embeds') else {}),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
async def drain_approved_tool_calls(request, form_data, user, model, metadata) -> bool:
|
async def drain_approved_tool_calls(request, form_data, user, model, metadata) -> bool:
|
||||||
chat_id = metadata.get('chat_id')
|
chat_id = metadata.get('chat_id')
|
||||||
message_id = metadata.get('message_id') or metadata.get('assistant_message_id')
|
message_id = metadata.get('message_id') or metadata.get('assistant_message_id')
|
||||||
@@ -3186,9 +3206,27 @@ async def drain_approved_tool_calls(request, form_data, user, model, metadata) -
|
|||||||
event_emitter,
|
event_emitter,
|
||||||
tool_call,
|
tool_call,
|
||||||
)
|
)
|
||||||
item['status'] = 'completed'
|
|
||||||
item['arguments'] = tool_call.get('function', {}).get('arguments', '{}')
|
item['arguments'] = tool_call.get('function', {}).get('arguments', '{}')
|
||||||
append_tool_result_output(output, result)
|
output_parts = [{'type': 'input_text', 'text': result.get('content', '')}]
|
||||||
|
item['status'] = 'failed' if _is_tool_result_error(result.get('content', '')) else 'completed'
|
||||||
|
display_files = []
|
||||||
|
for file_item in result.get('files', []):
|
||||||
|
if file_item.get('type') == 'image' and file_item.get('url', '').startswith('data:'):
|
||||||
|
output_parts.append({'type': 'input_image', 'image_url': file_item['url']})
|
||||||
|
else:
|
||||||
|
display_files.append(file_item)
|
||||||
|
|
||||||
|
output.append(
|
||||||
|
{
|
||||||
|
'type': 'function_call_output',
|
||||||
|
'id': output_id('fco'),
|
||||||
|
'call_id': result.get('tool_call_id', ''),
|
||||||
|
'output': output_parts,
|
||||||
|
'status': item['status'],
|
||||||
|
**({'files': display_files} if display_files else {}),
|
||||||
|
**({'embeds': result.get('embeds')} if result.get('embeds') else {}),
|
||||||
|
}
|
||||||
|
)
|
||||||
changed = True
|
changed = True
|
||||||
|
|
||||||
if changed:
|
if changed:
|
||||||
@@ -5489,7 +5527,7 @@ async def streaming_chat_response_handler(response, ctx):
|
|||||||
)
|
)
|
||||||
result = await function(**params)
|
result = await function(**params)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
result = str(e)
|
result = {'error': str(e)}
|
||||||
return params, result, tool, tool_type, direct_tool
|
return params, result, tool, tool_type, direct_tool
|
||||||
|
|
||||||
delegate_calls = [
|
delegate_calls = [
|
||||||
@@ -5575,19 +5613,13 @@ async def streaming_chat_response_handler(response, ctx):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
# Update function_call statuses and append function_call_output items
|
result_status_by_call_id = {}
|
||||||
for tc in response_tool_calls:
|
|
||||||
call_id = tc.get('id', '')
|
|
||||||
# Mark function_call as completed
|
|
||||||
for item in output:
|
|
||||||
if item.get('type') == 'function_call' and item.get('call_id') == call_id:
|
|
||||||
item['status'] = 'completed'
|
|
||||||
# Update arguments with parsed/sanitized version
|
|
||||||
item['arguments'] = tc.get('function', {}).get('arguments', '{}')
|
|
||||||
break
|
|
||||||
|
|
||||||
for result in results:
|
for result in results:
|
||||||
output_parts = [{'type': 'input_text', 'text': result.get('content', '')}]
|
output_parts = [{'type': 'input_text', 'text': result.get('content', '')}]
|
||||||
|
local_output_status = (
|
||||||
|
'failed' if _is_tool_result_error(result.get('content', '')) else 'completed'
|
||||||
|
)
|
||||||
|
result_status_by_call_id[result.get('tool_call_id', '')] = local_output_status
|
||||||
|
|
||||||
# Separate image data URIs (for LLM via input_image) from
|
# Separate image data URIs (for LLM via input_image) from
|
||||||
# other files (for frontend display via files attribute).
|
# other files (for frontend display via files attribute).
|
||||||
@@ -5606,12 +5638,21 @@ async def streaming_chat_response_handler(response, ctx):
|
|||||||
'id': output_id('fco'),
|
'id': output_id('fco'),
|
||||||
'call_id': result.get('tool_call_id', ''),
|
'call_id': result.get('tool_call_id', ''),
|
||||||
'output': output_parts,
|
'output': output_parts,
|
||||||
'status': 'completed',
|
'status': local_output_status,
|
||||||
**({'files': display_files} if display_files else {}),
|
**({'files': display_files} if display_files else {}),
|
||||||
**({'embeds': result.get('embeds')} if result.get('embeds') else {}),
|
**({'embeds': result.get('embeds')} if result.get('embeds') else {}),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Update function_call statuses and parsed/sanitized arguments.
|
||||||
|
for tc in response_tool_calls:
|
||||||
|
call_id = tc.get('id', '')
|
||||||
|
for item in output:
|
||||||
|
if item.get('type') == 'function_call' and item.get('call_id') == call_id:
|
||||||
|
item['status'] = result_status_by_call_id.get(call_id, 'completed')
|
||||||
|
item['arguments'] = tc.get('function', {}).get('arguments', '{}')
|
||||||
|
break
|
||||||
|
|
||||||
# Append a new empty message item for the next response
|
# Append a new empty message item for the next response
|
||||||
output.append(
|
output.append(
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -312,7 +312,7 @@ def convert_output_to_messages(
|
|||||||
for item in output
|
for item in output
|
||||||
if item.get('type') == 'function_call'
|
if item.get('type') == 'function_call'
|
||||||
and item.get('call_id')
|
and item.get('call_id')
|
||||||
and item.get('status') in {'completed', 'rejected'}
|
and item.get('status') in {'completed', 'failed', 'rejected'}
|
||||||
}
|
}
|
||||||
result_call_ids = {
|
result_call_ids = {
|
||||||
item.get('call_id') for item in output if item.get('type') == 'function_call_output' and item.get('call_id')
|
item.get('call_id') for item in output if item.get('type') == 'function_call_output' and item.get('call_id')
|
||||||
|
|||||||
@@ -21,6 +21,7 @@
|
|||||||
export let id = '';
|
export let id = '';
|
||||||
export let tokens: Array<{
|
export let tokens: Array<{
|
||||||
summary?: string;
|
summary?: string;
|
||||||
|
text?: string;
|
||||||
attributes?: {
|
attributes?: {
|
||||||
type?: string;
|
type?: string;
|
||||||
name?: string;
|
name?: string;
|
||||||
@@ -50,6 +51,49 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isToolResultError(value: unknown): boolean {
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
const text = value.trim().toLowerCase();
|
||||||
|
if (
|
||||||
|
text.startsWith('error:') ||
|
||||||
|
text.startsWith('exception:') ||
|
||||||
|
text.startsWith('traceback') ||
|
||||||
|
text.startsWith('http error!')
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let parsed = value;
|
||||||
|
while (typeof parsed === 'string') {
|
||||||
|
try {
|
||||||
|
parsed = JSON.parse(parsed);
|
||||||
|
} catch {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) return false;
|
||||||
|
|
||||||
|
const result = parsed as Record<string, unknown>;
|
||||||
|
const error = result.error;
|
||||||
|
if (
|
||||||
|
(typeof error === 'string' && error.trim().length > 0) ||
|
||||||
|
(typeof error === 'object' && error !== null)
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const status = typeof result.status === 'string' ? result.status.trim().toLowerCase() : '';
|
||||||
|
if (status === 'error' || status === 'failed') return true;
|
||||||
|
|
||||||
|
const message = result.message;
|
||||||
|
return (
|
||||||
|
(result.success === false || result.ok === false) &&
|
||||||
|
((typeof message === 'string' && message.trim().length > 0) ||
|
||||||
|
(typeof message === 'object' && message !== null))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
$: toolCallCount = tokens.filter((t) => t?.attributes?.type === 'tool_calls').length;
|
$: toolCallCount = tokens.filter((t) => t?.attributes?.type === 'tool_calls').length;
|
||||||
$: reasoningCount = tokens.filter((t) => t?.attributes?.type === 'reasoning').length;
|
$: reasoningCount = tokens.filter((t) => t?.attributes?.type === 'reasoning').length;
|
||||||
$: pendingToolTokens = tokens.filter(
|
$: pendingToolTokens = tokens.filter(
|
||||||
@@ -66,6 +110,12 @@
|
|||||||
$: hasRejected = tokens.some(
|
$: hasRejected = tokens.some(
|
||||||
(t) => t?.attributes?.type === 'tool_calls' && t?.attributes?.status === 'rejected'
|
(t) => t?.attributes?.type === 'tool_calls' && t?.attributes?.status === 'rejected'
|
||||||
);
|
);
|
||||||
|
$: hasError = tokens.some(
|
||||||
|
(t) =>
|
||||||
|
t?.attributes?.type === 'tool_calls' &&
|
||||||
|
(t?.attributes?.status === 'failed' ||
|
||||||
|
(t?.attributes?.done === 'true' && isToolResultError(decode(t?.text ?? ''))))
|
||||||
|
);
|
||||||
|
|
||||||
$: codeInterpreterCount = tokens.filter((t) => t?.attributes?.type === 'code_interpreter').length;
|
$: codeInterpreterCount = tokens.filter((t) => t?.attributes?.type === 'code_interpreter').length;
|
||||||
|
|
||||||
@@ -157,6 +207,10 @@
|
|||||||
<div class="text-red-400 dark:text-red-500">
|
<div class="text-red-400 dark:text-red-500">
|
||||||
<XMark className="size-4" strokeWidth="2.5" />
|
<XMark className="size-4" strokeWidth="2.5" />
|
||||||
</div>
|
</div>
|
||||||
|
{:else if toolCallCount > 0 && hasError}
|
||||||
|
<div class="text-red-500 dark:text-red-400">
|
||||||
|
<XMark className="size-4" strokeWidth="2.5" />
|
||||||
|
</div>
|
||||||
{:else if toolCallCount > 0}
|
{:else if toolCallCount > 0}
|
||||||
<div class="text-emerald-500 dark:text-emerald-400">
|
<div class="text-emerald-500 dark:text-emerald-400">
|
||||||
<CheckCircle className="size-4" strokeWidth="2" />
|
<CheckCircle className="size-4" strokeWidth="2" />
|
||||||
|
|||||||
@@ -90,6 +90,49 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isToolResultError(value: unknown): boolean {
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
const text = value.trim().toLowerCase();
|
||||||
|
if (
|
||||||
|
text.startsWith('error:') ||
|
||||||
|
text.startsWith('exception:') ||
|
||||||
|
text.startsWith('traceback') ||
|
||||||
|
text.startsWith('http error!')
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let parsed = value;
|
||||||
|
while (typeof parsed === 'string') {
|
||||||
|
try {
|
||||||
|
parsed = JSON.parse(parsed);
|
||||||
|
} catch {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) return false;
|
||||||
|
|
||||||
|
const result = parsed as Record<string, unknown>;
|
||||||
|
const error = result.error;
|
||||||
|
if (
|
||||||
|
(typeof error === 'string' && error.trim().length > 0) ||
|
||||||
|
(typeof error === 'object' && error !== null)
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const status = typeof result.status === 'string' ? result.status.trim().toLowerCase() : '';
|
||||||
|
if (status === 'error' || status === 'failed') return true;
|
||||||
|
|
||||||
|
const message = result.message;
|
||||||
|
return (
|
||||||
|
(result.success === false || result.ok === false) &&
|
||||||
|
((typeof message === 'string' && message.trim().length > 0) ||
|
||||||
|
(typeof message === 'object' && message !== null))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export let resultContent: string = '';
|
export let resultContent: string = '';
|
||||||
|
|
||||||
$: result = resultContent || decode(attributes?.result ?? '');
|
$: result = resultContent || decode(attributes?.result ?? '');
|
||||||
@@ -110,6 +153,7 @@
|
|||||||
$: isExecuting = !isDone && !isRejected && attributes?.status === 'completed';
|
$: isExecuting = !isDone && !isRejected && attributes?.status === 'completed';
|
||||||
$: isPreparing = !isDone && !isRejected && !needsApproval && !needsInput && !isExecuting;
|
$: isPreparing = !isDone && !isRejected && !needsApproval && !needsInput && !isExecuting;
|
||||||
$: isActive = isPreparing || isExecuting;
|
$: isActive = isPreparing || isExecuting;
|
||||||
|
$: isError = attributes?.status === 'failed' || (isDone && isToolResultError(result));
|
||||||
|
|
||||||
$: parsedArgs = parseArguments(args);
|
$: parsedArgs = parseArguments(args);
|
||||||
$: parsedResult = parseJSONString(result);
|
$: parsedResult = parseJSONString(result);
|
||||||
@@ -171,6 +215,10 @@
|
|||||||
<div class="text-red-400 dark:text-red-500">
|
<div class="text-red-400 dark:text-red-500">
|
||||||
<XMark className="size-4" strokeWidth="2.5" />
|
<XMark className="size-4" strokeWidth="2.5" />
|
||||||
</div>
|
</div>
|
||||||
|
{:else if isError}
|
||||||
|
<div class="text-red-500 dark:text-red-400">
|
||||||
|
<XMark className="size-4" strokeWidth="2.5" />
|
||||||
|
</div>
|
||||||
{:else if isDone}
|
{:else if isDone}
|
||||||
<div class="text-emerald-500 dark:text-emerald-400">
|
<div class="text-emerald-500 dark:text-emerald-400">
|
||||||
<CheckCircle className="size-4" strokeWidth="2" />
|
<CheckCircle className="size-4" strokeWidth="2" />
|
||||||
|
|||||||
Reference in new issue
Block a user