? ? ? ? 本文從最簡單的時間工具入手,分析Tools相關的代碼。
一、安裝工具
git clone https://github.com/open-webui/openapi-servers
cd openapi-servers# 進入時間工具目錄
cd servers/timepip install -r requirements.txt
# 啟動服務
uvicorn main:app --host 0.0.0.0 --reload #缺省使用8000端口
二、配置
以admin登錄webui,配置->工具,增加安裝完成的工具地址:
在聊天窗口出現安裝的工具:
在對話高級設置,設置函數調用(Function Calling)設置為原生。
? ? ? ? 三、代碼分析
? ? ? ?1)主要流程
? ? ? ?在交互過程中,工具調用相關流程如下圖所示:
? ? ? ? 2)入口參數
? ? ? ? http://{ip:port}/api/chat/completions入口參數如下,與前述對比其中增加了tool_servers,其中包含了所有工具的說明。
{
"stream": true,
"model": "deepseek-r1:1.5b",
"messages": [
{
"role": "user",
"content": "請告訴現在東京的時間"
}
],
"params": {},
"tool_servers": [
{
"url": "http://192.168.21.201:8000",
"openapi": {
"openapi": "3.1.0",
"info": {
"title": "Secure Time Utilities API",
"description": "Provides secure UTC/local time retrieval, formatting, timezone conversion, and comparison.",
"version": "1.0.0"
},
"paths": {
"/get_current_utc_time": {
"get": {
"summary": "Current UTC time",
"description": "Returns the current time in UTC in ISO format.",
"operationId": "get_current_utc_get_current_utc_time_get",
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {}
}
}
}
}
}
},
"/get_current_local_time": {
"get": {
"summary": "Current Local Time",
"description": "Returns the current time in local timezone in ISO format.",
"operationId": "get_current_local_get_current_local_time_get",
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {}
}
}
}
}
}
},
"/format_time": {
"post": {
"summary": "Format current time",
"description": "Return the current time formatted for a specific timezone and format.",
"operationId": "format_current_time_format_time_post",
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FormatTimeInput"
}
}
},
"required": true
},
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
}
},
"/convert_time": {
"post": {
"summary": "Convert between timezones",
"description": "Convert a timestamp from one timezone to another.",
"operationId": "convert_time_convert_time_post",
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ConvertTimeInput"
}
}
},
"required": true
},
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
}
},
"/elapsed_time": {
"post": {
"summary": "Time elapsed between timestamps",
"description": "Calculate the difference between two timestamps in chosen units.",
"operationId": "elapsed_time_elapsed_time_post",
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ElapsedTimeInput"
}
}
},
"required": true
},
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
}
},
"/parse_timestamp": {
"post": {
"summary": "Parse and normalize timestamps",
"description": "Parse human-friendly input timestamp and return standardized UTC ISO time.",
"operationId": "parse_timestamp_parse_timestamp_post",
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ParseTimestampInput"
}
}
},
"required": true
},
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
}
},
"/list_time_zones": {
"get": {
"summary": "All valid time zones",
"description": "Return a list of all valid IANA time zones.",
"operationId": "list_time_zones_list_time_zones_get",
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {}
}
}
}
}
}
}
},
"components": {
"schemas": {
"ConvertTimeInput": {
"properties": {
"timestamp": {
"type": "string",
"title": "Timestamp",
"description": "ISO 8601 formatted time string (e.g., 2024-01-01T12:00:00Z)"
},
"from_tz": {
"type": "string",
"title": "From Tz",
"description": "Original IANA time zone of input (e.g. UTC or Europe/Berlin)"
},
"to_tz": {
"type": "string",
"title": "To Tz",
"description": "Target IANA time zone to convert to"
}
},
"type": "object",
"required": [
"timestamp",
"from_tz",
"to_tz"
],
"title": "ConvertTimeInput"
},
"ElapsedTimeInput": {
"properties": {
"start": {
"type": "string",
"title": "Start",
"description": "Start timestamp in ISO 8601 format"
},
"end": {
"type": "string",
"title": "End",
"description": "End timestamp in ISO 8601 format"
},
"units": {
"type": "string",
"enum": [
"seconds",
"minutes",
"hours",
"days"
],
"title": "Units",
"description": "Unit for elapsed time",
"default": "seconds"
}
},
"type": "object",
"required": [
"start",
"end"
],
"title": "ElapsedTimeInput"
},
"FormatTimeInput": {
"properties": {
"format": {
"type": "string",
"title": "Format",
"description": "Python strftime format string",
"default": "%Y-%m-%d %H:%M:%S"
},
"timezone": {
"type": "string",
"title": "Timezone",
"description": "IANA timezone name (e.g., UTC, America/New_York)",
"default": "UTC"
}
},
"type": "object",
"title": "FormatTimeInput"
},
"HTTPValidationError": {
"properties": {
"detail": {
"items": {
"$ref": "#/components/schemas/ValidationError"
},
"type": "array",
"title": "Detail"
}
},
"type": "object",
"title": "HTTPValidationError"
},
"ParseTimestampInput": {
"properties": {
"timestamp": {
"type": "string",
"title": "Timestamp",
"description": "Flexible input timestamp string (e.g., 2024-06-01 12:00 PM)"
},
"timezone": {
"type": "string",
"title": "Timezone",
"description": "Assumed timezone if none is specified in input",
"default": "UTC"
}
},
"type": "object",
"required": [
"timestamp"
],
"title": "ParseTimestampInput"
},
"ValidationError": {
"properties": {
"loc": {
"items": {
"anyOf": [
{
"type": "string"
},
{
"type": "integer"
}
]
},
"type": "array",
"title": "Location"
},
"msg": {
"type": "string",
"title": "Message"
},
"type": {
"type": "string",
"title": "Error Type"
}
},
"type": "object",
"required": [
"loc",
"msg",
"type"
],
"title": "ValidationError"
}
}
}
},
"info": {
"title": "Secure Time Utilities API",
"description": "Provides secure UTC/local time retrieval, formatting, timezone conversion, and comparison.",
"version": "1.0.0"
},
"specs": [
{
"type": "function",
"name": "get_current_utc_get_current_utc_time_get",
"description": "Returns the current time in UTC in ISO format.",
"parameters": {
"type": "object",
"properties": {},
"required": []
}
},
{
"type": "function",
"name": "get_current_local_get_current_local_time_get",
"description": "Returns the current time in local timezone in ISO format.",
"parameters": {
"type": "object",
"properties": {},
"required": []
}
},
{
"type": "function",
"name": "format_current_time_format_time_post",
"description": "Return the current time formatted for a specific timezone and format.",
"parameters": {
"type": "object",
"properties": {
"format": {
"type": "string",
"description": "Python strftime format string"
},
"timezone": {
"type": "string",
"description": "IANA timezone name (e.g., UTC, America/New_York)"
}
},
"required": []
}
},
{
"type": "function",
"name": "convert_time_convert_time_post",
"description": "Convert a timestamp from one timezone to another.",
"parameters": {
"type": "object",
"properties": {
"timestamp": {
"type": "string",
"description": "ISO 8601 formatted time string (e.g., 2024-01-01T12:00:00Z)"
},
"from_tz": {
"type": "string",
"description": "Original IANA time zone of input (e.e.g. UTC or Europe/Berlin)"
},
"to_tz": {
"type": "string",
"description": "Target IANA time zone to convert to"
}
},
"required": [
"timestamp",
"from_tz",
"to_tz"
]
}
},
{
"type": "function",
"name": "elapsed_time_elapsed_time_post",
"description": "Calculate the difference between two timestamps in chosen units.",
"parameters": {
"type": "object",
"properties": {
"start": {
"type": "string",
"description": "Start timestamp in ISO 8601 format"
},
"end": {
"type": "string",
"description": "End timestamp in ISO 8601 format"
},
"units": {
"type": "string",
"description": "Unit for elapsed time"
}
},
"required": [
"start",
"end"
]
}
},
{
"type": "function",
"name": "parse_timestamp_parse_timestamp_post",
"description": "Parse human-friendly input timestamp and return standardized UTC ISO time.",
"parameters": {
"type": "object",
"properties": {
"timestamp": {
"type": "string",
"description": "Flexible input timestamp string (e.g., 2024-06-01 12:00 PM)"
},
"timezone": {
"type": "string",
"description": "Assumed timezone if none is specified in input"
}
},
"required": [
"timestamp"
]
}
},
{
"type": "function",
"name": "list_time_zones_list_time_zones_get",
"description": "Return a list of all valid IANA time zones.",
"parameters": {
"type": "object",
"properties": {},
"required": []
}
}
]
}
],
"features": {
"image_generation": false,
"code_interpreter": false,
"web_search": false,
"memory": false
},
"variables": {
"{{USER_NAME}}": "acaluis",
"{{USER_LOCATION}}": "Unknown",
"{{CURRENT_DATETIME}}": "2025-08-19 18:06:37",
"{{CURRENT_DATE}}": "2025-08-19",
"{{CURRENT_TIME}}": "18:06:37",
"{{CURRENT_WEEKDAY}}": "Tuesday",
"{{CURRENT_TIMEZONE}}": "Etc/GMT-8",
"{{USER_LANGUAGE}}": "zh-CN"
},
"model_item": {
"id": "deepseek-r1:1.5b",
"name": "deepseek-r1:1.5b",
"object": "model",
"created": 1755597385,
"owned_by": "ollama",
"ollama": {
"name": "deepseek-r1:1.5b",
"model": "deepseek-r1:1.5b",
"modified_at": "2025-08-17T04:50:08.766430912Z",
"size": 1117322768,
"digest": "e0979632db5a88d1a53884cb2a941772d10ff5d055aabaa6801c4e36f3a6c2d7",
"details": {
"parent_model": "",
"format": "gguf",
"family": "qwen2",
"families": [
"qwen2"
],
"parameter_size": "1.8B",
"quantization_level": "Q4_K_M"
},
"connection_type": "local",
"urls": [
0
]
},
"connection_type": "local",
"tags": [],
"actions": [],
"filters": []
},
"session_id": "R-JB6cdCyrSZ-GRcAAJc",
"chat_id": "f9ad2990-5ad1-44fc-b3ea-c5cfee936588",
"id": "d85123d0-276b-4796-afd0-f203a8606ecf",
"background_tasks": {
"title_generation": true,
"tags_generation": true,
"follow_up_generation": true
}
}
? ? ? ?3)代碼分析
? ? ? ? 在chat_completion方法中,在metadata中設置{function_calling:native},一般情況下不設置。
@app.post("/api/chat/completions")
async def chat_completion(
request: Request,
form_data: dict,
user=Depends(get_verified_user),
):? ?try:
if not model_item.get("direct", False): #使用ollama作為后臺時,走該分支
model_id = form_data.get("model", None)
if model_id not in request.app.state.MODELS:
raise Exception("Model not found")? ? ? ? ? ? model = request.app.state.MODELS[model_id]
? ? ? ? ? ? #如果使用ollama中的標準模型model_info為空
model_info = Models.get_model_by_id(model_id)? ? ? ? ? ? # Check if user has access to the model
if not BYPASS_MODEL_ACCESS_CONTROL and user.role == "user":
try:
check_model_access(user, model)
except Exception as e:
raise e
else:
model = model_item
model_info = None? ? ? ? ? ? request.state.direct = True
request.state.model = model? ? ? ? metadata = {
"user_id": user.id,
……
**( #一般情況,請求中的params為空,并且model_info也為空,所以走else分支
{"function_calling": "native"}
if form_data.get("params", {}).get("function_calling") == "native"
or (
model_info
and model_info.params.model_dump().get("function_calling")
== "native"
)
else {}#非native
),
}? ? ? ? ……
? ? ? ? 在process_chat_payload處理function_calling,相關代碼如下:
?async def process_chat_payload(request, form_data, user, metadata, model):
……? ? tools_dict = {}
? ? if tool_ids: #當前僅配置了一個Tool,故tool_ids為空
tools_dict = get_tools(
request,
tool_ids,
user,
{
**extra_params,
"__model__": models[task_model_id],
"__messages__": form_data["messages"],
"__files__": metadata.get("files", []),
},
)? ? if tool_servers:
for tool_server in tool_servers:
tool_specs = tool_server.pop("specs", [])?? ? ? ? ? ? for tool in tool_specs:
tools_dict[tool["name"]] = {
"spec": tool,
"direct": True,
"server": tool_server,
}? ? if tools_dict:?
? ? ? ? #一般情況,前面chat_completion方法中并未設置function_calling:native,所以走else
if metadata.get("function_calling") == "native":
# If the function calling is native, then call the tools function calling handler
metadata["tools"] = tools_dict
form_data["tools"] = [
{"type": "function", "function": tool.get("spec", {})}
for tool in tools_dict.values()
]
else:#走本分支,調用大模型獲取function_calling結果
try:
form_data, flags = await chat_completion_tools_handler(
request, form_data, extra_params, user, models, tools_dict
)
sources.extend(flags.get("sources", []))? ? ? ? ? ? except Exception as e:
log.exception(e)? ? ? ??
? ? # 僅處理知識庫上下文列表,與調用工具獲取的列表無關,后繼代碼省略
? ? if len(sources) > 0:
? ? ? ? context_string = ""
? ? ? ? citation_idx_map = {}
? ? ? ? for source in sources:
? ? ? ? ? ? is_tool_result = source.get("tool_result", False)
? ? ? ? ? ??if "document" in source and not is_tool_result:
? ? ? ? ? ? ? ? ……
? ? ?#如果沒有查詢過向量庫則context_string為空
? ? ?context_string = context_string.strip()
prompt = get_last_user_message(form_data["messages"])? ? ? ? if prompt is None:
? ? ? ? ? ? raise Exception("No user message found")
? ? ? ? if context_string == "":#如果未查詢向量庫或未查詢到,則輸出日志
? ? ? ? ? ? if request.app.state.config.RELEVANCE_THRESHOLD == 0:
? ? ? ? ? ? ? ? log.debug(
? ? ? ? ? ? ? ? ? ? f"With a 0 relevancy threshold for RAG, the context cannot be empty"
? ? ? ? ? ? ? ? )
? ? ? ? else:#如果有上下文查詢結果,則需要用系統所帶的RAG模版組裝請求消息。不再詳解
? ? ? ? ? ? # Workaround for Ollama 2.0+ system prompt issue
? ? ? ? ? ? # TODO: replace with add_or_update_system_message
? ? ? ? ? ? if model.get("owned_by") == "ollama":
? ? ? ? ? ? ? ? form_data["messages"] = prepend_to_first_user_message_content(
? ? ? ? ? ? ? ? ? ? rag_template(
? ? ? ? ? ? ? ? ? ? ? ? request.app.state.config.RAG_TEMPLATE, context_string, prompt
? ? ? ? ? ? ? ? ? ? ),
? ? ? ? ? ? ? ? ? ? form_data["messages"],
? ? ? ? ? ? ? ? )
? ? ? ? ? ? else:
? ? ? ? ? ? ? ? form_data["messages"] = add_or_update_system_message(
? ? ? ? ? ? ? ? ? ? rag_template(
? ? ? ? ? ? ? ? ? ? ? ? request.app.state.config.RAG_TEMPLATE, context_string, prompt
? ? ? ? ? ? ? ? ? ? ),
? ? ? ? ? ? ? ? ? ? form_data["messages"],
? ? ? ? ? ? ? ? )
? ? ?……
? ? ? ? 以下重點分析chat_completion_tools_handler方法。
async def chat_completion_tools_handler(
request: Request, body: dict, extra_params: dict, user: UserModel, models, tools
) -> tuple[dict, dict]:
async def get_content_from_response(response) -> Optional[str]:
content = None
if hasattr(response, "body_iterator"):
async for chunk in response.body_iterator:
data = json.loads(chunk.decode("utf-8"))
content = data["choices"][0]["message"]["content"]? ? ? ? ? ? # Cleanup any remaining background tasks if necessary
if response.background is not None:
await response.background()
else:
content = response["choices"][0]["message"]["content"]
return content? ?
? ? '''
? ? get_tools_function_calling_payload方法負責組裝發送的ollama的function_calling請求,示例如begin-end之間內容。
---------------------------------begin--------------------------------------------------------------------------------
{
"model": "qwen:0.5b",
"messages": [
{
"role": "system",
"content": "Available Tools: [{\"type\": \"function\", \"name\": \"get_current_utc_get_current_utc_time_get\", \"description\": \"Returns the current time in UTC in ISO format.\", \"parameters\": {\"type\": \"object\", \"properties\": {}, \"required\": []}}, {\"type\": \"function\", \"name\": \"get_current_local_get_current_local_time_get\", \"description\": \"Returns the current time in local timezone in ISO format.\", \"parameters\": {\"type\": \"object\", \"properties\": {}, \"required\": []}}, {\"type\": \"function\", \"name\": \"format_current_time_format_time_post\", \"description\": \"Return the current time formatted for a specific timezone and format.\", \"parameters\": {\"type\": \"object\", \"properties\": {\"format\": {\"type\": \"string\", \"description\": \"Python strftime format string\"}, \"timezone\": {\"type\": \"string\", \"description\": \"IANA timezone name (e.g., UTC, America/New_York)\"}}, \"required\": []}}, {\"type\": \"function\", \"name\": \"convert_time_convert_time_post\", \"description\": \"Convert a timestamp from one timezone to another.\", \"parameters\": {\"type\": \"object\", \"properties\": {\"timestamp\": {\"type\": \"string\", \"description\": \"ISO 8601 formatted time string (e.g., 2024-01-01T12:00:00Z)\"}, \"from_tz\": {\"type\": \"string\", \"description\": \"Original IANA time zone of input (e.g. UTC or Europe/Berlin)\"}, \"to_tz\": {\"type\": \"string\", \"description\": \"Target IANA time zone to convert to\"}}, \"required\": [\"timestamp\", \"from_tz\", \"to_tz\"]}}, {\"type\": \"function\", \"name\": \"elapsed_time_elapsed_time_post\", \"description\": \"Calculate the difference between two timestamps in chosen units.\", \"parameters\": {\"type\": \"object\", \"properties\": {\"start\": {\"type\": \"string\", \"description\": \"Start timestamp in ISO 8601 format\"}, \"end\": {\"type\": \"string\", \"description\": \"End timestamp in ISO 8601 format\"}, \"units\": {\"type\": \"string\", \"description\": \"Unit for elapsed time\"}}, \"required\": [\"start\", \"end\"]}}, {\"type\": \"function\", \"name\": \"parse_timestamp_parse_timestamp_post\", \"description\": \"Parse human-friendly input timestamp and return standardized UTC ISO time.\", \"parameters\": {\"type\": \"object\", \"properties\": {\"timestamp\": {\"type\": \"string\", \"description\": \"Flexible input timestamp string (e.g., 2024-06-01 12:00 PM)\"}, \"timezone\": {\"type\": \"string\", \"description\": \"Assumed timezone if none is specified in input\"}}, \"required\": [\"timestamp\"]}}, {\"type\": \"function\", \"name\": \"list_time_zones_list_time_zones_get\", \"description\": \"Return a list of all valid IANA time zones.\", \"parameters\": {\"type\": \"object\", \"properties\": {}, \"required\": []}}]\n\nYour task is to choose and return the correct tool(s) from the list of available tools based on the query. Follow these guidelines:\n\n- Return only the JSON object, without any additional text or explanation.\n\n- If no tools match the query, return an empty array: \n ? {\n ? ? \"tool_calls\": []\n ? }\n\n- If one or more tools match the query, construct a JSON response containing a \"tool_calls\" array with objects that include:\n ? - \"name\": The tool's name.\n ? - \"parameters\": A dictionary of required parameters and their corresponding values.\n\nThe format for the JSON response is strictly:\n{\n ?\"tool_calls\": [\n ? ?{\"name\": \"toolName1\", \"parameters\": {\"key1\": \"value1\"}},\n ? ?{\"name\": \"toolName2\", \"parameters\": {\"key2\": \"value2\"}}\n ?]\n}"
},
{
"role": "user",
"content": "Query: History:\nUSER: \"\"\"\u8bf7\u544a\u8bc9\u6211\u5f53\u524d\u5927\u962a\u7684\u65f6\u95f4\"\"\"\nQuery: \u8bf7\u544a\u8bc9\u6211\u5f53\u524d\u5927\u962a\u7684\u65f6\u95f4"
}
],
"stream": false? "metadata": {"ftask"f:"function_calling"}
}----------------------------------------------------end---------------------------------------------------------------
? ? '''
? ? def get_tools_function_calling_payload(messages, task_model_id, content):
? ? ? ? #從請求表單中提取用戶提問
user_message = get_last_user_message(messages)
history = "\n".join(
f"{message['role'].upper()}: \"\"\"{message['content']}\"\"\""
for message in messages[::-1][:4] #請求表單中messages列表倒序排列后取前4個
)? ? ? ? #先在history 前增加History:,再拼接Query:用戶問題
? ? ? ? prompt = f"History:\n{history}\nQuery: {user_message}"
? ? ? ? return {
"model": task_model_id,
"messages": [
{"role": "system", "content": content},
{"role": "user", "content": f"Query: {prompt}"},
],
"stream": False,
"metadata": {"task": str(TASKS.FUNCTION_CALLING)},
}? ? event_caller = extra_params["__event_call__"]
metadata = extra_params["__metadata__"]? ? #確定執行function_calling任務的模型,實際為用戶聊天時選擇的模型? ?
? ? task_model_id = get_task_model_id(
body["model"],
request.app.state.config.TASK_MODEL,
request.app.state.config.TASK_MODEL_EXTERNAL,
models,
)? ? skip_files = False
sources = []? ? specs = [tool["spec"] for tool in tools.values()]
? ? '''
? ? ? ? specs數據如下:
? ? ? ? [
? ? ? ? ? ? {
"type": "function",
"name": "get_current_utc_get_current_utc_time_get",
"description": "Returns the current time in UTC in ISO format.",
"parameters": {
"type": "object",
"properties": {},
"required": []
}
},? ? ? ? ? ? ……
? ? ? ?]
? ? '''
? ? tools_specs = json.dumps(specs)
? ? if request.app.state.config.TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE != "":
template = request.app.state.config.TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE
else: #未配置工具函數模板時,使用缺省的模板
template = DEFAULT_TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE? ? #用tool_spces內容替換模板中的{TOOL}
? ? tools_function_calling_prompt = tools_function_calling_generation_template(
template, tools_specs
)? ? #組織發送到ollama的請求,具體見上面的函數定義部分
payload = get_tools_function_calling_payload(
body["messages"], task_model_id, tools_function_calling_prompt
)? ? try:
? ? ? ? #調用大模型獲取需要調用的工具信息
response = await generate_chat_completion(request, form_data=payload, user=user)
log.debug(f"{response=}")
content = await get_content_from_response(response)
log.debug(f"{content=}")? ? ? ? '''?
? ? ? ? ? ? 以下是一個無參函數示例時cotent的示例內容
? ? ? ? ? ? {
"tool_calls": [
{
"name": "get_current_local",
"parameters": {}
}
]
}? ? ? ? '''
? ? ? ??
? ? ? ? if not content:
return body, {}? ? ? ? try:
content = content[content.find("{") : content.rfind("}") + 1]
if not content:
raise Exception("No JSON object found in the response")? ? ? ? ? ? result = json.loads(content)
? ? ? ? ? ? #該方法根據function_calling調用結果進行后繼的調用處理,需要重點分析
? ? ? ? ? ? async def tool_call_handler(tool_call):
nonlocal skip_files? ? ? ? ? ? ? ? log.debug(f"{tool_call=}")
? ? ? ? ? ? ? ? '''
? ? ? ? ? ? ? ? ? ? ?獲取函數名和函數參數。
? ? ? ? ? ? ? ? ? ? ?防錯處理:如果大模型返回的函數名字,不在本請求所提供的工具列表中,則
? ? ? ? ? ? ? ? ? ? 返回請求表單+{}
? ? ? ? ? ? ? ?'''
? ? ? ? ? ? ? ? tool_function_name = tool_call.get("name", None)
if tool_function_name not in tools:
return body, {}? ? ? ? ? ? ? ? tool_function_params = tool_call.get("parameters", {})
? ? ? ? ? ? ? ? try:
tool = tools[tool_function_name]? ? ? ? ? ? ? ? ? ? spec = tool.get("spec", {})
allowed_params = (#工具定義時允許的參數列表
spec.get("parameters", {}).get("properties", {}).keys()
)
tool_function_params = {#實際的參數必須在工具允許的參數列表中,否則丟棄
k: v
for k, v in tool_function_params.items()
if k in allowed_params
}? ? ? ? ? ? ? ? ? ? if tool.get("direct", False): #如果是外部服務函數,則本分支
? ? ? ? ? ? ? ? ? ? ? ? '''
? ? ? ? ? ? ? ? ? ? ? ? ? ?通過websocket發送請求到前端,前端走API調用,并返回結果。
? ? ? ? ? ? ? ? ? ? ? ? ? ?結果為列表,比如:
? ? ? ? ? ? ? ? ? ? ? ? ? [
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? {"local_time":"2025-08-20T12:09:16.773972"}
? ? ? ? ? ? ? ? ? ? ? ? ? ]
? ? ? ? ? ? ? ? ? ? ? ?'''
tool_result = await event_caller(
{
"type": "execute:tool",
"data": {
"id": str(uuid4()),
"name": tool_function_name,
"params": tool_function_params,
"server": tool.get("server", {}),
"session_id": metadata.get("session_id", None),
},
}
)
else: #如果是本地代碼中的函數,則直接調用函數
tool_function = tool["callable"]
tool_result = await tool_function(**tool_function_params)? ? ? ? ? ? ? ? except Exception as e:
tool_result = str(e)? ? ? ? ? ? ? ? '''
? ? ? ? ? ? ? ? ? ?以下代碼針對function_calling涉及引用文件時的處理,此時列表中的元素為
? ? ? ? ? ? ? ? ? ?data:開頭的字符串,支架到tool_result_files列表中,并從源列表刪除
? ? ? ? ? ? ? ? '''
? ? ? ? ? ? ? ? tool_result_files = []
if isinstance(tool_result, list):
for item in tool_result:
# check if string
if isinstance(item, str) and item.startswith("data:"):
tool_result_files.append(item)
tool_result.remove(item)? ? ? ? ? ? ? ? if isinstance(tool_result, dict) or isinstance(tool_result, list):#轉換為JSON串
tool_result = json.dumps(tool_result, indent=2)? ? ? ? ? ? ? ? if isinstance(tool_result, str):#因前面以把tool_result轉換為字符串,進入本分支
tool = tools[tool_function_name]
tool_id = tool.get("tool_id", "")? ? ? ? ? ? ? ? ? ? tool_name = (
f"{tool_id}/{tool_function_name}"
if tool_id
else f"{tool_function_name}"
)
'''? ? ? ? ? ? ? ? ? ? ? ? ? 把類似如下數據追加到sources列表中:
? ? ? ? ? ? ? ? ? ? ? ? {
"source":{? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?"name": "TOOL:get_current_local_get_current_local_time_get"
? ? ? ? ? ? ? ? ? ? ? ? ? ? },
"document": [
{
"local_time": "2025-08-20T11:54:16.180931"
}
],
"metadata": [
{
"source": "TOOL:get_current_local_get_current_local_time_get",
"parameters": {}
}
],? ? ? ? ? ? ? ? ? ? ? ? ? ? "tool_result": True
}? ? ? ? ? ? ? ? ? ? ? '''
sources.append(
{
"source": {
"name": (f"TOOL:{tool_name}"),
},
"document": [tool_result],
"metadata": [
{
"source": (f"TOOL:{tool_name}"),
"parameters": tool_function_params,
}
],
}
)
'''? ? ? ? ? ? ? ? ? ? ? ? ? ? 把function_calling相關結果拼接后追加到用戶請求表單的messages中,比
? ? ? ? ? ? ? ? ? ? ? ? ? ? 如一個對話中拼接后的messages:
? ? ? ? ? ? ? ? ? ? ? ? ? [
{
"role": "user",
"content": "請告訴我當前大阪的時間"
},
{
"role": "assistant",
"content": "\n根據工具返回的示例數據,當前大阪的本地時間是 **2025年8月20日 11:54:16**。請注意,此時間是示例數據,實際當前時間可能不同。若需真實時間,請結合實時數據更新。"
},
{
"role": "user",
"content": "請告訴我當前的時間\n\nTool? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?`get_current_local_get_current_local_time_get` Output: {\n? \"local_time\": \"2025-08-20T11:59:16.404818\"\n}"
}
]? ? ? ? ? ? ? ? ? ? ? ? '''
body["messages"] = add_or_update_user_message(
f"\nTool `{tool_name}` Output: {tool_result}",
body["messages"],
)? ? ? ? ? ? ? ? ? ? if (
tools[tool_function_name]
.get("metadata", {})
.get("file_handler", False)
):
skip_files = True? ? ? ? ? ? '''
? ? ? ? ? ? ? ? 如果function_calling返回的tool_calls列表不為空,則迭代調用tool_call_handler,
? ? ? ? ? ? ? ? 否則直接調用tool_call_handler
? ? ? ? ? ? '''
if result.get("tool_calls"):
for tool_call in result.get("tool_calls"):
await tool_call_handler(tool_call)
else:
await tool_call_handler(result)? ? ? ? except Exception as e:
log.debug(f"Error: {e}")
content = None
except Exception as e:
log.debug(f"Error: {e}")
content = None? ? log.debug(f"tool_contexts: {sources}")
? ? if skip_files and "files" in body.get("metadata", {}):
del body["metadata"]["files"]? ? return body, {"sources": sources}