Add parameter schema and defaults to ToolResource model and schemas. Implement runtime tool resolution in assistants and tools routers, ensuring proper handling of tool parameters. Update tests to validate new functionality and ensure correct integration of parameter handling in the API.

This commit is contained in:
Xin Wang
2026-02-27 14:44:28 +08:00
parent d942c85eff
commit 5f768edf68
13 changed files with 397 additions and 9 deletions

View File

@@ -2051,13 +2051,20 @@ export const DebugDrawer: React.FC<{
const item = byId.get(id);
const toolId = item?.id || id;
const isClientTool = (item?.category || 'query') === 'system';
const parameterSchema = (item?.parameterSchema && typeof item.parameterSchema === 'object')
? item.parameterSchema
: getDefaultToolParameters(toolId);
const parameterDefaults = (item?.parameterDefaults && typeof item.parameterDefaults === 'object')
? item.parameterDefaults
: undefined;
return {
type: 'function',
executor: isClientTool ? 'client' : 'server',
...(parameterDefaults && Object.keys(parameterDefaults).length > 0 ? { defaultArgs: parameterDefaults } : {}),
function: {
name: toolId,
description: item?.description || item?.name || id,
parameters: getDefaultToolParameters(toolId),
parameters: parameterSchema,
},
};
});

View File

@@ -20,6 +20,12 @@ const iconMap: Record<string, React.ReactNode> = {
Volume2: <Volume2 className="w-5 h-5" />,
};
const DEFAULT_PARAMETER_SCHEMA_TEXT = JSON.stringify(
{ type: 'object', properties: {}, required: [] },
null,
2
);
export const ToolLibraryPage: React.FC = () => {
const [tools, setTools] = useState<Tool[]>([]);
const [searchTerm, setSearchTerm] = useState('');
@@ -37,6 +43,8 @@ export const ToolLibraryPage: React.FC = () => {
const [toolHttpUrl, setToolHttpUrl] = useState('');
const [toolHttpHeadersText, setToolHttpHeadersText] = useState('{}');
const [toolHttpTimeoutMs, setToolHttpTimeoutMs] = useState(10000);
const [toolParameterSchemaText, setToolParameterSchemaText] = useState(DEFAULT_PARAMETER_SCHEMA_TEXT);
const [toolParameterDefaultsText, setToolParameterDefaultsText] = useState('{}');
const [saving, setSaving] = useState(false);
const loadTools = async () => {
@@ -66,6 +74,8 @@ export const ToolLibraryPage: React.FC = () => {
setToolHttpUrl('');
setToolHttpHeadersText('{}');
setToolHttpTimeoutMs(10000);
setToolParameterSchemaText(DEFAULT_PARAMETER_SCHEMA_TEXT);
setToolParameterDefaultsText('{}');
setIsToolModalOpen(true);
};
@@ -80,6 +90,8 @@ export const ToolLibraryPage: React.FC = () => {
setToolHttpUrl(tool.httpUrl || '');
setToolHttpHeadersText(JSON.stringify(tool.httpHeaders || {}, null, 2));
setToolHttpTimeoutMs(tool.httpTimeoutMs || 10000);
setToolParameterSchemaText(JSON.stringify(tool.parameterSchema || { type: 'object', properties: {}, required: [] }, null, 2));
setToolParameterDefaultsText(JSON.stringify(tool.parameterDefaults || {}, null, 2));
setIsToolModalOpen(true);
};
@@ -153,8 +165,52 @@ export const ToolLibraryPage: React.FC = () => {
try {
setSaving(true);
let parsedHeaders: Record<string, string> = {};
let parsedParameterSchema: Record<string, any> = {};
let parsedParameterDefaults: Record<string, any> = {};
try {
const parsed = JSON.parse(toolParameterSchemaText || '{}');
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
throw new Error('schema must be object');
}
parsedParameterSchema = parsed as Record<string, any>;
} catch {
alert('参数 Schema 必须是合法 JSON 对象');
setSaving(false);
return;
}
if (parsedParameterSchema.type && parsedParameterSchema.type !== 'object') {
alert("参数 Schema 的 type 必须是 'object'");
setSaving(false);
return;
}
if (!parsedParameterSchema.type) parsedParameterSchema.type = 'object';
if (!parsedParameterSchema.properties || typeof parsedParameterSchema.properties !== 'object' || Array.isArray(parsedParameterSchema.properties)) {
parsedParameterSchema.properties = {};
}
if (!Array.isArray(parsedParameterSchema.required)) {
parsedParameterSchema.required = [];
}
try {
const parsed = JSON.parse(toolParameterDefaultsText || '{}');
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
throw new Error('defaults must be object');
}
parsedParameterDefaults = parsed as Record<string, any>;
} catch {
alert('参数默认值必须是合法 JSON 对象');
setSaving(false);
return;
}
if (toolCategory === 'query') {
if (!toolHttpUrl.trim() && editingTool?.id !== 'calculator' && editingTool?.id !== 'code_interpreter') {
if (
!toolHttpUrl.trim()
&& editingTool?.id !== 'calculator'
&& editingTool?.id !== 'code_interpreter'
&& editingTool?.id !== 'current_time'
) {
alert('信息查询工具请填写 HTTP URL');
setSaving(false);
return;
@@ -183,6 +239,8 @@ export const ToolLibraryPage: React.FC = () => {
httpUrl: toolHttpUrl.trim(),
httpHeaders: parsedHeaders,
httpTimeoutMs: toolHttpTimeoutMs,
parameterSchema: parsedParameterSchema,
parameterDefaults: parsedParameterDefaults,
enabled: toolEnabled,
});
setTools((prev) => prev.map((item) => (item.id === updated.id ? updated : item)));
@@ -196,6 +254,8 @@ export const ToolLibraryPage: React.FC = () => {
httpUrl: toolHttpUrl.trim(),
httpHeaders: parsedHeaders,
httpTimeoutMs: toolHttpTimeoutMs,
parameterSchema: parsedParameterSchema,
parameterDefaults: parsedParameterDefaults,
enabled: toolEnabled,
});
setTools((prev) => [created, ...prev]);
@@ -368,6 +428,31 @@ export const ToolLibraryPage: React.FC = () => {
/>
</div>
<div className="space-y-4 rounded-md border border-white/10 bg-white/5 p-3">
<div className="text-[10px] font-black uppercase tracking-widest text-emerald-300">Tool Parameters</div>
<div className="space-y-1.5">
<label className="text-[10px] font-black text-muted-foreground uppercase tracking-widest block">Schema (JSON Schema)</label>
<textarea
className="flex min-h-[110px] w-full rounded-md border border-white/10 bg-black/20 px-3 py-2 text-sm shadow-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-primary/50 text-white font-mono"
value={toolParameterSchemaText}
onChange={(e) => setToolParameterSchemaText(e.target.value)}
placeholder={DEFAULT_PARAMETER_SCHEMA_TEXT}
/>
</div>
<div className="space-y-1.5">
<label className="text-[10px] font-black text-muted-foreground uppercase tracking-widest block">Default Args (JSON)</label>
<textarea
className="flex min-h-[90px] w-full rounded-md border border-white/10 bg-black/20 px-3 py-2 text-sm shadow-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-primary/50 text-white font-mono"
value={toolParameterDefaultsText}
onChange={(e) => setToolParameterDefaultsText(e.target.value)}
placeholder='{"step": 1}'
/>
</div>
<p className="text-[11px] text-muted-foreground">
Default Args
</p>
</div>
{toolCategory === 'query' && (
<div className="space-y-4 rounded-md border border-blue-500/20 bg-blue-500/5 p-3">
<div className="text-[10px] font-black uppercase tracking-widest text-blue-300">HTTP Request Config</div>