open webui源碼分析5-Tools

? ? ? ? 本文從最簡單的時間工具入手,分析Tools相關的代碼。

一、安裝工具

git clone https://github.com/open-webui/openapi-servers
cd openapi-servers

# 進入時間工具目錄
cd servers/time

pip 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}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/919655.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/919655.shtml
英文地址,請注明出處:http://en.pswp.cn/news/919655.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

windows下通過vscode遠程調試linux c/cpp程序配置

windows下通過vscode遠程調試linux c/cpp程序配置vscode插件配置linux依賴工具安裝launch.json配置vscode插件配置 CodeLLDB插件需要提前下載: linux依賴工具安裝 sudo apt update sudo apt install cmake clangdlaunch.json配置 {"version": "0…

IDEA報JDK版本問題

解決思路:1.找到配置jdk的IDEA配置位置settings和project structure2.先配置setting3.再修改項目結構

VirtualBox 安裝 Ubuntu Server 系統及 Ubuntu 初始配置

文章目錄簡介VirtualBoxUbuntu Server 簡介Ubuntu Server 下載安裝 Ubuntu Server首選項配置導入系統鏡像配置系統用戶配置內存 CPU 虛擬硬盤開始安裝 Ubuntu安裝完成登錄系統配置網絡Ubuntu 系統配置安裝常用工具安裝 SSH設置 root 密碼配置 IP 地址(推薦自動分配I…

Milvus 可觀測性最佳實踐

Milvus 介紹 Milvus 是一個開源的向量數據庫,專為處理大規模、高維度向量數據而設計,廣泛應用于人工智能、推薦系統、圖像檢索、自然語言處理等場景。它支持億級向量的高效存儲與快速檢索,內置多種相似度搜索算法(如 HNSW、IVF、…

arcgis-空間矯正工具(將下發數據A的信息放置原始數據B的原始信息并放置到成果數據C中,主要按下發數據A的范圍)

正常來說,可以直接相交獲取,但是會存在原始數據B將下發數據A進行分割,所以相交功能會導致最終成果會產生稀碎圖斑及圖斑切割,因此,經學習了解,學會此方法進行既保留原始數據B的信息,又按下發數據…

MySQL深分頁慢問題及性能優化

在數據驅動的應用中,分頁是不可或缺的功能。然而,當數據量達到百萬甚至千萬級別時,傳統基于 LIMIT OFFSET 的分頁方式會遭遇嚴重的性能瓶頸,即“深分頁”問題。本文將剖析其根源并提供主流的優化策略。問題根源:LIMIT …

漫談《數字圖像處理》之平滑

在數字圖像處理中,平滑(Smoothing) 的核心目標是降低圖像噪聲、模糊細節或簡化紋理,本質是通過 “局部鄰域運算” 對像素值進行 “平均化” 或 “規整化”,讓圖像整體更 “平緩”。形態學平滑與高斯平滑、均值平滑等其…

機器學習之數據預處理學習總結

在機器學習中,數據預處理是模型訓練前至關重要的環節,直接影響模型的性能和準確性。通過本次學習,我系統掌握了數據預處理的核心方法與工具,現將主要內容總結如下:一、缺失值處理缺失值是實際數據中常見的問題&#xf…

在完全沒有無線網絡(Wi-Fi)和移動網絡(蜂窩數據)的環境下,使用安卓平板,通過USB數據線(而不是Wi-Fi)來控制電腦(版本2)

在完全沒有無線網絡(Wi-Fi)和移動網絡(蜂窩數據)的環境下,要實現用安卓手機通過USB數據線控制電腦,核心思路是:利用USB數據線創建一個純粹的、本地的有線網絡連接。 這不僅是可行的,…

Ubuntu22.04配置網絡上網

前言 安裝Ubuntu系統后,有時會遇到無法聯網、無法使用瀏覽器的問題。然而當宿主機已連接網絡時,虛擬機通常也能聯網,需要進行一些配置,現在就以Ubuntu22.04為例。 VMware配置打開虛擬網絡編輯器 啟動VMWare點擊編輯,并…

網絡協議之TCP和UDP

寫在前面 本文來看下TCP和UDP協議。 我們接觸這兩個協議最多的應該就是在面試中了,經典題目就是“TCP和UDP有什么區別?”,而最常得到的答案就是TCP是面向連接的,而UDP是面向無連接的。 那么這里的連接到底是什么呢?難…

Qt音樂播放器項目實踐:本地持久化與邊角問題處理

本音樂播放器完整項目源碼(包含各個按鈕的圖片文件): ly/Project-Code - Gitee.com 一.本地持久化 請注意,學習此部分之前需要讀者具有一定的Mysql基礎。如果讀者能夠接受無法本地持久化,那么可以跳過這部分內容,直接去看邊角問題處理。我…

基于NB-IoT技術的寵物定位跟蹤系統設計#基于STM32\物聯網\單片機技術的寵物定位跟蹤系統

基于NB-IoT技術的寵物定位跟蹤系統設計#基于STM32\物聯網\單片機技術的寵物定位跟蹤系統在設計基于NB-IoT技術的寵物定位跟蹤系統時,首先明確了系統分為感知層、網絡層和應用層三個部分。在感知層,考慮到需要獲取寵物位置和運動狀態,選用GPS定…

【入門級-算法-3、基礎算法:遞歸法】

遞歸是一種非常重要的算法思想,它指的是函數調用自身的過程。遞歸通常包含兩個主要部分:基線條件(終止條件)和遞歸條件(調用自身的條件)。 下面通過例子來理解遞歸算法: 計算階乘 階乘的遞歸定義…

【CS創世SD NAND征文】存儲芯片在工業電表中的應用與技術演進

【CS創世SD NAND征文】存儲芯片在工業電表中的應用與技術演進1.工業電表的市場背景2.技術方案分析3.核心技術特性3.1.主控芯片:APM32F465VET63.3.存儲芯片:CSNP4GCR01-DPW3.3.1. 基本概述3.3.2. 核心特性3.3.3. 優勢特點3.3.4 四大管理算法4.存儲芯片性能…

建筑施工遮擋場景漏檢率↓76%:陌訊動態融合算法實戰解析

原創聲明 本文為原創內容,技術參數及架構解析引用自《陌訊技術白皮書》,未經授權禁止轉載。 一、行業痛點:建筑施工安全監控的 "看得見" 與 "看不準" 建筑施工場景的安全監控長期面臨雙重挑戰:一方面&…

【LeetCode題解】LeetCode 209. 長度最小的子數組

【題目鏈接】 209. 長度最小的子數組 【題目描述】 【題解】 方法一:滑動窗口 本題可以使用雙指針算法,定義兩個指針l和r分別表示子數組的開始位置和起始位置,sum數組存儲的從l到r區間內所有元素的和。初始狀態下,l和r都指向下…

2025-08-21 Python進階6——迭代器生成器與with

文章目錄1 迭代器與生成器1.1 迭代器1.1.1 基本使用1.1.2 手動迭代(帶異常處理)1.1.3 自定義迭代器1.2 生成器1.2.1 工作原理1.2.2 斐波那契數列示例1.3 推導式1.3.1 列表推導式1.3.2 字典推導式1.3.3 集合推導式1.4.4 元組推導式(生成器表達…

C++——C++重點知識點復習2(詳細復習模板,繼承)

目錄 模板 函數模板 類模板 非類型模板參數 模板的特化 函數模板特化 類模板的特化 為什么普通函數可以分離? 繼承 繼承概念 基類和派生類對象賦值轉換(切割,切片) 隱藏 派生類的默認成員函數 .復雜的菱形繼承及菱形…

python 項目編號 2025821 有關于中英文數據的收集、處理

python專欄記錄:前言 批量讀取單詞 JSON 文件 → 解析出單詞、釋義、例句、短語 → 數據清洗(去掉特殊符號) → 同步更新到 MySQL 數據庫。 內容 import json import pymysql import re import time from pymysql.converters import escape_s…