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}

相关推荐
Ethan.Yuan17 小时前
【深度长文】Anthropic发布Prompt Engineering全新指南
大模型·llm·prompt·提示工程
zhayujie19 小时前
RAG优化实战 - LinkAI智能体平台的知识库升级之路
ai·大模型·agent·知识库·rag
造梦师阿鹏20 小时前
004.从 API 裸调到 LangChain
经验分享·ai·大模型·ai技术·大模型应用开发
AIGC安琪1 天前
Transformer中的编码器和解码器是什么?
人工智能·深度学习·ai·语言模型·大模型·transformer·ai大模型
bug_undefine1 天前
UTMatrix VS VideoLingo 到底哪个好?
ai·大模型·视频翻译·videolingo·utmatrix
J_bean2 天前
Spring AI Alibaba 项目接入兼容 OpenAI API 的大模型
人工智能·spring·大模型·openai·spring ai·ai alibaba
青衫客362 天前
用 Python 实现一个“小型 ReAct 智能体”:思维链 + 工具调用 + 环境交互
python·大模型·llm·react
zhangbaolin2 天前
open webui源码分析3—一次对话
大模型·open webui
胡耀超2 天前
从哲学(业务)视角看待数据挖掘:从认知到实践的螺旋上升
人工智能·python·数据挖掘·大模型·特征工程·crisp-dm螺旋认知·批判性思维