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:
@@ -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,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user