当前位置: 首页 > news >正文

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.dtcms.com/a/341634.html

相关文章:

  • 基于单片机水质检测系统/污水监测系统/水情监测
  • ansible中roles角色是什么意思?
  • 详解flink table api基础(三)
  • 【网络】使用 DNAT 进行负载均衡时,若未配置配套的 SNAT,回包失败
  • 猫头虎开源AI分享|基于大模型和RAG的一款智能text2sql问答系统:SQLBot(SQL-RAG-QABot),可以帮你用自然语言查询数据库
  • Three.js 初级教程大全
  • 分享|财务大数据实验室建设方案
  • 机器学习(Machine Learning, ML)
  • Web网站的运行原理2
  • Ubuntu实现程序开机自动运行
  • AI每日需求进度分析总结(附实战操作)
  • 云原生环境下的ITSM新趋势:从传统运维到智能化服务管理
  • 政务网站与新媒体自查情况的报告怎么写?
  • 【ssh】ssh免密登录配置【docker】
  • STM32_0001 KEILMDK V5.36 编译一个STM32F103C8T6说core_cm3.h文件找不到以及编译器版本不匹配的解决办法
  • 25_基于深度学习的行人检测识别系统(yolo11、yolov8、yolov5+UI界面+Python项目源码+模型+标注好的数据集)
  • 详解ThreadLocal<HttpServletRequest> requestThreadLocal
  • Kernel Study
  • 关联规则挖掘1:Apriori算法
  • Deepresearch Agents:下一代自动研究智能体的架构革命与产业实践
  • CAMEL-Task1-CAMEL环境配置及你的第一个Agent
  • postgreSQL卸载踩坑
  • Kolors Virtual Try-On:快手可图推出的AI虚拟换衣项目
  • JAVA中向量数据库(Milvus)怎么配合大模型使用
  • 简笔成画:让AI绘画变得简单而有趣
  • pyecharts可视化图表仪表盘_Gauge:从入门到精通
  • 【Linux】重生之从零开始学习运维之LVS
  • UUID(通用唯一标识符)详解和实践
  • 今日行情明日机会——20250820
  • K8S集群-基于Ingress资源实现域名访问