open webui源碼分析6-Function

? ? ? ? 一、Functions簡介

? ? ? ? 可以把Tools作為依賴于外部服務的插件,Functions就是內部插件,二者都是用來增強open webui的能力的。Functions是輕量的,高度可定制的,并且是用純Python編寫的,所以你可以自由地創建任何東西——從新的人工智能工作流到與你使用的任何東西的集成,比如谷歌搜索或家庭助理。

? ? ? ?在open webui中的Function包括三種類型:Pipe Function、Filter Function和Action Function。

? ? ? ? Pipe類型的Function用于自定義Agent或模型,用戶在對話中可以像普通的模型那樣選擇使用。

? ? ? ? Filter類型的Function用于對往返大模型的數據進行處理,從而可以在不中斷對話的前提下,攔截對話內容并進行修改或其他處理,比如日志。過濾器一般用于輕量級處理,包括:發送數據到監控平臺、記錄日志、修改用戶輸入、阻斷有害消息、翻譯和限流等。

? ? ? ? Action類型的Function用來對聊天界面的按鈕進行定制。這些按鈕出現在單個聊天消息下方,讓您可以方便地一鍵訪問您定義的操作。

? ? ? ? 本文僅對Action類型的Function進行解析。? ? ? ??

? ? ? ? 二、導入一個Function

? ? ? ? 1)進入open webui社區的Functions頁面,選擇一個Function,這里以Save Outputs為例

? ? ? ? 2)點擊Save Outputs,進入如下頁面

? ? ? ? 3)點擊Get,在對話框填寫你的open webui的地址

? ? ? ? 4)點擊Import to WebUI

? ? ? ? 進入open webui頁面,顯示函數代碼,核心代碼為把大模型的輸出寫入本地文件中,完整源碼如下:

"""
title: save_outputs
author: stefanpietrusky
author_url: https://downchurch.studio/
inspiration: add_to_memories_action_button @pad4651
instruction: you need to mount the container folder /app/data with a local folder when creating the container! ?--mount type=bind,source="FOLDER PATH\docker_data",target=/app/data“
icon_url: ?data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMjRweCIgdmlld0JveD0iMCAtOTYwIDk2MCA5NjAiIHdpZHRoPSIyNHB4IiBmaWxsPSIjNWY2MzY4Ij48cGF0aCBkPSJNODQwLTY4MHY0ODBxMCAzMy0yMy41IDU2LjVUNzYwLTEyMEgyMDBxLTMzIDAtNTYuNS0yMy41VDEyMC0yMDB2LTU2MHEwLTMzIDIzLjUtNTYuNVQyMDAtODQwaDQ4MGwxNjAgMTYwWm0tODAgMzRMNjQ2LTc2MEgyMDB2NTYwaDU2MHYtNDQ2Wk00ODAtMjQwcTUwIDAgODUtMzV0MzUtODVxMC01MC0zNS04NXQtODUtMzVxLTUwIDAtODUgMzV0LTM1IDg1cTAgNTAgMzUgODV0ODUgMzVaTTI0MC01NjBoMzYwdi0xNjBIMjQwdjE2MFptLTQwLTg2djQ0Ni01NjAgMTE0WiIvPjwvc3ZnPg==
version: 0.1
"""

import os
from pydantic import BaseModel, Field
from typing import Optional


class Action:
? ? class Valves(BaseModel):
? ? ? ? pass

? ? class UserValves(BaseModel):
? ? ? ? show_status: bool = Field(
? ? ? ? ? ? default=True, description="Show status of the action."
? ? ? ? )
? ? ? ? pass

? ? def __init__(self):
? ? ? ? self.valves = self.Valves()
? ? ? ? pass

? ? async def action(
? ? ? ? self,
? ? ? ? body: dict,
? ? ? ? __user__=None,
? ? ? ? __event_emitter__=None,
? ? ? ? __event_call__=None,
? ? ) -> Optional[dict]:
? ? ? ? print(f"action:{__name__}")

? ? ? ? user_valves = __user__.get("valves")
? ? ? ? if not user_valves:
? ? ? ? ? ? user_valves = self.UserValves()

? ? ? ? if __event_emitter__:
? ? ? ? ? ? last_assistant_message = body["messages"][-1]

? ? ? ? ? ? if user_valves.show_status:
? ? ? ? ? ? ? ? await __event_emitter__(
? ? ? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? ? ? "type": "status",
? ? ? ? ? ? ? ? ? ? ? ? "data": {"description": "Saving to file", "done": False},
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? )

? ? ? ? ? ? try:
? ? ? ? ? ? ? ? directory = "/app/data"
? ? ? ? ? ? ? ? if not os.path.exists(directory):
? ? ? ? ? ? ? ? ? ? os.makedirs(directory)

? ? ? ? ? ? ? ? file_path = os.path.join(directory, "saved_outputs.txt")
? ? ? ? ? ? ? ? with open(file_path, "a") as file:
? ? ? ? ? ? ? ? ? ? file.write(f"{last_assistant_message['content']}\n\n")
? ? ? ? ? ? ? ? print("Output saved to file in the container, accessible on the host.")

? ? ? ? ? ? except Exception as e:
? ? ? ? ? ? ? ? print(f"Error saving output to file: {str(e)}")
? ? ? ? ? ? ? ? if user_valves.show_status:
? ? ? ? ? ? ? ? ? ? await __event_emitter__(
? ? ? ? ? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? ? ? ? ? "type": "status",
? ? ? ? ? ? ? ? ? ? ? ? ? ? "data": {
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? "description": "Error Saving to File",
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? "done": True,
? ? ? ? ? ? ? ? ? ? ? ? ? ? },
? ? ? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? )

? ? ? ? ? ? if user_valves.show_status:
? ? ? ? ? ? ? ? await __event_emitter__(
? ? ? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? ? ? "type": "status",
? ? ? ? ? ? ? ? ? ? ? ? "data": {"description": "Output Saved", "done": True},
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? )
?

? ? ? ? 因為可能是惡意代碼,所以需要閱讀檢查代碼。檢查無誤后,可以保存,該函數便作為插件進入open webui體系中。

? ? ? ? ? 三、具體使用

? ? ? ? 函數生效后,在大模型返回對一個問題的應答后,在工具欄顯示該函數圖標。

? ? ? ? 用戶點擊該鏈接,則保存當前大模型輸出寫入到文件中。

? ? ? ? 三、源碼分析

? ? ? ? 1)數據模型

? ? ? ?Function數據保存在Function表中,表定義如下:

? ? ? ? 其中:

? ? ? ? ? ? ? ? id:函數唯一標識

? ? ? ? ? ? ? ? userid:用戶唯一標識

? ? ? ? ? ? ? ? name:函數名

? ? ? ? ? ? ? ? type:函數類型 filter|pipe|action

? ? ? ? ? ? ? ? content:方法源代碼

? ? ? ? ? ? ? ?meta:元數據

? ? ? ? ? ? ? ? valves:閾值

? ? ? ? ? ? ? ? is_active:是否被激活(激活后才可見)

? ? ? ? ? ? ? ? is_global:全局還是局部(僅某個用戶使用)

? ? ? ? 2)導入函數

? ? ? ? 從open webui社區頁面點擊 Import to WebUI時,瀏覽器啟動一個新頁面,并提交代碼格式化請求到/app/v1/utils/code/format,后端調用black模塊進行嚴格格式化處理,并把格式化后的代碼返回前端。

@router.post("/code/format")
async def format_code(form_data: CodeForm, user=Depends(get_admin_user)):
? ? try:
? ? ? ? formatted_code = black.format_str(form_data.code, mode=black.Mode())
? ? ? ? return {"code": formatted_code}
? ? except black.NothingChanged:
? ? ? ? return {"code": form_data.code}
? ? except Exception as e:
? ? ? ? raise HTTPException(status_code=400, detail=str(e))

? ? ? ? 完成格式化處理后,后端再提交創建Function請求到 /api/v1/functions/create。請求數據為:

{
? ? "id": "save_outputs",
? ? "name": "Save Outputs",
? ? "meta": {
? ? ? ? "description": "Save outputs locally on your computer.",
? ? ? ? "manifest": {
? ? ? ? ? ? "title": "save_outputs",
? ? ? ? ? ? "author": "stefanpietrusky",
? ? ? ? ? ? "author_url": "https://downchurch.studio/",
? ? ? ? ? ? "inspiration": "add_to_memories_action_button @pad4651",
? ? ? ? ? ? "instruction": "you need to mount the container folder /app/data with a local folder when creating the container! ?--mount type=bind,source=\"FOLDER PATH\\docker_data\",target=/app/data\"",
? ? ? ? ? ? "icon_url": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMjRweCIgdmlld0JveD0iMCAtOTYwIDk2MCA5NjAiIHdpZHRoPSIyNHB4IiBmaWxsPSIjNWY2MzY4Ij48cGF0aCBkPSJNODQwLTY4MHY0ODBxMCAzMy0yMy41IDU2LjVUNzYwLTEyMEgyMDBxLTMzIDAtNTYuNS0yNS41VDEyMC0yMDB2LTU2MHEwLTMzIDIzLjUtNTYuNVQyMDAtODQwaDQ4MGwxNjAgMTYwWm0tODAgMzRMNjQ2LTc2MEgyMDB2NTYwaDU2MHYtNDQ2Wk00ODAtMjQwcTUwIDAgODUtMzV0MzUtODVxMC01MC0zNS04NXQtODUtMzVxLTUwIDAtODUgMzV0LTM1IDg1cTAgNTAgMzUgODV0ODUgMzVaTTI0MC01NjBoMzYwdi0xNjBIMjQwdjE2MFptLTQwLTg2djQ0Ni01NjAgMTE0WiIvPjwvc3ZnPg==",
? ? ? ? ? ? "version": "0.1"
? ? ? ? },
? ? ? ? "type": "action",
? ? ? ? "user": {
? ? ? ? ? ? "id": "9e4f4854-71d9-429a-99b9-9338a393de9e",
? ? ? ? ? ? "username": "pietrusky",
? ? ? ? ? ? "name": "",
? ? ? ? ? ? "createdAt": 1724186428,
? ? ? ? ? ? "role": null,
? ? ? ? ? ? "verified": false
? ? ? ? },
? ? ? ? "id": "542145b0-59a0-44f2-86f1-dd2f1e64d705"
? ? },

? ? #content由注釋和源代碼組成
? ? "content": ?"\"\"\"\ntitle: save_outputs\nauthor: stefanpietrusky\nauthor_url: https://downchurch.studio/\ninspiration: add_to_memories_action_button @pad4651\ninstruction: you need to mount the container folder /app/data with a local folder when creating the container! ?--mount type=bind,source=\"FOLDER PATH\\docker_data\",target=/app/data\"\nicon_url: data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMjRweCIgdmlld0JveD0iMCAtOTYwIDk2MCA5NjAiIHdpZHRoPSIyNHB4IiBmaWxsPSIjNWY2MzY4Ij48cGF0aCBkPSJNODQwLTY4MHY0ODBxMCAzMy0yMy41IDU2LjVUNzYwLTEyMEgyMDBxLTMzIDAtNTYuNS0yNS41VDEyMC0yMDBvLTU2MHEwLTMzIDIzLjUtNTYuNVQyMDAtODQwaDQ4MGwxNjAgMTYwWm0tODAgMzRMNjQ2LTc2MEgyMDB2NTYwaDU2MHYtNDQ2Wk00ODAtMjQwcTUwIDAgODUtMzV0MzUtODVxMC01MC0zNS04NXQtODUtMzVxLTUwIDAtODUgMzV0LTM1IDg1cTAgNTAgMzUgODV0ODUgMzVaTTI0MC01NjBoMzYwdi0xNjBIMjQwdjE2MFptLTQwLTg2djQ0Ni01NjAgMTE0WiIvPjwvc3ZnPg==\nversion: 0.1\n\"\"\"\n\nimport os\nfrom pydantic import BaseModel, Field\nfrom typing import Optional\n\n\nclass Action:\n ? ?class Valves(BaseModel):\n ? ? ? ?pass\n\n ? ?class UserValves(BaseModel):\n ? ? ? ?show_status: bool = Field(\n ? ? ? ? ? ?default=True, description=\"Show status of the action.\"\n ? ? ? ?)\n ? ? ? ?pass\n\n ? ?def __init__(self):\n ? ? ? ?self.valves = self.Valves()\n ? ? ? ?pass\n\n ? ?async def action(\n ? ? ? ?self,\n ? ? ? ?body: dict,\n ? ? ? ?__user__=None,\n ? ? ? ?__event_emitter__=None,\n ? ? ? ?__event_call__=None,\n ? ?) -> Optional[dict]:\n ? ? ? ?print(f\"action:{__name__}\")\n\n ? ? ? ?user_valves = __user__.get(\"valves\")\n ? ? ? ?if not user_valves:\n ? ? ? ? ? ?user_valves = self.UserValves()\n\n ? ? ? ?if __event_emitter__:\n ? ? ? ? ? ?last_assistant_message = body[\"messages\"][-1]\n\n ? ? ? ? ? ?if user_valves.show_status:\n ? ? ? ? ? ? ? ?await __event_emitter__(\n ? ? ? ? ? ? ? ? ? ?{\n ? ? ? ? ? ? ? ? ? ? ? ?\"type\": \"status\",\n ? ? ? ? ? ? ? ? ? ? ? ?\"data\": {\"description\": \"Saving to file\", \"done\": False},\n ? ? ? ? ? ? ? ? ? ?}\n ? ? ? ? ? ? ? ?)\n\n ? ? ? ? ? ?try:\n ? ? ? ? ? ? ? ?directory = \"/app/data\"\n ? ? ? ? ? ? ? ?if not os.path.exists(directory):\n ? ? ? ? ? ? ? ? ? ?os.makedirs(directory)\n\n ? ? ? ? ? ? ? ?file_path = os.path.join(directory, \"saved_outputs.txt\")\n ? ? ? ? ? ? ? ?with open(file_path, \"a\") as file:\n ? ? ? ? ? ? ? ? ? ?file.write(f\"{last_assistant_message['content']}\\n\\n\")\n ? ? ? ? ? ? ? ?print(\"Output saved to file in the container, accessible on the host.\")\n\n ? ? ? ? ? ?except Exception as e:\n ? ? ? ? ? ? ? ?print(f\"Error saving output to file: {str(e)}\")\n ? ? ? ? ? ? ? ?if user_valves.show_status:\n ? ? ? ? ? ? ? ? ? ?await __event_emitter__(\n ? ? ? ? ? ? ? ? ? ? ? ?{\n ? ? ? ? ? ? ? ? ? ? ? ? ? ?\"type\": \"status\",\n ? ? ? ? ? ? ? ? ? ? ? ? ? ?\"data\": {\n ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?\"description\": \"Error Saving to File\",\n ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?\"done\": True,\n ? ? ? ? ? ? ? ? ? ? ? ? ? ?},\n ? ? ? ? ? ? ? ? ? ? ? ?}\n ? ? ? ? ? ? ? ? ? ?)\n\n ? ? ? ? ? ?if user_valves.show_status:\n ? ? ? ? ? ? ? ?await __event_emitter__(\n ? ? ? ? ? ? ? ? ? ?{\n ? ? ? ? ? ? ? ? ? ? ? ?\"type\": \"status\",\n ? ? ? ? ? ? ? ? ? ? ? ?\"data\": {\"description\": \"Output Saved\", \"done\": True},\n ? ? ? ? ? ? ? ? ? ?}\n ? ? ? ? ? ? ? ?)"
}

? ? ? ?對應函數源碼如下:

處理流程如下:

1)防錯處理,判斷函數名是否符合python標識符的命名規則,不符合則報錯

2)對源中import的模塊名進行替換

3)加載源碼成為可使用的模塊

4)把該函數加載到全局FUNCTIONS中,供后繼使用

5)為函數創建緩存目錄

@router.post("/create", response_model=Optional[FunctionResponse])
async def create_new_function(
? ? request: Request, form_data: FunctionForm, user=Depends(get_admin_user)
):
? ? if not form_data.id.isidentifier(): #對id進行校驗
? ? ? ? raise HTTPException(
? ? ? ? ? ? status_code=status.HTTP_400_BAD_REQUEST,
? ? ? ? ? ? detail="Only alphanumeric characters and underscores are allowed in the id",
? ? ? ? )

? ? form_data.id = form_data.id.lower()

? ? #從Function表查詢該函數是否已經入庫

? ? function = Functions.get_function_by_id(form_data.id)
? ? if function is None:?
? ? ? ? try:

? ? ? ? ? ? #用本地模塊名,替換源碼中的模塊名,比如用from open_webui.utils替換from utils?
? ? ? ? ? ? form_data.content = replace_imports(form_data.content)

? ? ? ? ? ? #把函數加載為模塊
? ? ? ? ? ? function_module, function_type, frontmatter = load_function_module_by_id(
? ? ? ? ? ? ? ? form_data.id,
? ? ? ? ? ? ? ? content=form_data.content,
? ? ? ? ? ? )
? ? ? ? ? ? form_data.meta.manifest = frontmatter

? ? ? ? ? ? #把Function實例增加到全局FUNCTIONS中

? ? ? ? ? ? FUNCTIONS = request.app.state.FUNCTIONS
? ? ? ? ? ? FUNCTIONS[form_data.id] = function_module

? ? ? ? ? ? #把函數數據插入到FUNCTION表中

? ? ? ? ? ? function = Functions.insert_new_function(user.id, function_type, form_data)

? ? ? ? ? ? #為該方法創建目錄/app/backend/data/cache/functions/{函數名}

? ? ? ? ? ? function_cache_dir = CACHE_DIR / "functions" / form_data.id
? ? ? ? ? ? function_cache_dir.mkdir(parents=True, exist_ok=True)

? ? ? ? ? ? if function:
? ? ? ? ? ? ? ? return function
? ? ? ? ? ? else:
? ? ? ? ? ? ? ? raise HTTPException(
? ? ? ? ? ? ? ? ? ? status_code=status.HTTP_400_BAD_REQUEST,
? ? ? ? ? ? ? ? ? ? detail=ERROR_MESSAGES.DEFAULT("Error creating function"),
? ? ? ? ? ? ? ? )
? ? ? ? except Exception as e:
? ? ? ? ? ? log.exception(f"Failed to create a new function: {e}")
? ? ? ? ? ? raise HTTPException(
? ? ? ? ? ? ? ? status_code=status.HTTP_400_BAD_REQUEST,
? ? ? ? ? ? ? ? detail=ERROR_MESSAGES.DEFAULT(e),
? ? ? ? ? ? )
? ? else: #如果已經入庫,則報錯
? ? ? ? raise HTTPException(
? ? ? ? ? ? status_code=status.HTTP_400_BAD_REQUEST,
? ? ? ? ? ? detail=ERROR_MESSAGES.ID_TAKEN,
? ? ? ? )
?

? ? ? ? 在該方法中的核心代碼是load_function_module_by_id,load_function_module_by_id實現代碼動態加載,重點分析一下。

def load_function_module_by_id(function_id: str, content: str | None = None):

? ? #如果參數content為None,則從數據庫查詢
? ? if content is None:
? ? ? ? function = Functions.get_function_by_id(function_id)
? ? ? ? if not function:
? ? ? ? ? ? raise Exception(f"Function not found: {function_id}")
? ? ? ? content = function.content

? ? ? ? content = replace_imports(content)#替換源碼中的導入的模塊名
? ? ? ? Functions.update_function_by_id(function_id, {"content": content})#更新數據庫content
? ? else:#從content提取元數據
? ? ? ? frontmatter = extract_frontmatter(content)

? ? ? ? #安裝依賴模塊
? ? ? ? install_frontmatter_requirements(frontmatter.get("requirements", ""))

? ?

? ? module_name = f"function_{function_id}"

? ? #創建function_{function_id}模塊,比如function_save_outputs
? ? module = types.ModuleType(module_name)

? ? #加載模塊到sys_modules
? ? sys.modules[module_name] = module

? ? # 創建臨時文件,用于存儲函數的源代碼
?? ?temp_file = tempfile.NamedTemporaryFile(delete=False)
? ? temp_file.close()
? ? try:

? ? ? ? #把源代碼寫入臨時文件
? ? ? ? with open(temp_file.name, "w", encoding="utf-8") as f:
? ? ? ? ? ? f.write(content)
? ? ? ? module.__dict__["__file__"] = temp_file.name #設置模塊的__file__為臨時文件名

? ? ? ? # 在本模塊的命名空間運行源代碼,完成模塊源碼的載入
? ? ? ? exec(content, module.__dict__)
? ? ? ? frontmatter = extract_frontmatter(content)
? ? ? ? log.info(f"Loaded module: {module.__name__}")

? ? ? ? # 根據Function類型,返回對應類的實例
? ? ? ? if hasattr(module, "Pipe"):#返回管道實例
? ? ? ? ? ? return module.Pipe(), "pipe", frontmatter
? ? ? ? elif hasattr(module, "Filter"): #返回過濾器實例
? ? ? ? ? ? return module.Filter(), "filter", frontmatter
? ? ? ? elif hasattr(module, "Action"):
? ? ? ? ? ? return module.Action(), "action", frontmatter? #返回Action實例
? ? ? ? else:
? ? ? ? ? ? raise Exception("No Function class found in the module")
? ? except Exception as e:
? ? ? ? log.error(f"Error loading module: {function_id}: {e}")
? ? ? ? # Cleanup by removing the module in case of error
? ? ? ? del sys.modules[module_name]

? ? ? ? Functions.update_function_by_id(function_id, {"is_active": False})
? ? ? ? raise e
? ? finally:
? ? ? ? os.unlink(temp_file.name)

? ? ? ? 3)執行函數

? ? ? ? 用戶在對話界面點擊按鈕執行函數時,后端入口為http://{ip:port}/api/chat/actions/{函數名},后端調用該函數執行對應的操作。對應入口函數為chat_action。

該方法和簡潔,主要是調用chat_action_handle。

@app.post("/api/chat/actions/{action_id}")
async def chat_action(
? ? request: Request, action_id: str, form_data: dict, user=Depends(get_verified_user)
):
? ? try:
? ? ? ? model_item = form_data.pop("model_item", {})

? ? ? ? if model_item.get("direct", False):
? ? ? ? ? ? request.state.direct = True
? ? ? ? ? ? request.state.model = model_item

? ? ? ? return await chat_action_handler(request, action_id, form_data, user)
? ? except Exception as e:
? ? ? ? raise HTTPException(
? ? ? ? ? ? status_code=status.HTTP_400_BAD_REQUEST,
? ? ? ? ? ? detail=str(e),
? ? ? ? )

? ? ? ??chat_action_handle實際對應?open_webui.utils.chat模塊中的chat_action方法,具體源碼如下:

async def chat_action(request: Request, action_id: str, form_data: dict, user: Any):
? ? if "." in action_id: #如果action_id是多層,則用'.'分割
? ? ? ? action_id, sub_action_id = action_id.split(".")
? ? else:
? ? ? ? sub_action_id = None

? ? action = Functions.get_function_by_id(action_id)#從數據庫查找Function是否存在
? ? if not action:
? ? ? ? raise Exception(f"Action not found: {action_id}")

? ? #以下代碼確定使用的模型

? ? if not request.app.state.MODELS:?
? ? ? ? await get_all_models(request, user=user)

? ? if getattr(request.state, "direct", False) and hasattr(request.state, "model"):
? ? ? ? models = {
? ? ? ? ? ? request.state.model["id"]: request.state.model,
? ? ? ? }
? ? else:
? ? ? ? models = request.app.state.MODELS

? ? data = form_data
? ? model_id = data["model"]

? ? if model_id not in models:
? ? ? ? raise Exception("Model not found")
? ? model = models[model_id]

? ? #通過websocket發送數據到前端

? ? __event_emitter__ = get_event_emitter(
? ? ? ? {
? ? ? ? ? ? "chat_id": data["chat_id"],
? ? ? ? ? ? "message_id": data["id"],
? ? ? ? ? ? "session_id": data["session_id"],
? ? ? ? ? ? "user_id": user.id,
? ? ? ? }
? ? )
? ? __event_call__ = get_event_call(
? ? ? ? {
? ? ? ? ? ? "chat_id": data["chat_id"],
? ? ? ? ? ? "message_id": data["id"],
? ? ? ? ? ? "session_id": data["session_id"],
? ? ? ? ? ? "user_id": user.id,
? ? ? ? }
? ? )

? ? #根據action_id獲取模塊

? ? function_module, _, _ = get_function_module_from_cache(request, action_id)

? ? #閥門處理

? ? if hasattr(function_module, "valves") and hasattr(function_module, "Valves"):
? ? ? ? valves = Functions.get_function_valves_by_id(action_id)
? ? ? ? function_module.valves = function_module.Valves(**(valves if valves else {}))

? ? if hasattr(function_module, "action"):
? ? ? ? try:
? ? ? ? ? ? action = function_module.action#從Action類中獲取action方法

? ? ? ? ? ? # 得到函數簽名
? ? ? ? ? ? sig = inspect.signature(action)
? ? ? ? ? ? params = {"body": data}

? ? ? ? ? ? # Extra parameters to be passed to the function
? ? ? ? ? ? extra_params = {
? ? ? ? ? ? ? ? "__model__": model,
? ? ? ? ? ? ? ? "__id__": sub_action_id if sub_action_id is not None else action_id,
? ? ? ? ? ? ? ? "__event_emitter__": __event_emitter__,
? ? ? ? ? ? ? ? "__event_call__": __event_call__,
? ? ? ? ? ? ? ? "__request__": request,
? ? ? ? ? ? }

? ? ? ? ? ? #把extra_params中的項中與函數簽名中的參數匹配的項加入到params中
? ? ? ? ? ? for key, value in extra_params.items():
? ? ? ? ? ? ? ? if key in sig.parameters:
? ? ? ? ? ? ? ? ? ? params[key] = value

? ? ? ? ? ? if "__user__" in sig.parameters:

? ? ? ? ? ? ? ? #如果函數簽名中有__user__,則在調用參數中增加用戶相關閥門設置
? ? ? ? ? ? ? ? __user__ = user.model_dump() if isinstance(user, UserModel) else {}

? ? ? ? ? ? ? ? try:
? ? ? ? ? ? ? ? ? ? if hasattr(function_module, "UserValves"):
? ? ? ? ? ? ? ? ? ? ? ? __user__["valves"] = function_module.UserValves(
? ? ? ? ? ? ? ? ? ? ? ? ? ? **Functions.get_user_valves_by_id_and_user_id(
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? action_id, user.id
? ? ? ? ? ? ? ? ? ? ? ? ? ? )
? ? ? ? ? ? ? ? ? ? ? ? )
? ? ? ? ? ? ? ? except Exception as e:
? ? ? ? ? ? ? ? ? ? log.exception(f"Failed to get user values: {e}")

? ? ? ? ? ? ? ? params = {**params, "__user__": __user__}

? ? ? ? ? ? if inspect.iscoroutinefunction(action): #如果action方法是協程,則await調用
? ? ? ? ? ? ? ? data = await action(**params)
? ? ? ? ? ? else: #非協程則直接調用
? ? ? ? ? ? ? ? data = action(**params)

? ? ? ? except Exception as e:
? ? ? ? ? ? return Exception(f"Error: {e}")

? ? return data
?

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

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

相關文章

C2039 “unref“:不是“osgEarth::Symbology::Style”的成員 問題分析及解決方法

在osgEarth2.10中實現多線段連續測量功能時,遇到下圖中的錯誤; 經過測試和驗證,主要問題出現在下圖圈出代碼的定義上 圖22-1 對于22-1中的兩個變量這樣定義是錯誤的。因為Style類沒有繼承自osg::Referenced,因此不能與osg::ref_ptr配合使用

GitHub 熱榜項目 - 日榜(2025-08-19)

GitHub 熱榜項目 - 日榜(2025-08-19) 生成于:2025-08-19 統計摘要 共發現熱門項目:12 個 榜單類型:日榜 本期熱點趨勢總結 本期GitHub熱榜呈現三大技術熱點:1)AI原生開發持續爆發,Archon OS、Parlant等…

ingress 配置ssl證書

模擬環境舉例&#xff1a; # 生成帶 OU 的證書配置文件 cat > csr.conf <<EOF [ req ] default_bits 2048 prompt no default_md sha256 distinguished_name dn[ dn ] C CN ST Beijing L Beijing O YourCompany, Inc. # 組織名稱 (必填) OU DevOps De…

Pandas 合并數據集:concat 和 append

文章目錄Pandas 合并數據集&#xff1a;concat 和 append回顧&#xff1a;NumPy 數組的拼接使用 pd.concat 進行簡單拼接重復索引將重復索引視為錯誤忽略索引添加多級索引&#xff08;MultiIndex&#xff09;鍵使用連接&#xff08;Join&#xff09;方式拼接append 方法Pandas …

2025年5月架構設計師綜合知識真題回顧,附參考答案、解析及所涉知識點(七)

本文主要回顧2025年上半年(2025-5-24)系統架構設計師考試上午綜合知識科目的選擇題,同時附帶參考答案、解析和所涉知識點。 2025年5月架構設計師綜合知識真題回顧,附參考答案、解析及所涉知識點(一) 2025年5月架構設計師綜合知識真題回顧,附參考答案、解析及所涉知識點(…

面向RF設計人員的微帶貼片天線計算器

微帶貼片天線和陣列可能是僅次于單極天線和偶極天線的最簡單的天線設計。這些天線也很容易集成到PCB中&#xff0c;因此通常用于5G天線陣列和雷達等高級系統。這些天線陣列在基諧模式和高階模式下也遵循一組簡單的設計方程&#xff0c;因此您甚至可以在不使用仿真工具的情況下設…

明基RD280U編程顯示器深度測評:碼農的「第二塊鍵盤」竟然會發光?

文章目錄前言一、開箱篇&#xff1a;當理工男遇到「俄羅斯套娃式包裝」二、外觀篇&#xff1a;深空灰的「代碼容器」1. 桌面變形記2. 保護肩頸的人體工學設計三、顯示篇&#xff1a;給代碼做「光子嫩膚」1. 28寸超大大屏 3:2屏比 4K超清2.專業編程模式&#xff0c;讓代碼一目…

算法114. 二叉樹展開為鏈表

題目&#xff1a;給你二叉樹的根結點 root &#xff0c;請你將它展開為一個單鏈表&#xff1a; 展開后的單鏈表應該同樣使用 TreeNode &#xff0c;其中 right 子指針指向鏈表中下一個結點&#xff0c;而左子指針始終為 null 。 展開后的單鏈表應該與二叉樹 先序遍歷 順序相同。…

智慧能源管理系統:點亮山東零碳園區的綠色引擎

一、概述在全球積極踐行“雙碳”目標的時代浪潮下&#xff0c;山東作為經濟大省&#xff0c;正全力推動產業的綠色變革&#xff0c;零碳園區建設成為其中的關鍵一環。《山東省零碳園區建設方案》明確規劃&#xff0c;到2027年建成15個左右省級零碳園區 &#xff0c;到2030年進一…

分布式日志分析平臺(ELFK 與 EFK)理論

一、日志分析平臺核心概念在分布式系統中&#xff0c;日志是系統運行狀態監控、問題排查和業務分析的重要依據。隨著系統規模擴大&#xff0c;單機日志管理方式已無法滿足需求&#xff0c;分布式日志分析平臺應運而生。其核心目標是實現日志的集中收集、統一處理、高效存儲和可…

CoreShop微信小程序商城框架開啟多租戶-添加一個WPF客戶端以便進行本地操作--讀取店鋪信息(6)

本節內容&#xff0c;使用登錄的token進行店鋪信息讀取&#xff0c;順利的話&#xff0c;進行EXCEL上傳測試。 1。在后臺編寫 讀取店鋪信息代碼 1.1 查看原來鋪店信息在什么位置&#xff0c;店鋪的表格為CoreCmsStore#region 獲取列表// POST: Api/CoreCmsStore/GetPageList///…

UE5關卡藍圖能不能保存副本呀?

提問 關卡藍圖能不能保存副本呀&#xff1f; 回答 在 UE 里&#xff0c;“關卡藍圖&#xff08;Level Blueprint&#xff09;”本身其實是不能直接復制/保存成獨立資源的&#xff0c;因為它和具體的 **Level&#xff08;.umap 文件&#xff09;**是綁定的——相當于一個“場景腳…

機器學習數據預處理學習報告

一、學習背景與目的在機器學習流程中&#xff0c;數據預處理是保障模型訓練效果的關鍵環節。原始數據常存在缺失值、量綱不一致、特征格式不匹配等問題&#xff0c;直接影響模型對數據規律的學習。本次學習圍繞 Pandas 與 Scikit-learn&#xff08;sklearn&#xff09;工具庫&a…

git舊倉庫遷移到新倉庫

git舊倉庫遷移到新倉庫 A倉庫(舊倉庫)&#xff1a;git172.16.21.21:xxxx_software/Ni-Handler-Mgr.git B倉庫(新倉庫)&#xff1a;git172.16.11.11:yyyy/hostpc/ni-handler-mgr.git Step1 新建新倉庫 創建新 GitHub 倉庫? 在 GitHub 頁面點擊 “New repository”&#xff0c;命…

YOLO --- YOLOv5模型以及項目詳解

YOLO — YOLOv5模型以及項目詳解 文章目錄YOLO --- YOLOv5模型以及項目詳解一&#xff0c;開源地址二&#xff0c;改進點Focus 模塊三&#xff0c;網絡結構3.1 CSP1_X 與 CSP2_X3.2 自適應Anchor的計算3.3 激活函數3.3.1 SiLU3.3.2 Swish3.4 Bottleneck3.5 C33.5.1 BottleneckC…

Linux文本三劍客的使用及常見重點操作

文本三劍客指 Linux環境下的 grep&#xff08;搜索&#xff09;、sed&#xff08;編輯&#xff09;、awk&#xff08;分析&#xff09;三款用于文本處理的核心命令&#xff0c;三者分工明確、功能互補&#xff0c;是處理日志、配置文件、結構化數據等場景的 “剛需工具”。一、…

??《開源字幕神器VideoCaptioner實戰:基于Whisper+LLM的全鏈路方案,免費平替剪映會員》??

&#x1f4cc; 大家好&#xff0c;我是智界工具庫&#xff0c;每天分享好用實用且智能的開源項目&#xff0c;以及在JAVA語言開發中遇到的問題&#xff0c;如果本篇文章對您有所幫助&#xff0c;請幫我點個小贊小收藏小關注吧&#xff0c;謝謝喲&#xff01;&#x1f618; 博主…

redisIO模型

??1. 總述核心??“Redis采用了??單線程的Reactor模型??來處理網絡IO和命令請求。其核心在于&#xff0c;??它使用一個主線程通過IO多路復用機制來并發地處理大量的客戶端連接&#xff0c;而實際的命令解析和執行則是單線程的??。”這句話非常重要&#xff0c;它直接…

視覺采集模塊的用法

一、圖像源模塊用法采集模塊中最基礎的單元就是圖像源模塊&#xff0c;其中圖像的輸入方式包括相機輸入、本地圖像、SDK三種。添加圖像源后&#xff0c;需要對內部的參數進行對應的配置&#xff0c;正常我們連接相機后圖像源選擇我們對應的連接相機。配置所需要的相機參數&…

Linux下基于Electron的程序ibus輸入法問題

Linux下基于Electron的程序ibus輸入法問題 最近想體驗一下KDE Plasma桌面&#xff0c;遇到一個問題&#xff0c;就是瀏覽器輸入不了中文&#xff0c;Edge、Chrome都一樣&#xff0c;當然它們都是基于Chromium的&#xff0c;出同樣的問題很正常。后面發現Visual Code也有同樣的問…