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

open webui源码分析13-模型管理

        在open webui中使用的模型来源很多,可以直连第三方大模型,可以连接ollama使用本地化模型,可以使用本地定制的模型(比如挂接了知识库的模型),可以是本地管道,还可以是流水线,各种模型特征不一,数据结构也有差距,代码中经常要根据模型中的属性执行不同的逻辑,属实让人头疼,本文对open webui中的模型管理进行透彻的分析。

     1.持久化存储分析

      1.1外部公用大模型

       1.1.1获取可用模型列表

        在open webui增加外部公用大模型时,在输入大模型的URL和API_KEY之后,先调用第三方大模型的modles接口获取可用大模型(以deepseek为例就是https://api.deepseek.com/models),返回数据如下:

{
    "object": "list",
    "data": [
        {
            "id": "deepseek-chat",
            "object": "model",
            "owned_by": "deepseek"
        },
        {
            "id": "deepseek-reasoner",
            "object": "model",
            "owned_by": "deepseek"
        }
    ]
}

        1.1.2更新open webui系统内大模型数据

        更新大模型数据入口地址为:http://{ip:port}/api/v1/users/user/settings/update,请求数据如下:

{
  "ui": {
    "directConnections": {
      "OPENAI_API_BASE_URLS": [
        "https://api.deepseek.com"
      ],
      "OPENAI_API_KEYS": [
        "sk-46afcc3ac8f341e680344a6942209532"
      ],
      "OPENAI_API_CONFIGS": {
        "0": {
          "enable": true,
          "tags": [],
          "prefix_id": "",
          "model_ids": [],
          "connection_type": "external"
        }
      }
    }

}

        使用第三方大模型数据更新本地相关数据入口方法为update_user_settings_by_session_user,代码如下:

@router.post("/user/settings/update", response_model=UserSettings)
async def update_user_settings_by_session_user(
    request: Request, form_data: UserSettings, user=Depends(get_verified_user)
):
    updated_user_settings = form_data.model_dump()
    if (#权限检查
        user.role != "admin"
        and "toolServers" in updated_user_settings.get("ui").keys()
        and not has_permission(
            user.id,
            "features.direct_tool_servers",
            request.app.state.config.USER_PERMISSIONS,
        )
    ):
        # If the user is not an admin and does not have permission to use tool servers, remove the key
        updated_user_settings["ui"].pop("toolServers", None)

    #用整个更新请求表单数据更新user表的settings字段

    user = Users.update_user_settings_by_id(user.id, updated_user_settings)
    if user:
        return user.settings
    else:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail=ERROR_MESSAGES.USER_NOT_FOUND,

      1.1.3结论

        公有第三方大模型数据存储在 user表的settings字段中。

     1.2.内部定制大模型

        内部定制模型是指基于ollama内的基础大模型进行特性定制的模型,比如追加知识库,在open webui源码分析9-知识库-CSDN博客中已经讲过,这类大模型信息保存在model表中,具体结构如下图:

     1.3管道

        管道作为一种大模型,其信息存储在function表中,具体结构如下图所示:

     1.4.ollama内大模型

        在ollama运行的大模型,其数据并不在open webui后端持久化存储。在open webui运行过程中会请求获取数据。如何获取,后面再分析。

     1.5流水线

        流水线运行在Pipelines服务中,其数据也不在open webui内持久化存储。在open webui运行过程中会请求获取数据。如何获取,后面再分析。

     2.大模型数据加载分析

     2.1整体流程

       大模型数据加载流程如下图所示:

     2.2 入口方法

        open webui启动过程中并不加载模型数据,前端启动后会从后台请求模型数据。入口为http://{ip:port}/api/models,对应方法为main.py的get_models方法,下面对该方法的代码进行详细分析。

 本方法流程如下:

1) 调用get_all_models获取所有的模型,包括ollama模型,openai模型(比如pipeline)和管道函数

2)遍历模型列表

    2.1)剔除过滤器类型的pipeline

    2.2)为模型设置标签

3)对所有模型按照优先级重排序

4)剔除用户无权访问的模型

@app.get("/api/models")
async def get_models(
    request: Request, refresh: bool = False, user=Depends(get_verified_user)
):
    def get_filtered_models(models, user):
        filtered_models = []
        for model in models:
            if model.get("arena"):
                if has_access(
                    user.id,
                    type="read",
                    access_control=model.get("info", {})
                    .get("meta", {})
                    .get("access_control", {}),
                ):
                    filtered_models.append(model)
                continue

            model_info = Models.get_model_by_id(model["id"])
            if model_info:
                if user.id == model_info.user_id or has_access(
                    user.id, type="read", access_control=model_info.access_control
                ):
                    filtered_models.append(model)

        return filtered_models

    all_models = await get_all_models(request, refresh=refresh, user=user)

    models = []
    for model in all_models:
        # 如果模型类型为流水线,并且流水线的类型为filter,则剔除不做处理
        if "pipeline" in model and model["pipeline"].get("type", None) == "filter":
            continue

        try:
            model_tags = [#从模型的info.meta.tags获取模型标签
                tag.get("name")
                for tag in model.get("info", {}).get("meta", {}).get("tags", [])
            ]

            #从模型tags获取模型标签
            tags = [tag.get("name") for tag in model.get("tags", [])]

            tags = list(set(model_tags + tags))#计算两个标签的并集
            model["tags"] = [{"name": tag} for tag in tags] #设置模型tags为并集的结果
        except Exception as e:
            log.debug(f"Error processing model tags: {e}")
            model["tags"] = []
            pass

        models.append(model)

    '''

        以下对模型模型列表按照优先级排序,优先级保存在

        request.app.state.config.MODEL_ORDER_LIST

   '''

    model_order_list = request.app.state.config.MODEL_ORDER_LIST
    if model_order_list:
        model_order_dict = {model_id: i for i, model_id in enumerate(model_order_list)}
        # Sort models by order list priority, with fallback for those not in the list
        models.sort(
            key=lambda x: (model_order_dict.get(x["id"], float("inf")), x["name"])
        )

    # 剔除用户无权访问的模型,仅返回用户有权限使用的所有模型
    if user.role == "user" and not BYPASS_MODEL_ACCESS_CONTROL:
        models = get_filtered_models(models, user)

    log.debug(
        f"/api/models returned filtered models accessible to the user: {json.dumps([model['id'] for model in models])}"
    )
    return {"data": models}
 

        下面对以上get_models方法和核心方法get_all_models进行分析,该方法位于utils目录的 models.py文件中,具体如下:

本方法流程如下:

1)首次调用时,调用get_all_base_models方法,并设置全局变量 request.app.state.BASE_MODELS,否则直接使用基础模型

2)把Arena模型增加到模型列表中

3)把定制模型增加到模型列表中

4)遍历模型列表,为模型设置actions和filters

5)把所有的模型数据保存在全局变量request.app.state.MODELS中

6)返回可用模型列表

async def get_all_models(request, refresh: bool = False, user: UserModel = None):
    if (
        request.app.state.MODELS
        and request.app.state.BASE_MODELS
        and (request.app.state.config.ENABLE_BASE_MODELS_CACHE and not refresh)
    ):
        base_models = request.app.state.BASE_MODELS
    else:
        base_models = await get_all_base_models(request, user=user)
        request.app.state.BASE_MODELS = base_models

    # 把基础模型拷贝到models
    models = [model.copy() for model in base_models]

    # 如果没有可用模型直接返回
    if len(models) == 0:
        return []

    # 以下为Arena模型处理,暂不分析
    if request.app.state.config.ENABLE_EVALUATION_ARENA_MODELS:
        arena_models = []
        if len(request.app.state.config.EVALUATION_ARENA_MODELS) > 0:
            arena_models = [
                {
                    "id": model["id"],
                    "name": model["name"],
                    "info": {
                        "meta": model["meta"],
                    },
                    "object": "model",
                    "created": int(time.time()),
                    "owned_by": "arena",
                    "arena": True,
                }
                for model in request.app.state.config.EVALUATION_ARENA_MODELS
            ]
        else:
            # Add default arena model
            arena_models = [
                {
                    "id": DEFAULT_ARENA_MODEL["id"],
                    "name": DEFAULT_ARENA_MODEL["name"],
                    "info": {
                        "meta": DEFAULT_ARENA_MODEL["meta"],
                    },
                    "object": "model",
                    "created": int(time.time()),
                    "owned_by": "arena",
                    "arena": True,
                }
            ]
        models = models + arena_models

    global_action_ids = [#从function表查询全局action类型的function
        function.id for function in Functions.get_global_action_functions()
    ]
    enabled_action_ids = [#保留状态为激活的action类型的function
        function.id
        for function in Functions.get_functions_by_type("action", active_only=True)
    ]

    global_filter_ids = [#从function查询全局filter类型的function
        function.id for function in Functions.get_global_filter_functions()
    ]
    enabled_filter_ids = [#保留状态为激活的过滤器function
        function.id
        for function in Functions.get_functions_by_type("filter", active_only=True)
    ]

    #以下为定制模型处理,包括挂接知识库的模型

    custom_models = Models.get_all_models()#从model表查询所有定制模型
    for custom_model in custom_models:

        #如果定制模型的基础模型id为空
        if custom_model.base_model_id is None:
            for model in models:

                '''

                    如果基础模型id与定制模型id相等,或者基础模型是ollama模型并且二者id匹配,

                    如果定制模型是激活状态,则用定制模型的名字、action_ids和 filter_ids设置基                     础模型,否则从基础模型中剔除

               '''
                if custom_model.id == model["id"] or (
                    model.get("owned_by") == "ollama"
                    and custom_model.id
                    == model["id"].split(":")[
                        0
                    ]  # Ollama may return model ids in different formats (e.g., 'llama3' vs. 'llama3:7b')
                ):
                    if custom_model.is_active:
                        model["name"] = custom_model.name
                        model["info"] = custom_model.model_dump()

                        # Set action_ids and filter_ids
                        action_ids = []
                        filter_ids = []

                        if "info" in model and "meta" in model["info"]:
                            action_ids.extend(
                                model["info"]["meta"].get("actionIds", [])
                            )
                            filter_ids.extend(
                                model["info"]["meta"].get("filterIds", [])
                            )

                        model["action_ids"] = action_ids
                        model["filter_ids"] = filter_ids
                    else: #从models中剔除未激活的模型
                        models.remove(model)

        elif custom_model.is_active and (
            custom_model.id not in [model["id"] for model in models]
        ):#如果定制模型为激活状态,并且该模型的id在基础模型中找不到
            owned_by = "openai"
            pipe = None

            action_ids = []
            filter_ids = []

            for model in models:#遍历模型,找到与定制模型 base_model_id匹配的模型
                if (
                    custom_model.base_model_id == model["id"]
                    or custom_model.base_model_id == model["id"].split(":")[0]
                ):
                    owned_by = model.get("owned_by", "unknown owner")
                    if "pipe" in model:
                        pipe = model["pipe"]
                    break

            #从定制模型的meta中提取actionIds和filterIds,并分别追加到action_ids和 filter_ids

            if custom_model.meta:
                meta = custom_model.meta.model_dump()

                if "actionIds" in meta:
                    action_ids.extend(meta["actionIds"])

                if "filterIds" in meta:
                    filter_ids.extend(meta["filterIds"])

            models.append(
                {#把定制模型增加到models列表中
                    "id": f"{custom_model.id}",
                    "name": custom_model.name,
                    "object": "model",
                    "created": custom_model.created_at,
                    "owned_by": owned_by,
                    "info": custom_model.model_dump(),
                    "preset": True,
                    **({"pipe": pipe} if pipe is not None else {}),
                    "action_ids": action_ids,
                    "filter_ids": filter_ids,
                }
            )

    # Process action_ids to get the actions
    def get_action_items_from_module(function, module):
        actions = []
        if hasattr(module, "actions"):
            actions = module.actions
            return [
                {
                    "id": f"{function.id}.{action['id']}",
                    "name": action.get("name", f"{function.name} ({action['id']})"),
                    "description": function.meta.description,
                    "icon": action.get(
                        "icon_url",
                        function.meta.manifest.get("icon_url", None)
                        or getattr(module, "icon_url", None)
                        or getattr(module, "icon", None),
                    ),
                }
                for action in actions
            ]
        else:
            return [
                {
                    "id": function.id,
                    "name": function.name,
                    "description": function.meta.description,
                    "icon": function.meta.manifest.get("icon_url", None)
                    or getattr(module, "icon_url", None)
                    or getattr(module, "icon", None),
                }
            ]

    # Process filter_ids to get the filters
    def get_filter_items_from_module(function, module):
        return [
            {
                "id": function.id,
                "name": function.name,
                "description": function.meta.description,
                "icon": function.meta.manifest.get("icon_url", None)
                or getattr(module, "icon_url", None)
                or getattr(module, "icon", None),
            }
        ]

    def get_function_module_by_id(function_id):
        function_module, _, _ = get_function_module_from_cache(request, function_id)
        return function_module

    for model in models:#遍历模型列表,为模型设置actions和filters
        action_ids = [#从模型自带的action与全局action中抽取出被启用的
            action_id
            for action_id in list(set(model.pop("action_ids", []) + global_action_ids))
            if action_id in enabled_action_ids
        ]
        filter_ids = [#从模型自带的filter与全局action中抽取出被启用的
            filter_id
            for filter_id in list(set(model.pop("filter_ids", []) + global_filter_ids))
            if filter_id in enabled_filter_ids
        ]

        model["actions"] = []

        #把action_ids中的所有action的模块信息{action_function, function_module}增加到模型的actions列表中
        for action_id in action_ids:
            action_function = Functions.get_function_by_id(action_id)
            if action_function is None:
                raise Exception(f"Action not found: {action_id}")

            function_module = get_function_module_by_id(action_id)
            model["actions"].extend(
                get_action_items_from_module(action_function, function_module)
            )

        model["filters"] = []

        #把action_ids中的所有action的模块信息{filter_function, filter_module}增加到模型的filters列表中
        for filter_id in filter_ids:
            filter_function = Functions.get_function_by_id(filter_id)
            if filter_function is None:
                raise Exception(f"Filter not found: {filter_id}")

            function_module = get_function_module_by_id(filter_id)

            if getattr(function_module, "toggle", None):
                model["filters"].extend(
                    get_filter_items_from_module(filter_function, function_module)
                )

    log.debug(f"get_all_models() returned {len(models)} models")

    #把所有的模型数据赋值给全局变量request.app.state.MODELS

    request.app.state.MODELS = {model["id"]: model for model in models}
    return models #返回所有的模型


def check_model_access(user, model):
    if model.get("arena"):
        if not has_access(
            user.id,
            type="read",
            access_control=model.get("info", {})
            .get("meta", {})
            .get("access_control", {}),
        ):
            raise Exception("Model not found")
    else:
        model_info = Models.get_model_by_id(model.get("id"))
        if not model_info:
            raise Exception("Model not found")
        elif not (
            user.id == model_info.user_id
            or has_access(
                user.id, type="read", access_control=model_info.access_control
            )
        ):
            raise Exception("Model not found")
 

        get_all_base_models方法用户获取所有的基础模型,包括ollama内的模型,pipeline和管道。

该方法三个异步任务,分别从Pipelines服务和ollama服务中获取模型列表,然后再从本地获取管道类型的函数列表,汇总后返回。

async def get_all_base_models(request: Request, user: UserModel = None):
   

     openai_task = (#从Pipelines服务获取模型
        fetch_openai_models(request, user)
        if request.app.state.config.ENABLE_OPENAI_API
        else asyncio.sleep(0, result=[])
    )
    ollama_task = (#从ollama获取模型
        fetch_ollama_models(request, user)
        if request.app.state.config.ENABLE_OLLAMA_API
        else asyncio.sleep(0, result=[])
    )
    function_task = get_function_models(request) #获取本地管道类型函数列表

    #异步执行三个任务

    openai_models, ollama_models, function_models = await asyncio.gather(
        openai_task, ollama_task, function_task
    )

    return function_models + openai_models + ollama_models

       
  2.3从Pipelines拉取数据

        fetch_openai_models负责从Pipelines服务获取模型,调用openai模块的get_all_models,具体代码如下:

async def fetch_openai_models(request: Request, user: UserModel = None):
    openai_response = await openai.get_all_models(request, user=user)
    return openai_response["data"]

     下面对openai模块get_all_models进行分析,具体代码如下:

@cached(ttl=MODELS_CACHE_TTL)
async def get_all_models(request: Request, user: UserModel) -> dict[str, list]:
    log.info("get_all_models()")

    '''

        在open webui源码分析12-Pipeline-CSDN博客中,增加连接时设置了

        ENABLE_OPENAI_API

    '''

    if not request.app.state.config.ENABLE_OPENAI_API:
        return {"data": []}

    responses = await get_all_models_responses(request, user=user)

    def extract_data(response):
        if response and "data" in response:
            return response["data"]
        if isinstance(response, list):
            return response
        return None

    #该方法是内嵌方法,把从所有open ai服务获取的模型合并成一个列表

    def merge_models_lists(model_lists):
        log.debug(f"merge_models_lists {model_lists}")
        merged_list = []

        for idx, models in enumerate(model_lists):
            if models is not None and "error" not in models:

                merged_list.extend(
                    [
                        {#存在同名kv时,后面的覆盖前面的
                            **model,
                            "name": model.get("name", model["id"]),
                            "owned_by": "openai",
                            "openai": model,#新增加的
                            "connection_type": model.get("connection_type", "external"),
                            "urlIdx": idx,
                        }
                        for model in models
                        if (model.get("id") or model.get("name"))
                        and (
                            "api.openai.com"
                            not in request.app.state.config.OPENAI_API_BASE_URLS[idx]
                            or not any(
                                name in model["id"]
                                for name in [
                                    "babbage",
                                    "dall-e",
                                    "davinci",
                                    "embedding",
                                    "tts",
                                    "whisper",
                                ]
                            )
                        )
                    ]
                )

        return merged_list

    #调用merge_models_lists对所有来源的模型进行合并

    models = {"data": merge_models_lists(map(extract_data, responses))}
    log.debug(f"models: {models}")

    '''

        此时models数据如下:

       {
            "data": [
                {
                    "id": "example_pipeline_scaffold",
                    "name": "Pipeline Example",
                    "object": "model",
                    "created": 1756518781,
                    "owned_by": "openai",
                    "pipeline": {
                    "type": "pipe",
                    "valves": false
                },
                "connection_type": "external",
                "openai": {
                    "id": "example_pipeline_scaffold",
                    "name": "Pipeline Example",
                    "object": "model",
                    "created": 1756518781,
                    "owned_by": "openai",
                    "pipeline": {
                            "type": "pipe",
                            "valves": false
                        },
                        "connection_type": "external"
                    },
                   "urlIdx": 0
                }
            ]
       }

       重要:针对本类型的模型connection_type为external,owned_by是openai

    '''

    #把models设置到request.app.state.OPENAI_MODELS中

    request.app.state.OPENAI_MODELS = {model["id"]: model for model in models["data"]}
    return models
 

        下面对关键方法get_all_modles_responses方法进行分析,具体代码如下:

async def get_all_models_responses(request: Request, user: UserModel) -> list:
    if not request.app.state.config.ENABLE_OPENAI_API:
        return []

    #处理API_URL和API_KEY长度不匹配参见open webui源码分析12-Pipeline-CSDN博客
    num_urls = len(request.app.state.config.OPENAI_API_BASE_URLS)
    num_keys = len(request.app.state.config.OPENAI_API_KEYS)

    if num_keys != num_urls:
        # if there are more keys than urls, remove the extra keys
        if num_keys > num_urls:
            new_keys = request.app.state.config.OPENAI_API_KEYS[:num_urls]
            request.app.state.config.OPENAI_API_KEYS = new_keys
        # if there are more urls than keys, add empty keys
        else:
            request.app.state.config.OPENAI_API_KEYS += [""] * (num_urls - num_keys)

   

    request_tasks = []

    '''

        遍历所有的连接,请求模型列表。

        结合以下数据看代码更容易,这些数据存储在 request.app.state.config中。

        ________________________________________________________________

        {
             "OPENAI_API_BASE_URLS": [
                     "http://localhost:9099" #运行的Pipelines服务地址
              ],
              "OPENAI_API_KEYS": [
                     "0p3n-w3bu!"             #Pipelines服务访问密钥
              ],
              "OPENAI_API_CONFIGS": { #配置信息
                  "0": {
                        "enable": true,
                        "tags": [],
                        "prefix_id": "",
                        "model_ids": [], #指定一个连接中模型的子集,为空是相当于全部模型
                        "connection_type": "external"
                 }
             }
        } 

        ————————————————————————————————————

    '''

  
    for idx, url in enumerate(request.app.state.config.OPENAI_API_BASE_URLS):
        if (str(idx) not in request.app.state.config.OPENAI_API_CONFIGS) and (
            url not in request.app.state.config.OPENAI_API_CONFIGS  # Legacy support
        ):#不须关注
            request_tasks.append(
                send_get_request(
                    f"{url}/models",
                    request.app.state.config.OPENAI_API_KEYS[idx],
                    user=user,
                )
            )
        else:

            #python语法糖,如果在OPENAI_API_CONFIGS没有idx,则使用'url',否则为空
            api_config = request.app.state.config.OPENAI_API_CONFIGS.get(
                str(idx),
                request.app.state.config.OPENAI_API_CONFIGS.get(
                    url, {}
                ),  # Legacy support
            )

            enable = api_config.get("enable", True)
            model_ids = api_config.get("model_ids", [])

            if enable: #针对被启用的配置

                '''

                    如果model_ids为空创建一个异步任务,向指定的服务,比如Pipelines请求模型

                    列表。如果model_ids为空,则直接调用服务的对应接口。否则直接组织

                    model_list                    

                '''
                if len(model_ids) == 0:
                    request_tasks.append(
                        send_get_request(
                            f"{url}/models",
                            request.app.state.config.OPENAI_API_KEYS[idx],
                            user=user,
                        )
                    )
                else:
                    model_list = {
                        "object": "list",
                        "data": [
                            {
                                "id": model_id,
                                "name": model_id,
                                "owned_by": "openai",
                                "openai": {"id": model_id},
                                "urlIdx": idx,
                            }
                            for model_id in model_ids
                        ],
                    }

                    request_tasks.append(#生成一个future,直接返回model_list
                        asyncio.ensure_future(asyncio.sleep(0, model_list))
                    )
            else:#如果没有被启用,则创建一个什么都不做的future,增加到 request_tasks中
                request_tasks.append(asyncio.ensure_future(asyncio.sleep(0, None)))

    responses = await asyncio.gather(*request_tasks)#异步执行所有任务

    '''

        调用Pipelines返回的数据如下,对照理解下面代码。

        {
              "data": [
                  {
                        "id": "example_pipeline_scaffold",
                         "name": "Pipeline Example",
                         "object": "model",
                         "created": 1756459482,
                         "owned_by": "openai",
                         "pipeline": {
                              "type": "pipe",
                              "valves": false
                        }
                }
            ],
            "object": "list",
            "pipelines": true
        }

    '''

    for idx, response in enumerate(responses):
        if response:
            url = request.app.state.config.OPENAI_API_BASE_URLS[idx]
            api_config = request.app.state.config.OPENAI_API_CONFIGS.get(
                str(idx),
                request.app.state.config.OPENAI_API_CONFIGS.get(
                    url, {}
                ),  # Legacy support
            )

            connection_type = api_config.get("connection_type", "external")
            prefix_id = api_config.get("prefix_id", None)
            tags = api_config.get("tags", [])

            for model in (
                response if isinstance(response, list) else response.get("data", [])
            ):
                if prefix_id:#如果配置中有prefix_id则在模型id前拼接prefix_id
                    model["id"] = f"{prefix_id}.{model['id']}"

                if tags: #如果配置中的tags不为空,则设置modes的标签
                    model["tags"] = tags

                if connection_type:#设置模型的连接类型
                    model["connection_type"] = connection_type

    log.debug(f"get_all_models:responses() {responses}")

    '''

        返回的responses数据如下:

        {
            "data": [
                {
                    "id": "example_pipeline_scaffold",
                    "name": "Pipeline Example",
                    "object": "model",
                    "created": 1756518781,
                    "owned_by": "openai",
                    "pipeline": {
                        "type": "pipe",
                        "valves": false
                    },
                    "connection_type": "external"
                }
            ],
            "object": "list",
            "pipelines": true
        }   

    '''
    return responses
 

      完成Pipelines数据拉取后,一方面在request.app.state.OPENAI_MODELS设置了所有的模型(形式为{model_id:model,……}),另一方面返回所有数据给上层。

 2.4从ollama拉取数据

        fetch_ollama_models从ollama请求可用模型列表,具体代码如下:

该方法很简单,主要逻辑看ollama.get_all_models

async def fetch_ollama_models(request: Request, user: UserModel = None):
    raw_ollama_models = await ollama.get_all_models(request, user=user)
    return [
        {
            "id": model["model"],
            "name": model["name"],
            "object": "model",
            "created": int(time.time()),
            "owned_by": "ollama",
            "ollama": model,
            "connection_type": model.get("connection_type", "local"),
            "tags": model.get("tags", []),
        }
        for model in raw_ollama_models["models"]
    ]

        下面对ollama模块的get_all_modles方法进行分析:

@cached(ttl=MODELS_CACHE_TTL)
async def get_all_models(request: Request, user: UserModel = None):
    log.info("get_all_models()")
    if request.app.state.config.ENABLE_OLLAMA_API:
        request_tasks = []

    '''

        遍历所有的连接,请求模型列表。

        结合以下数据看代码更容易,这些数据存储在 request.app.state.config中。

        ________________________________________________________________

        {
             "OLLAMA_BASE_URLS": [
                     "http://localhost:11434" #运行的ollama服务地址
              ],
              "OLLAMA_API_CONFIGS": { #配置信息
                  "0": {
                        "enable": true,
                        "tags": [],
                        "prefix_id": "",
                        "model_ids": [], #指定一个ollama模型的子集,为空是相当于全部模型
                        "connection_type": "external"
                 }
             }
        } 

        ————————————————————————————————————

    '''      
        for idx, url in enumerate(request.app.state.config.OLLAMA_BASE_URLS):
            if (str(idx) not in request.app.state.config.OLLAMA_API_CONFIGS) and (
                url not in request.app.state.config.OLLAMA_API_CONFIGS  # Legacy support
            ):#关注这个分支。创建一个异步任务从ollama获取可用模型
                request_tasks.append(send_get_request(f"{url}/api/tags", user=user))
            else:
                api_config = request.app.state.config.OLLAMA_API_CONFIGS.get(
                    str(idx),
                    request.app.state.config.OLLAMA_API_CONFIGS.get(
                        url, {}
                    ),  # Legacy support
                )

                enable = api_config.get("enable", True)
                key = api_config.get("key", None)

                if enable:
                    request_tasks.append(
                        send_get_request(f"{url}/api/tags", key, user=user)
                    )
                else:
                    request_tasks.append(asyncio.ensure_future(asyncio.sleep(0, None)))

        responses = await asyncio.gather(*request_tasks)#运行异步任务并收集应答

        for idx, response in enumerate(responses):#对照下面调用ollama返回的数据
            if response:
                url = request.app.state.config.OLLAMA_BASE_URLS[idx]
                api_config = request.app.state.config.OLLAMA_API_CONFIGS.get(
                    str(idx),
                    request.app.state.config.OLLAMA_API_CONFIGS.get(
                        url, {}
                    ),  # Legacy support
                )

                connection_type = api_config.get("connection_type", "local")

                prefix_id = api_config.get("prefix_id", None)
                tags = api_config.get("tags", [])
                model_ids = api_config.get("model_ids", [])

                #如果在ollama配置中设置了模型列表,则剔除modle_ids列表之外的模型

                if len(model_ids) != 0 and "models" in response:
                    response["models"] = list(
                        filter(
                            lambda model: model["model"] in model_ids,
                            response["models"],
                        )
                    )

                for model in response.get("models", []):
                    if prefix_id:#设置前缀
                        model["model"] = f"{prefix_id}.{model['model']}"

                    if tags:#设置标签
                        model["tags"] = tags

                    if connection_type:#设置连接类型为local
                        model["connection_type"] = connection_type

       #调用merge_ollama_models_lists把多个ollama服务器获取的模型列表合并

        models = {
            "models": merge_ollama_models_lists(
                map(
                    lambda response: response.get("models", []) if response else None,
                    responses,
                )
            )
        }

        try:

            '''

                查询ollama已经加载的模型列表,此处调用的get_ollama_loaded_models方法,逻

               辑与前面的请求ollama服务器返回模型列表相同,只不过请求地址不同,请求

               ollama服务器获取模型地址为{ollama_url}/api/tags,获取已加载模型地址为

               {ollama_url}/api/ps,所以对get_ollama_loaded_models代码不再做分析

            '''
            loaded_models = await get_ollama_loaded_models(request, user=user)
            expires_map = { #把已加载的模型的超时时间写入expires_map中
                m["name"]: m["expires_at"]
                for m in loaded_models["models"]
                if "expires_at" in m
            }

            '''

                针对前面从所有ollama服务获取的所有模型,根据如果已经加载并且有超时时间,

                则设置模型的超时时间

            '''

            for m in models["models"]:
                if m["name"] in expires_map:
                    # Parse ISO8601 datetime with offset, get unix timestamp as int
                    dt = datetime.fromisoformat(expires_map[m["name"]])
                    m["expires_at"] = int(dt.timestamp())
        except Exception as e:
            log.debug(f"Failed to get loaded models: {e}")

    else: #如果未启用ollama,设置models为[]
        models = {"models": []}

    #把models设置到request.app.state..OLLAMA_MODELS中,形式为{model_id:model}

    request.app.state.OLLAMA_MODELS = {
        model["model"]: model for model in models["models"]
    }
    return models
 

        open webui调用{ollama_url}/api/tags接口,返回的应答数据如下。

{
  "models": [
    {
      "name": "qwen3:1.7b",
      "model": "qwen3:1.7b",
      "modified_at": "2025-08-20T03:50:50.085066919Z",
      "size": 1359293444,
      "digest": "8f68893c685c3ddff2aa3fffce2aa60a30bb2da65ca488b61fff134a4d1730e7",
      "details": {
        "parent_model": "",
        "format": "gguf",
        "family": "qwen3",
        "families": [
          "qwen3"
        ],
        "parameter_size": "2.0B",
        "quantization_level": "Q4_K_M"
      }
    },
    {
      "name": "qwen:0.5b",
      "model": "qwen:0.5b",
      "modified_at": "2025-08-17T05:40:18.859598053Z",
      "size": 394998579,
      "digest": "b5dc5e784f2a3ee1582373093acf69a2f4e2ac1710b253a001712b86a61f88bb",
      "details": {
        "parent_model": "",
        "format": "gguf",
        "family": "qwen2",
        "families": [
          "qwen2"
        ],
        "parameter_size": "620M",
        "quantization_level": "Q4_0"
      }
    }
  ]
}
    

     2.5获取本地管道

      get_function_models方法用于获取所有的管道,具体代码如下:

async def get_function_models(request):

    #从数据库function表查询被激活的管道
    pipes = Functions.get_functions_by_type("pipe", active_only=True)
    pipe_models = []

    for pipe in pipes:
        function_module = get_function_module_by_id(request, pipe.id)

        if hasattr(function_module, "pipes"):#多层管道处理
            sub_pipes = []

            #得到所有的子管道
            try:
                if callable(function_module.pipes):
                    if asyncio.iscoroutinefunction(function_module.pipes):
                        sub_pipes = await function_module.pipes()
                    else:
                        sub_pipes = function_module.pipes()
                else:
                    sub_pipes = function_module.pipes
            except Exception as e:
                log.exception(e)
                sub_pipes = []

            log.debug(
                f"get_function_models: function '{pipe.id}' is a manifold of {sub_pipes}"
            )

            for p in sub_pipes:#针对每个子管道,设置为与模型一致的数据结构
                sub_pipe_id = f'{pipe.id}.{p["id"]}'
                sub_pipe_name = p["name"]

                if hasattr(function_module, "name"):
                    sub_pipe_name = f"{function_module.name}{sub_pipe_name}"

                pipe_flag = {"type": pipe.type}

                pipe_models.append(
                    {
                        "id": sub_pipe_id,
                        "name": sub_pipe_name,
                        "object": "model",
                        "created": pipe.created_at,
                        "owned_by": "openai",
                        "pipe": pipe_flag,
                    }
                )
        else:#单层管道
            pipe_flag = {"type": "pipe"}

            log.debug(
                f"get_function_models: function '{pipe.id}' is a single pipe {{ 'id': {pipe.id}, 'name': {pipe.name} }}"
            )

            pipe_models.append(
                {
                    "id": pipe.id,
                    "name": pipe.name,
                    "object": "model",
                    "created": pipe.created_at,
                    "owned_by": "openai",
                    "pipe": pipe_flag,
                }
            )

    return pipe_models
 

      2.6模型数据

        经过以上处理后,返回前端数据如下:

{
    "data": [
        {
            "id": "example_pipeline_scaffold",
            "name": "Pipeline Example",
            "object": "model",
            "created": 1756555949,
            "owned_by": "openai",
            "pipeline": {
                "type": "pipe",
                "valves": false
            },
            "connection_type": "external",
            "openai": {
                "id": "example_pipeline_scaffold",
                "name": "Pipeline Example",
                "object": "model",
                "created": 1756555949,
                "owned_by": "openai",
                "pipeline": {
                    "type": "pipe",
                    "valves": false
                },
                "connection_type": "external"
            },
            "urlIdx": 0,
            "actions": [],
            "filters": [],
            "tags": []
        },
        {
            "id": "qwen3:1.7b",
            "name": "qwen3:1.7b",
            "object": "model",
            "created": 1756555949,
            "owned_by": "ollama",
            "ollama": {
                "name": "qwen3:1.7b",
                "model": "qwen3:1.7b",
                "modified_at": "2025-08-20T03:50:50.085066919Z",
                "size": 1359293444,
                "digest": "8f68893c685c3ddff2aa3fffce2aa60a30bb2da65ca488b61fff134a4d1730e7",
                "details": {
                    "parent_model": "",
                    "format": "gguf",
                    "family": "qwen3",
                    "families": [
                        "qwen3"
                    ],
                    "parameter_size": "2.0B",
                    "quantization_level": "Q4_K_M"
                },
                "connection_type": "local",
                "urls": [
                    0
                ]
            },
            "connection_type": "local",
            "tags": [],
            "actions": [],
            "filters": []
        },
        {
            "id": "qwen:0.5b",
            "name": "qwen:0.5b",
            "object": "model",
            "created": 1756555949,
            "owned_by": "ollama",
            "ollama": {
                "name": "qwen:0.5b",
                "model": "qwen:0.5b",
                "modified_at": "2025-08-17T05:40:18.859598053Z",
                "size": 394998579,
                "digest": "b5dc5e784f2a3ee1582373093acf69a2f4e2ac1710b253a001712b86a61f88bb",
                "details": {
                    "parent_model": "",
                    "format": "gguf",
                    "family": "qwen2",
                    "families": [
                        "qwen2"
                    ],
                    "parameter_size": "620M",
                    "quantization_level": "Q4_0"
                },
                "connection_type": "local",
                "urls": [
                    0
                ]
            },
            "connection_type": "local",
            "tags": [],
            "actions": [],
            "filters": []
        },
        {
            "id": "deepseek-r1:1.5b",
            "name": "deepseek-r1:1.5b",
            "object": "model",
            "created": 1756555949,
            "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": []
        },
        {
            "id": "arena-model",
            "name": "Arena Model",
            "info": {
                "meta": {
                    "profile_image_url": "/favicon.png",
                    "description": "Submit your questions to anonymous AI chatbots and vote on the best response.",
                    "model_ids": null
                }
            },
            "object": "model",
            "created": 1756555949,
            "owned_by": "arena",
            "arena": true,
            "actions": [],
            "filters": [],
            "tags": []
        },
        {
            "id": "政府工作报告",
            "name": "政府工作报告",
            "object": "model",
            "created": 1756275416,
            "owned_by": "ollama",
            "info": {
                "id": "政府工作报告",
                "user_id": "e6d4a214-8982-40ad-9bbc-77ee14534d58",
                "base_model_id": "deepseek-r1:1.5b",
                "name": "政府工作报告",
                "params": {},
                "meta": {
                    "profile_image_url": "/static/favicon.png",
                    "description": "挂接2023年北京市政府工作报告",
                    "capabilities": {
                        "vision": false,
                        "file_upload": false,
                        "web_search": false,
                        "image_generation": false,
                        "code_interpreter": false,
                        "citations": false
                    },
                    "suggestion_prompts": null,
                    "tags": [],
                    "knowledge": [
                        {
                            "id": "161dd6ea-2e14-4b60-b8a2-48993ec9e4f2",
                            "user_id": "e6d4a214-8982-40ad-9bbc-77ee14534d58",
                            "name": "政府工作报告",
                            "description": "共享政府工作报告内容",
                            "data": {
                                "file_ids": [
                                    "ca856fca-3eef-44d7-882d-24b312a72c48"
                                ]
                            },
                            "meta": null,
                            "access_control": null,
                            "created_at": 1756275107,
                            "updated_at": 1756275184,
                            "user": {
                                "id": "e6d4a214-8982-40ad-9bbc-77ee14534d58",
                                "name": "acaluis",
                                "email": "acaluis@sina.com",
                                "role": "admin",
                                "profile_image_url": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAYAAABw4pVUAAAAAXNSR0IArs4c6QAABItJREFUeF7tnF2IVHUYxp9zzpyZ2Q9ZtdZscfdmEzOtZfEDtmhtIxXRQEQIuxFUikRFKOou0RtRgrqIBNmCQLab6IM1yjVaK8QLBVGkUlwFFSoldHXdna+dE3OOLnvOfszsTLXPP565G+Z/Zp7z/M5z3vO+/2Wte52zPehF44AlIDQsfCECwsVDQMh4CIiAsDlApkc1REDIHCCTo4QICJkDZHKUEAEhc4BMjhIiIGQOkMlRQgSEzAEyOUqIgJA5QCZHCREQMgfI5CghAkLmAJkcJURAyBwgk6OECAiZA2RylBABIXOATI4SIiBkDpDJUUIEhMwBMjlGJyS58ghiTWtGLPWy95E5vRfZXz8ms7l0OcYCceYsRbKjE1ZtY+hsc9d7kOrZVLoDZCuNBRJf9i7ii7cDthuy1Bu6hdRPOzB843syq0uTYyyQqrVH4cxt88/SG7oJK14HOAnAyyN78VOkT75VmgNkq4wEEmveiETbAViJmb6duWvfwXm0BVb14/77/O3fMNS9Gl52gMzu4nKMBJJ47j24CzYDlg0Mp5E59z6cuc/CaWgPEmNwcTcOiF3XjOTKLth1TwTmD/6O1I9vwKlfgnjr28Ftq5AaQ4u7cUDcp7YhvmwPrFi1b/zwH6cw9M06RJ+6TC3uxgFJrvoMscZVwc04n0XmwkfInN7nvw31JYYWd6OAOA3PI7ni0Ejx9gauI9W7DcM3z/hA3IVbg/S4NQGv/stIHX8V+f6+4tWUZIVRQKK9R7ROROuLX/DPHkTm3AckdheXYRSQ6vW9sB95JijmucFgTPJLZ+gsQ09gBhZ3Y4BEe4+JbkfRdaYVd2OAJF84jFjzhsJ/AynkA9lLXUj/vGvce0DVum/hPLY8+Myw4m4EkDG9R/oO0qfeQa7v83GBRGuNScXdCCDxlt2hpi//13kMftUxYYUcMwk2qLgbAWSy3mMiKqFjRjWQxZ9zpncFPRBn3ktItn8Iq6q+Iqe89G2kT76J3NWvK/qef/tgeiCJ5fvgLnodsGOVeWFIcacGYrm1qFrbPdJ7VEbEjM6dGog7fxPibfthuTOCJ9jBP/1Ru5fpL4mNXdMA9+mdI/smJnTu1EDCvUd5I/XR3X2B4sPpcElEp2ERLRC7vhXJFz+BXdsU2FLmo2u0J/GK9DDTwCD0k7RAxvQeA9eQ+mEL8rfOTsmz6IS40OXn+r5A6sRrU/qe/2oxLZDRf8RQMCN35Ut/1F7OK9qT5O9eRarnFcqxPCWQaO9R6R55dJex3NtfORfDVI+hBBIdoVc6ixqzT0Jc3OmA+L3Hy8dgz3rywcU1+WS31CswCpm1uNMBiW7D/lPGRfdJWIs7HZBSr/j/6zoBISMrIAJC5gCZHCVEQMgcIJOjhAgImQNkcpQQASFzgEyOEiIgZA6QyVFCBITMATI5SoiAkDlAJkcJERAyB8jkKCECQuYAmRwlREDIHCCTo4QICJkDZHKUEAEhc4BMjhIiIGQOkMlRQgSEzAEyOUqIgJA5QCZHCREQMgfI5CghAkLmAJkcJURAyBwgk6OEkAH5G7gElAPcU3ncAAAAAElFTkSuQmCC"
                            },
                            "files": [
                                {
                                    "id": "ca856fca-3eef-44d7-882d-24b312a72c48",
                                    "meta": {
                                        "name": "microsoft_annual_report_2022.pdf",
                                        "content_type": "application/pdf",
                                        "size": 1285495,
                                        "data": {},
                                        "collection_name": "161dd6ea-2e14-4b60-b8a2-48993ec9e4f2"
                                    },
                                    "created_at": 1756275120,
                                    "updated_at": 1756275120
                                }
                            ],
                            "type": "collection"
                        }
                    ]
                },
                "access_control": null,
                "is_active": true,
                "updated_at": 1756275416,
                "created_at": 1756275416
            },
            "preset": true,
            "actions": [],
            "filters": [],
            "tags": []
        }
    ]
}

       同时在全局变量request.app.state.MODELS、request.app.state.OPENAI_MODELS、request.app.state.OLLAMA_MODELS和request.app.state.BASE_MODELS分别存储可用模型数据、open ai模型数据、ollama数据和基础模型数据。全部数据均以dict方式存储,其中key为模型的id,v为模型数据(与应答中的模型数据相同)。

http://www.xdnf.cn/news/19399.html

相关文章:

  • 数据结构--栈(Stack) 队列(Queue)
  • Python API接口实战指南:从入门到精通
  • Linux查看有线网卡和无线网卡详解
  • 【Linux】基础I/O和文件系统
  • 初学者如何学习项目管理
  • 计算机毕设javayit商城 基于SSM框架的校园二手交易全流程管理系统设计与实现 Java+MySQL的校园二手商品交易与供需对接平台开发
  • 【嵌入式原理系列-第六篇】从Flash到RAM:MCU ld脚本全解析
  • TuringComplete游戏攻略(一、基础逻辑电路)
  • Python Facebook Logo
  • 神经网络正则化三重奏:Weight Decay, Dropout, 和LayerNorm
  • ARM 裸机开发 知识点
  • 豌豆压缩怎么用?3步避免网盘资源被和谐 网盘压缩包总被和谐?豌豆压缩实测解析 豌豆压缩避坑指南:敏感资源存储必读
  • 雷卯国产化之SE3401完全替代AOS的AO3401
  • 数字签名 digital signature
  • 年化225%,回撤9%,夏普4.32,3积分可查看参数
  • Java 常见异常系列:ClassNotFoundException 类找不到
  • Java 学习笔记(基础篇12)
  • 学习Python中Selenium模块的基本用法(10:浏览器操作)
  • 让演化可编程:XLang 与可逆计算的结构化范式
  • [ZJCTF 2019]NiZhuanSiWei
  • 第2节:项目前期准备
  • C++抽象类
  • 【Python 后端框架】总结
  • Nginx反向代理与负载均衡
  • 基于单片机指纹考勤系统/智能考勤
  • DeepSeek应用技巧-通过MCP打造数据分析助手
  • YOLOv11 训练参数全解析:一文掌握 epochs、batch、optimizer 调优技巧
  • kali下sqlmap更新失败问题
  • PB-重装系统后,重新注册ole控件,pb中窗口控件失效的问题。
  • 不用公网IP也能?cpolar实现Web-Check远程安全检测(1)