๋ณธ๋ฌธ์œผ๋กœ ๊ฑด๋„ˆ๋›ฐ๊ธฐ
๊ฒฝ๊ณ 

์ด ํŠœํ† ๋ฆฌ์–ผ์€ ์ปค๋ฎค๋‹ˆํ‹ฐ ๊ธฐ์—ฌ๋กœ ์ž‘์„ฑ๋˜์—ˆ์œผ๋ฉฐ Open WebUI ํŒ€์— ์˜ํ•ด ์ง€์›๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ํŠน์ • ์‚ฌ์šฉ ์‚ฌ๋ก€์— ๋งž๊ฒŒ Open WebUI๋ฅผ ์ปค์Šคํ„ฐ๋งˆ์ด์ฆˆํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์‹œ์—ฐํ•˜๋Š” ๋ชฉ์ ์„ ๊ฐ–์Šต๋‹ˆ๋‹ค. ๊ธฐ์—ฌํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด ๊ธฐ์—ฌ ํŠœํ† ๋ฆฌ์–ผ์„ ํ™•์ธํ•˜์„ธ์š”.

๐Ÿ’ก ํŠน์ˆ˜ ์ธ์ž

์ž์‹ ๋งŒ์˜ Tools, Functions (Filters, Pipes ๋˜๋Š” Actions), Pipelines ๋“ฑ์„ ๊ฐœ๋ฐœํ•  ๋•Œ Open-WebUI์˜ ๋ชจ๋“  ๊ฐ€๋Šฅ์„ฑ์„ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ํŠน์ˆ˜ ์ธ์ž๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ด ํŽ˜์ด์ง€๋Š” ๊ฐ ํŠน์ˆ˜ ์ธ์ž์˜ ์œ ํ˜•๊ณผ ๊ตฌ์กฐ๋ฅผ ์ž์„ธํžˆ ์„ค๋ช…ํ•˜๊ณ  ์˜ˆ์ œ๋„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

bodyโ€‹

dict ํ˜•์‹์œผ๋กœ ๋ณดํ†ต ๋ชจ๋ธ์— ๊ฑฐ์˜ ์ง์ ‘ ์ „๋‹ฌ๋˜๋Š” ๋ฐ์ดํ„ฐ์ž…๋‹ˆ๋‹ค. ์—„๋ฐ€ํžˆ ๋งํ•ด ํŠน์ˆ˜ ์ธ์ž๋Š” ์•„๋‹ˆ์ง€๋งŒ, ๋” ์‰ฝ๊ฒŒ ์ฐธ์กฐํ•˜๊ธฐ ์œ„ํ•ด ์—ฌ๊ธฐ์— ํฌํ•จ๋˜์—ˆ์œผ๋ฉฐ ์ž์ฒด์ ์œผ๋กœ ์ผ๋ถ€ ํŠน์ˆ˜ ์ธ์ž๋ฅผ ํฌํ•จํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

์˜ˆ์ œ

{
"stream": true,
"model": "my-cool-model",
# ์†Œ๋ฌธ์ž ๋ฌธ์ž์—ด, ํ•˜์ดํ”ˆ์œผ๋กœ ๊ตฌ๋ถ„๋œ ๋‹จ์–ด: ์ด๊ฒƒ์ด ๋ชจ๋ธ์˜ ID ์ž…๋‹ˆ๋‹ค
"messages": [
{
"role": "user",
"content": [
{
"type": "text",
"text": "์ด ์‚ฌ์ง„์— ๋ฌด์—‡์ด ์žˆ๋‚˜์š”?"
},
{
"type": "image_url",
"image_url": {
"url": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAdYAAAGcCAYAAABk2YF[REDACTED]"
# ์ด๋ฏธ์ง€๋Š” base64๋กœ ์ธ์ฝ”๋”ฉ๋œ ๋ฐ์ดํ„ฐ๋กœ ์ „๋‹ฌ๋ฉ๋‹ˆ๋‹ค
}
}
]
},
{
"role": "assistant",
"content": "์ด๋ฏธ์ง€๋Š” [REDACTED]์ฒ˜๋Ÿผ ๋ณด์ž…๋‹ˆ๋‹ค"
},
],
"features": {
"image_generation": false,
"code_interpreter": false,
"web_search": false
},
"stream_options": {
"include_usage": true
},
"metadata": "[__metadata__์™€ ์ •ํ™•ํžˆ ๋™์ผํ•œ dict]",
"files": "[__files__์™€ ์ •ํ™•ํžˆ ๋™์ผํ•œ ๋ชฉ๋ก]"
}


__user__โ€‹

์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ํฌํ•จํ•˜๋Š” dict์ž…๋‹ˆ๋‹ค.

UserValves ํด๋ž˜์Šค๊ฐ€ ์ •์˜๋œ๋‹ค๋ฉด, ๊ทธ ์ธ์Šคํ„ด์Šค๋Š” __user__["valves"]๋ฅผ ํ†ตํ•ด ์ ‘๊ทผํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ ‡์ง€ ์•Š๋‹ค๋ฉด valves ํ‚ค๊ฐ’์€ __user__์—์„œ ์™„์ „ํžˆ ์ƒ๋žต๋ฉ๋‹ˆ๋‹ค.

์˜ˆ์ œ
{
"id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"email": "[email protected]",
"name": "Patrick",
"role": "user",
# ์—ญํ• ์€ `user` ๋˜๋Š” `admin`์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค
"valves": "[UserValve ์ธ์Šคํ„ด์Šค]"
}

__metadata__โ€‹

์ฑ„ํŒ…, ๋ชจ๋ธ, ํŒŒ์ผ ๋“ฑ์— ๋Œ€ํ•œ ๊ด‘๋ฒ”์œ„ํ•œ ์ •๋ณด๋ฅผ ํฌํ•จํ•˜๋Š” dict์ž…๋‹ˆ๋‹ค.

์˜ˆ์ œ
{
"user_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"chat_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"message_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"session_id": "xxxxxxxxxxxxxxxxxxxx",
"tool_ids": null,
# tool_ids๋Š” ๋ฌธ์ž์—ด์˜ ๋ชฉ๋ก์ž…๋‹ˆ๋‹ค.
"tool_servers": [],
"files": "[body['files']์™€ ๋™์ผ]",
# ํŒŒ์ผ์ด ์ง€์ •๋˜์ง€ ์•Š์€ ๊ฒฝ์šฐ __metadata__์— ํŒŒ์ผ ํ‚ค๊ฐ€ ์กด์žฌํ•˜๋ฉฐ ๊ฐ’์€ [] ์ž…๋‹ˆ๋‹ค
"features": {
"image_generation": false,
"code_interpreter": false,
"web_search": false
},
"variables": {
"{{USER_NAME}}": "cheesy_username",
"{{USER_LOCATION}}": "Unknown",
"{{CURRENT_DATETIME}}": "2025-02-02 XX:XX:XX",
"{{CURRENT_DATE}}": "2025-02-02",
"{{CURRENT_TIME}}": "XX:XX:XX",
"{{CURRENT_WEEKDAY}}": "Monday",
"{{CURRENT_TIMEZONE}}": "Europe/Berlin",
"{{USER_LANGUAGE}}": "en-US"
},
"model": "[__model__์™€ ๋™์ผํ•œ dict]",
"direct": false,
"function_calling": "native",
"type": "user_response",
"interface": "open-webui"
}

__model__โ€‹

๋ชจ๋ธ์— ๋Œ€ํ•œ ์ •๋ณด๋ฅผ ํฌํ•จํ•˜๋Š” dict์ž…๋‹ˆ๋‹ค.

์˜ˆ์ œ
{
"id": "my-cool-model",
"name": "My Cool Model",
"object": "model",
"created": 1746000000,
"owned_by": "openai",
# openai ๋˜๋Š” ollama ์ค‘ ํ•˜๋‚˜
"info": {
"id": "my-cool-model",
"user_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"base_model_id": "gpt-4o",
# ๋ชจ๋ธ ์—”๋“œํฌ์ธํŠธ๊ฐ€ ์ œ๊ณตํ•˜๋Š” ์‹ค์ œ ๋ชจ๋ธ ์ด๋ฆ„
"name": "My Cool Model",
"params": {
"system": "๋‹น์‹ ์€ ๋‚˜์˜ ์ตœ๊ณ ์˜ ๋น„์„œ์ž…๋‹ˆ๋‹ค. ๋‹น์‹ ์€ [REDACTED]๋ผ๊ณ  ๋‹ต๋ณ€ํ•ฉ๋‹ˆ๋‹ค",
"function_calling": "native"
# ์‚ฌ์šฉ์ž ์ •์˜ ์˜ต์…˜์€ ์—ฌ๊ธฐ ๋‚˜ํƒ€๋‚ฉ๋‹ˆ๋‹ค, ์˜ˆ๋ฅผ ๋“ค์–ด "Top K"
},
"meta": {
"profile_image_url": "/static/favicon.png",
"description": "my-cool-model์— ๋Œ€ํ•œ ์„ค๋ช…",
"capabilities": {
"vision": true,
"usage": true,
"citations": true
},
"position": 17,
"tags": [
{
"name": "for_friends"
},
{
"name": "vision_enabled"
}
],
"suggestion_prompts": null
},
"access_control": {
"read": {
"group_ids": [],
"user_ids": []
},
"write": {
"group_ids": [],
"user_ids": []
}
},
"is_active": true,
"updated_at": 1740000000,
"created_at": 1740000000
},
"preset": true,
"actions": [],
"tags": [
{
"name": "for_friends"
},
{
"name": "vision_enabled"
}
]
}

__messages__โ€‹

์ด์ „ ๋ฉ”์‹œ์ง€๋“ค์˜ list์ž…๋‹ˆ๋‹ค.

body["messages"] ๊ฐ’์€ ์œ„๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”.

__chat_id__โ€‹

chat_id์˜ str์ž…๋‹ˆ๋‹ค.

__metadata__["chat_id"] ๊ฐ’์€ ์œ„๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”.

__session_id__โ€‹

session_id์˜ str์ž…๋‹ˆ๋‹ค.

__metadata__["session_id"] ๊ฐ’์€ ์œ„๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”.

__message_id__โ€‹

message_id์˜ str์ž…๋‹ˆ๋‹ค.

__metadata__["message_id"] ๊ฐ’์€ ์œ„๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”.

__event_emitter__โ€‹

์‚ฌ์šฉ์ž์—๊ฒŒ ์ด๋ฒคํŠธ ์ •๋ณด๋ฅผ ํ‘œ์‹œํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉ๋˜๋Š” Callable์ž…๋‹ˆ๋‹ค.

__event_call__โ€‹

Actions์„ ์œ„ํ•ด ์‚ฌ์šฉ๋˜๋Š” Callable์ž…๋‹ˆ๋‹ค.

__files__โ€‹

์ฑ„ํŒ…์„ ํ†ตํ•ด ์ „์†ก๋œ ํŒŒ์ผ๋“ค์˜ list์ž…๋‹ˆ๋‹ค. ์ด๋ฏธ์ง€๋Š” ํŒŒ์ผ๋กœ ๊ฐ„์ฃผ๋˜์ง€ ์•Š์œผ๋ฉฐ body["messages"] ๋ฆฌ์ŠคํŠธ์˜ ์ผ๋ถ€๋กœ ๋ชจ๋ธ์— ์ง์ ‘ ์ „์†ก๋ฉ๋‹ˆ๋‹ค.

์„ฑ๋Šฅ์ƒ์˜ ์ด์œ ๋กœ ํŒŒ์ผ์˜ ์‹ค์ œ ๋ฐ”์ด๋„ˆ๋ฆฌ๋Š” ์ธ์ˆ˜์— ํฌํ•จ๋˜์ง€ ์•Š์ง€๋งŒ ํ•„์š”ํ•  ๊ฒฝ์šฐ ๊ฒฝ๋กœ๋ฅผ ํ†ตํ•ด ์—ฌ์ „ํžˆ ์ ‘๊ทผ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, docker๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ ํŒŒ์ด์ฌ ๊ฒฝ๋กœ ๊ตฌ๋ฌธ์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค:

from pathlib import Path

the_file = Path(f"/app/backend/data/uploads/{__files__[0]["files"]["id"]}_{__files__[0]["files"]["filename"]}")
assert the_file.exists()

ํŒŒ์ผ dict๋Š” __metadata__["files"]๋ฅผ ํ†ตํ•ด์„œ๋„ ์ ‘๊ทผ ๊ฐ€๋Šฅํ•˜๋ฉฐ (ํŒŒ์ผ์ด ์ „์†ก๋˜์ง€ ์•Š์€ ๊ฒฝ์šฐ ๊ฐ’์€ []) body["files"]๋ฅผ ํ†ตํ•ด์„œ๋„ ์ ‘๊ทผ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค (body์— ํŒŒ์ผ์ด ์ „์†ก๋˜์ง€ ์•Š์œผ๋ฉด files ํ‚ค ์ž์ฒด๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š์Œ).

์˜ˆ์ œ

[
{
"type": "file",
"file": {
"id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"filename": "Napoleon - Wikipedia.pdf",
"user_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"hash": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"data": {
"content": "Napoleon - Wikipedia\n\n\nNapoleon I\n\nThe Emperor Napoleon in His Study at the\nTuileries, 1812\n\nEmperor of the French\n\n1st reign 18 May 1804 โ€“ 6 April 1814\n\nSuccessor Louis XVIII[a]\n\n2nd reign 20 March 1815ย โ€“ 22ย June 1815\n\nSuccessor Louis XVIII[a]\n\nFirst Consul of the French Republic\n\nIn office\n13 December 1799ย โ€“ 18 May 1804\n\nBorn Napoleone Buonaparte\n15 August 1769\nAjaccio, Corsica, Kingdom of\nFrance\n\nDied 5 May 1821 (agedย 51)\nLongwood, Saint Helena\n\nBurial 15 December 1840\nLes Invalides, Paris\n\nNapoleon\nNapoleon Bonaparte[b] (born Napoleone\nBuonaparte;[1][c] 15 August 1769 โ€“ 5 May 1821), later\nknown [REDACTED]",
# ์ฝ˜ํ…์ธ  ๊ฐ’์€ ๋ฌธ์„œ ๋ถ„์„๊ธฐ์˜ ์ถœ๋ ฅ๋ฌผ์ด๋ฉฐ, ์œ„ ์˜ˆ์‹œ๋Š” Tika๋ฅผ ๋ฌธ์„œ ๋ถ„์„๊ธฐ๋กœ ์‚ฌ์šฉํ•œ ๊ฒฝ์šฐ์ž…๋‹ˆ๋‹ค
},
"meta": {
"name": "Napoleon - Wikipedia.pdf",
"content_type": "application/pdf",
"size": 10486578,
# ๋ฐ”์ดํŠธ ๋‹จ์œ„, ์—ฌ๊ธฐ์„œ๋Š” ์•ฝ 10Mb
"data": {},
"collection_name": "file-96xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
# ํ•ญ์ƒ 'file'๋กœ ์‹œ์ž‘๋จ
},
"created_at": 1740000000,
"updated_at": 1740000000
},
"id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"url": "/api/v1/files/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"name": "Napoleon - Wikipedia.pdf",
"collection_name": "file-96xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
"status": "uploaded",
"size": 10486578,
"error": "",
"itemId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
# itemId๋Š” file["id"]์™€ ๋™์ผํ•˜์ง€ ์•Š์Œ
}
]


__request__โ€‹

fastapi.Request์˜ ์ธ์Šคํ„ด์Šค์ž…๋‹ˆ๋‹ค. ๋” ์ž์„ธํ•œ ๋‚ด์šฉ์€ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ํŽ˜์ด์ง€ ๋˜๋Š” fastapi์˜ ๋ฌธ์„œ๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”.

__task__โ€‹

์ž‘์—… ์œ ํ˜•์— ๋Œ€ํ•œ str์ž…๋‹ˆ๋‹ค. ๊ฐ’์€ __metadata__["task"]๊ฐ€ ์žˆ๋Š” ๊ฒฝ์šฐ ํ•ด๋‹น ๊ฐ’์„ ๊ฐ„๋‹จํžˆ ๋‚˜ํƒ€๋‚ด๋ฉฐ, ๊ทธ๋ ‡์ง€ ์•Š์€ ๊ฒฝ์šฐ None์ž…๋‹ˆ๋‹ค.

๊ฐ€๋Šฅํ•œ ๊ฐ’

[
"title_generation",
"tags_generation",
"emoji_generation",
"query_generation",
"image_prompt_generation",
"autocomplete_generation",
"function_calling",
"moa_response_generation"
]

__task_body__โ€‹

์ฃผ์–ด์ง„ __task__๋ฅผ ์ˆ˜ํ–‰ํ•˜๊ธฐ ์œ„ํ•ด ํ•„์š”ํ•œ body๋ฅผ ํฌํ•จํ•˜๋Š” dict์ž…๋‹ˆ๋‹ค. ๊ฐ’์€ __metadata__["task_body"]๊ฐ€ ์žˆ๋Š” ๊ฒฝ์šฐ ํ•ด๋‹น ๊ฐ’์„ ๊ฐ„๋‹จํžˆ ๋‚˜ํƒ€๋‚ด๋ฉฐ, ๊ทธ๋ ‡์ง€ ์•Š์€ ๊ฒฝ์šฐ None์ž…๋‹ˆ๋‹ค.

๊ตฌ์กฐ๋Š” ์œ„์˜ body์™€ ๋™์ผํ•˜๋ฉฐ ์ ์ ˆํ•œ ๋ชจ๋ธ๊ณผ ์‹œ์Šคํ…œ ๋ฉ”์‹œ์ง€ ๋“ฑ์„ ์‚ฌ์šฉํ•˜๋„๋ก ์ˆ˜์ •๋ฉ๋‹ˆ๋‹ค.

__tools__โ€‹

ToolUserModel ์ธ์Šคํ„ด์Šค๋“ค์˜ list์ž…๋‹ˆ๋‹ค.

ToolUserModel ์ธ์Šคํ„ด์Šค์˜ ์†์„ฑ์— ๋Œ€ํ•œ ์ž์„ธํ•œ ๋‚ด์šฉ์€ tools.py์˜ ์ฝ”๋“œ๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”.