Bläddra i källkod

Merge branch 'dev' into feat/otel-logger-handler

Tim Jaeryang Baek 2 månader sedan
förälder
incheckning
49926f06ee

+ 17 - 0
backend/open_webui/env.py

@@ -644,12 +644,19 @@ AUDIT_EXCLUDED_PATHS = [path.lstrip("/") for path in AUDIT_EXCLUDED_PATHS]
 ENABLE_OTEL = os.environ.get("ENABLE_OTEL", "False").lower() == "true"
 ENABLE_OTEL_METRICS = os.environ.get("ENABLE_OTEL_METRICS", "False").lower() == "true"
 ENABLE_OTEL_LOGS = os.environ.get("ENABLE_OTEL_LOGS", "False").lower() == "true"
+
 OTEL_EXPORTER_OTLP_ENDPOINT = os.environ.get(
     "OTEL_EXPORTER_OTLP_ENDPOINT", "http://localhost:4317"
 )
+OTEL_METRICS_EXPORTER_OTLP_ENDPOINT = os.environ.get(
+    "OTEL_METRICS_EXPORTER_OTLP_ENDPOINT", OTEL_EXPORTER_OTLP_ENDPOINT
+)
 OTEL_EXPORTER_OTLP_INSECURE = (
     os.environ.get("OTEL_EXPORTER_OTLP_INSECURE", "False").lower() == "true"
 )
+OTEL_METRICS_EXPORTER_OTLP_INSECURE = (
+    os.environ.get("OTEL_METRICS_EXPORTER_OTLP_INSECURE", "False").lower() == "true"
+)
 OTEL_SERVICE_NAME = os.environ.get("OTEL_SERVICE_NAME", "open-webui")
 OTEL_RESOURCE_ATTRIBUTES = os.environ.get(
     "OTEL_RESOURCE_ATTRIBUTES", ""
@@ -660,11 +667,21 @@ OTEL_TRACES_SAMPLER = os.environ.get(
 OTEL_BASIC_AUTH_USERNAME = os.environ.get("OTEL_BASIC_AUTH_USERNAME", "")
 OTEL_BASIC_AUTH_PASSWORD = os.environ.get("OTEL_BASIC_AUTH_PASSWORD", "")
 
+OTEL_METRICS_BASIC_AUTH_USERNAME = os.environ.get(
+    "OTEL_METRICS_BASIC_AUTH_USERNAME", OTEL_BASIC_AUTH_USERNAME
+)
+OTEL_METRICS_BASIC_AUTH_PASSWORD = os.environ.get(
+    "OTEL_METRICS_BASIC_AUTH_PASSWORD", OTEL_BASIC_AUTH_PASSWORD
+)
 
 OTEL_OTLP_SPAN_EXPORTER = os.environ.get(
     "OTEL_OTLP_SPAN_EXPORTER", "grpc"
 ).lower()  # grpc or http
 
+OTEL_METRICS_OTLP_SPAN_EXPORTER = os.environ.get(
+    "OTEL_METRICS_OTLP_SPAN_EXPORTER", OTEL_OTLP_SPAN_EXPORTER
+).lower()  # grpc or http
+
 
 ####################################
 # TOOLS/FUNCTIONS PIP OPTIONS

+ 46 - 14
backend/open_webui/utils/telemetry/metrics.py

@@ -19,37 +19,69 @@ from __future__ import annotations
 
 import time
 from typing import Dict, List, Sequence, Any
+from base64 import b64encode
 
 from fastapi import FastAPI, Request
 from opentelemetry import metrics
 from opentelemetry.exporter.otlp.proto.grpc.metric_exporter import (
     OTLPMetricExporter,
 )
+
+from opentelemetry.exporter.otlp.proto.http.metric_exporter import (
+    OTLPMetricExporter as OTLPHttpMetricExporter,
+)
 from opentelemetry.sdk.metrics import MeterProvider
 from opentelemetry.sdk.metrics.view import View
 from opentelemetry.sdk.metrics.export import (
     PeriodicExportingMetricReader,
 )
-from opentelemetry.sdk.resources import SERVICE_NAME, Resource
-
-from open_webui.env import OTEL_SERVICE_NAME, OTEL_EXPORTER_OTLP_ENDPOINT
-
+from opentelemetry.sdk.resources import Resource
+
+from open_webui.env import (
+    OTEL_SERVICE_NAME,
+    OTEL_METRICS_EXPORTER_OTLP_ENDPOINT,
+    OTEL_METRICS_BASIC_AUTH_USERNAME,
+    OTEL_METRICS_BASIC_AUTH_PASSWORD,
+    OTEL_METRICS_OTLP_SPAN_EXPORTER,
+    OTEL_METRICS_EXPORTER_OTLP_INSECURE,
+)
 from open_webui.socket.main import get_active_user_ids
 from open_webui.models.users import Users
 
 _EXPORT_INTERVAL_MILLIS = 10_000  # 10 seconds
 
 
-def _build_meter_provider() -> MeterProvider:
+def _build_meter_provider(resource: Resource) -> MeterProvider:
     """Return a configured MeterProvider."""
+    headers = []
+    if OTEL_METRICS_BASIC_AUTH_USERNAME and OTEL_METRICS_BASIC_AUTH_PASSWORD:
+        auth_string = (
+            f"{OTEL_METRICS_BASIC_AUTH_USERNAME}:{OTEL_METRICS_BASIC_AUTH_PASSWORD}"
+        )
+        auth_header = b64encode(auth_string.encode()).decode()
+        headers = [("authorization", f"Basic {auth_header}")]
 
     # Periodic reader pushes metrics over OTLP/gRPC to collector
-    readers: List[PeriodicExportingMetricReader] = [
-        PeriodicExportingMetricReader(
-            OTLPMetricExporter(endpoint=OTEL_EXPORTER_OTLP_ENDPOINT),
-            export_interval_millis=_EXPORT_INTERVAL_MILLIS,
-        )
-    ]
+    if OTEL_METRICS_OTLP_SPAN_EXPORTER == "http":
+        readers: List[PeriodicExportingMetricReader] = [
+            PeriodicExportingMetricReader(
+                OTLPHttpMetricExporter(
+                    endpoint=OTEL_METRICS_EXPORTER_OTLP_ENDPOINT, headers=headers
+                ),
+                export_interval_millis=_EXPORT_INTERVAL_MILLIS,
+            )
+        ]
+    else:
+        readers: List[PeriodicExportingMetricReader] = [
+            PeriodicExportingMetricReader(
+                OTLPMetricExporter(
+                    endpoint=OTEL_METRICS_EXPORTER_OTLP_ENDPOINT,
+                    insecure=OTEL_METRICS_EXPORTER_OTLP_INSECURE,
+                    headers=headers,
+                ),
+                export_interval_millis=_EXPORT_INTERVAL_MILLIS,
+            )
+        ]
 
     # Optional view to limit cardinality: drop user-agent etc.
     views: List[View] = [
@@ -70,17 +102,17 @@ def _build_meter_provider() -> MeterProvider:
     ]
 
     provider = MeterProvider(
-        resource=Resource.create({SERVICE_NAME: OTEL_SERVICE_NAME}),
+        resource=resource,
         metric_readers=list(readers),
         views=views,
     )
     return provider
 
 
-def setup_metrics(app: FastAPI) -> None:
+def setup_metrics(app: FastAPI, resource: Resource) -> None:
     """Attach OTel metrics middleware to *app* and initialise provider."""
 
-    metrics.set_meter_provider(_build_meter_provider())
+    metrics.set_meter_provider(_build_meter_provider(resource))
     meter = metrics.get_meter(__name__)
 
     # Instruments

+ 3 - 6
backend/open_webui/utils/telemetry/setup.py

@@ -26,11 +26,8 @@ from open_webui.env import (
 
 def setup(app: FastAPI, db_engine: Engine):
     # set up trace
-    trace.set_tracer_provider(
-        TracerProvider(
-            resource=Resource.create(attributes={SERVICE_NAME: OTEL_SERVICE_NAME})
-        )
-    )
+    resource = Resource.create(attributes={SERVICE_NAME: OTEL_SERVICE_NAME})
+    trace.set_tracer_provider(TracerProvider(resource=resource))
 
     # Add basic auth header only if both username and password are not empty
     headers = []
@@ -56,4 +53,4 @@ def setup(app: FastAPI, db_engine: Engine):
 
     # set up metrics only if enabled
     if ENABLE_OTEL_METRICS:
-        setup_metrics(app)
+        setup_metrics(app, resource)

+ 2 - 0
backend/open_webui/utils/tools.py

@@ -520,6 +520,8 @@ async def get_tool_servers_data(
         openapi_data = response.get("openapi", {})
 
         if info and isinstance(openapi_data, dict):
+            openapi_data["info"] = openapi_data.get("info", {})
+
             if "name" in info:
                 openapi_data["info"]["title"] = info.get("name", "Tool Server")
 

+ 5 - 5
backend/requirements.txt

@@ -9,7 +9,7 @@ passlib[bcrypt]==1.7.4
 cryptography
 
 requests==2.32.4
-aiohttp==3.11.11
+aiohttp==3.12.15
 async-timeout
 aiocache
 aiofiles
@@ -42,14 +42,14 @@ asgiref==3.8.1
 # AI libraries
 openai
 anthropic
-google-genai==1.15.0
+google-genai==1.28.0
 google-generativeai==0.8.5
 tiktoken
 
 langchain==0.3.26
 langchain-community==0.3.26
 
-fake-useragent==2.1.0
+fake-useragent==2.2.0
 chromadb==0.6.3
 posthog==5.4.0
 pymilvus==2.5.0
@@ -75,7 +75,7 @@ docx2txt==0.8
 python-pptx==1.0.2
 unstructured==0.16.17
 nltk==3.9.1
-Markdown==3.7
+Markdown==3.8.2
 pypandoc==1.15
 pandas==2.2.3
 openpyxl==3.1.5
@@ -97,7 +97,7 @@ onnxruntime==1.20.1
 faster-whisper==1.1.1
 
 PyJWT[crypto]==2.10.1
-authlib==1.4.1
+authlib==1.6.1
 
 black==25.1.0
 langfuse==2.44.0

+ 6 - 1
src/lib/components/chat/Messages/Markdown/HTMLToken.svelte

@@ -122,6 +122,11 @@
 	{:else if token.text.includes(`<source_id`)}
 		<Source {id} {token} onClick={onSourceClick} />
 	{:else}
-		{token.text}
+		{@const br = token.text.match(/<br\s*\/?>/)}
+		{#if br}
+			<br />
+		{:else}
+			{token.text}
+		{/if}
 	{/if}
 {/if}

+ 1 - 1
src/lib/components/layout/Sidebar.svelte

@@ -82,7 +82,7 @@
 
 	const initPinnedModelsSortable = () => {
 		const pinnedModelsList = document.getElementById('pinned-models-list');
-		if (pinnedModelsList) {
+		if (pinnedModelsList && !$mobile) {
 			new Sortable(pinnedModelsList, {
 				animation: 150,
 				onUpdate: async (event) => {

+ 1 - 1
src/lib/i18n/locales/fa-IR/translation.json

@@ -1072,7 +1072,7 @@
 	"Refer to yourself as \"User\" (e.g., \"User is learning Spanish\")": "به خود به عنوان \"کاربر\" اشاره کنید (مثلاً، \"کاربر در حال یادگیری اسپانیایی است\")",
 	"References from": "مراجع از",
 	"Refused when it shouldn't have": "رد شده زمانی که باید نباشد",
-	"Regenerate": "ری\u200cسازی",
+	"Regenerate": "تولید مجدد",
 	"Reindex": "فهرست\u200cبندی مجدد",
 	"Reindex Knowledge Base Vectors": "فهرست\u200cبندی مجدد بردارهای پایگاه دانش",
 	"Release Notes": "یادداشت\u200cهای انتشار",