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

@@ -287,6 +287,7 @@ class DuplexPipeline:
raw_default_tools = settings.tools if isinstance(settings.tools, list) else []
self._runtime_tools: List[Any] = list(raw_default_tools)
self._runtime_tool_executor: Dict[str, str] = {}
self._runtime_tool_default_args: Dict[str, Dict[str, Any]] = {}
self._pending_tool_waiters: Dict[str, asyncio.Future] = {}
self._early_tool_results: Dict[str, Dict[str, Any]] = {}
self._completed_tool_call_ids: set[str] = set()
@@ -307,6 +308,7 @@ class DuplexPipeline:
self._last_llm_delta_emit_ms: float = 0.0
self._runtime_tool_executor = self._resolved_tool_executor_map()
self._runtime_tool_default_args = self._resolved_tool_default_args_map()
self._initial_greeting_emitted = False
if self._server_tool_executor is None:
@@ -408,9 +410,11 @@ class DuplexPipeline:
if isinstance(tools_payload, list):
self._runtime_tools = tools_payload
self._runtime_tool_executor = self._resolved_tool_executor_map()
self._runtime_tool_default_args = self._resolved_tool_default_args_map()
elif "tools" in metadata:
self._runtime_tools = []
self._runtime_tool_executor = {}
self._runtime_tool_default_args = {}
if self.llm_service and hasattr(self.llm_service, "set_knowledge_config"):
self.llm_service.set_knowledge_config(self._resolved_knowledge_config())
@@ -1473,6 +1477,25 @@ class DuplexPipeline:
result[name] = executor
return result
def _resolved_tool_default_args_map(self) -> Dict[str, Dict[str, Any]]:
result: Dict[str, Dict[str, Any]] = {}
for item in self._runtime_tools:
if not isinstance(item, dict):
continue
fn = item.get("function")
if isinstance(fn, dict) and fn.get("name"):
name = str(fn.get("name")).strip()
else:
name = str(item.get("name") or "").strip()
if not name:
continue
raw_defaults = item.get("defaultArgs")
if raw_defaults is None:
raw_defaults = item.get("default_args")
if isinstance(raw_defaults, dict):
result[name] = dict(raw_defaults)
return result
def _resolved_tool_allowlist(self) -> List[str]:
names: set[str] = set()
for item in self._runtime_tools:
@@ -1518,6 +1541,15 @@ class DuplexPipeline:
return {"raw": raw}
return {}
def _apply_tool_default_args(self, tool_name: str, args: Dict[str, Any]) -> Dict[str, Any]:
defaults = self._runtime_tool_default_args.get(tool_name)
if not isinstance(defaults, dict) or not defaults:
return args
merged = dict(defaults)
if isinstance(args, dict):
merged.update(args)
return merged
def _normalize_tool_result(self, result: Dict[str, Any]) -> Dict[str, Any]:
status = result.get("status") if isinstance(result.get("status"), dict) else {}
status_code = int(status.get("code") or 0) if status else 0
@@ -1702,15 +1734,27 @@ class DuplexPipeline:
enriched_tool_call["executor"] = executor
tool_name = self._tool_name(enriched_tool_call) or "unknown_tool"
call_id = str(enriched_tool_call.get("id") or "").strip()
fn_payload = enriched_tool_call.get("function")
fn_payload = (
dict(enriched_tool_call.get("function"))
if isinstance(enriched_tool_call.get("function"), dict)
else None
)
raw_args = str(fn_payload.get("arguments") or "") if isinstance(fn_payload, dict) else ""
tool_arguments = self._tool_arguments(enriched_tool_call)
merged_tool_arguments = self._apply_tool_default_args(tool_name, tool_arguments)
try:
merged_args_text = json.dumps(merged_tool_arguments, ensure_ascii=False)
except Exception:
merged_args_text = raw_args if raw_args else "{}"
if isinstance(fn_payload, dict):
fn_payload["arguments"] = merged_args_text
enriched_tool_call["function"] = fn_payload
args_preview = raw_args if len(raw_args) <= 160 else f"{raw_args[:160]}..."
logger.info(
f"[Tool] call requested name={tool_name} call_id={call_id} "
f"executor={executor} args={args_preview}"
f"executor={executor} args={args_preview} merged_args={merged_args_text}"
)
tool_calls.append(enriched_tool_call)
tool_arguments = self._tool_arguments(enriched_tool_call)
if executor == "client" and call_id:
self._pending_client_tool_call_ids.add(call_id)
await self._send_event(