agno 2.3.13__py3-none-any.whl → 2.3.15__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- agno/agent/agent.py +1149 -1392
- agno/db/migrations/manager.py +3 -3
- agno/eval/__init__.py +21 -8
- agno/knowledge/embedder/azure_openai.py +0 -1
- agno/knowledge/embedder/google.py +1 -1
- agno/models/anthropic/claude.py +9 -4
- agno/models/base.py +8 -4
- agno/models/metrics.py +12 -0
- agno/models/openai/chat.py +2 -0
- agno/models/openai/responses.py +2 -2
- agno/os/app.py +59 -2
- agno/os/auth.py +40 -3
- agno/os/interfaces/a2a/router.py +619 -9
- agno/os/interfaces/a2a/utils.py +31 -32
- agno/os/middleware/jwt.py +5 -5
- agno/os/router.py +1 -57
- agno/os/routers/agents/schema.py +14 -1
- agno/os/routers/database.py +150 -0
- agno/os/routers/teams/schema.py +14 -1
- agno/os/settings.py +3 -0
- agno/os/utils.py +61 -53
- agno/reasoning/anthropic.py +85 -1
- agno/reasoning/azure_ai_foundry.py +93 -1
- agno/reasoning/deepseek.py +91 -1
- agno/reasoning/gemini.py +81 -1
- agno/reasoning/groq.py +103 -1
- agno/reasoning/manager.py +1244 -0
- agno/reasoning/ollama.py +93 -1
- agno/reasoning/openai.py +113 -1
- agno/reasoning/vertexai.py +85 -1
- agno/run/agent.py +21 -0
- agno/run/base.py +20 -1
- agno/run/team.py +21 -0
- agno/session/team.py +0 -3
- agno/team/team.py +1211 -1445
- agno/tools/toolkit.py +119 -8
- agno/utils/events.py +99 -4
- agno/utils/hooks.py +4 -10
- agno/utils/print_response/agent.py +26 -0
- agno/utils/print_response/team.py +11 -0
- agno/utils/prompts.py +8 -6
- agno/utils/string.py +46 -0
- agno/utils/team.py +1 -1
- agno/vectordb/milvus/milvus.py +32 -3
- {agno-2.3.13.dist-info → agno-2.3.15.dist-info}/METADATA +3 -2
- {agno-2.3.13.dist-info → agno-2.3.15.dist-info}/RECORD +49 -47
- {agno-2.3.13.dist-info → agno-2.3.15.dist-info}/WHEEL +0 -0
- {agno-2.3.13.dist-info → agno-2.3.15.dist-info}/licenses/LICENSE +0 -0
- {agno-2.3.13.dist-info → agno-2.3.15.dist-info}/top_level.txt +0 -0
agno/db/migrations/manager.py
CHANGED
|
@@ -66,13 +66,13 @@ class MigrationManager:
|
|
|
66
66
|
current_version = packaging_version.parse(self.db.get_latest_schema_version(table_name))
|
|
67
67
|
|
|
68
68
|
if current_version is None:
|
|
69
|
-
|
|
69
|
+
log_info(f"Skipping migration: No version found for table {table_name}.")
|
|
70
70
|
continue
|
|
71
71
|
|
|
72
72
|
# If the target version is less or equal to the current version, no migrations needed
|
|
73
73
|
if _target_version <= current_version and not force:
|
|
74
|
-
|
|
75
|
-
f"Skipping
|
|
74
|
+
log_info(
|
|
75
|
+
f"Skipping migration: the version of table '{table_name}' ({current_version}) is less or equal to the target version ({_target_version})."
|
|
76
76
|
)
|
|
77
77
|
continue
|
|
78
78
|
|
agno/eval/__init__.py
CHANGED
|
@@ -1,12 +1,4 @@
|
|
|
1
|
-
from agno.eval.accuracy import AccuracyAgentResponse, AccuracyEval, AccuracyEvaluation, AccuracyResult
|
|
2
|
-
from agno.eval.agent_as_judge import (
|
|
3
|
-
AgentAsJudgeEval,
|
|
4
|
-
AgentAsJudgeEvaluation,
|
|
5
|
-
AgentAsJudgeResult,
|
|
6
|
-
)
|
|
7
1
|
from agno.eval.base import BaseEval
|
|
8
|
-
from agno.eval.performance import PerformanceEval, PerformanceResult
|
|
9
|
-
from agno.eval.reliability import ReliabilityEval, ReliabilityResult
|
|
10
2
|
|
|
11
3
|
__all__ = [
|
|
12
4
|
"AccuracyAgentResponse",
|
|
@@ -22,3 +14,24 @@ __all__ = [
|
|
|
22
14
|
"ReliabilityEval",
|
|
23
15
|
"ReliabilityResult",
|
|
24
16
|
]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def __getattr__(name: str):
|
|
20
|
+
"""Lazy import for eval implementations to avoid circular imports with Agent."""
|
|
21
|
+
if name in ("AccuracyAgentResponse", "AccuracyEval", "AccuracyEvaluation", "AccuracyResult"):
|
|
22
|
+
from agno.eval import accuracy
|
|
23
|
+
|
|
24
|
+
return getattr(accuracy, name)
|
|
25
|
+
elif name in ("AgentAsJudgeEval", "AgentAsJudgeEvaluation", "AgentAsJudgeResult"):
|
|
26
|
+
from agno.eval import agent_as_judge
|
|
27
|
+
|
|
28
|
+
return getattr(agent_as_judge, name)
|
|
29
|
+
elif name in ("PerformanceEval", "PerformanceResult"):
|
|
30
|
+
from agno.eval import performance
|
|
31
|
+
|
|
32
|
+
return getattr(performance, name)
|
|
33
|
+
elif name in ("ReliabilityEval", "ReliabilityResult"):
|
|
34
|
+
from agno.eval import reliability
|
|
35
|
+
|
|
36
|
+
return getattr(reliability, name)
|
|
37
|
+
raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
|
|
@@ -18,7 +18,6 @@ except ImportError:
|
|
|
18
18
|
@dataclass
|
|
19
19
|
class AzureOpenAIEmbedder(Embedder):
|
|
20
20
|
id: str = "text-embedding-3-small" # This has to match the model that you deployed at the provided URL
|
|
21
|
-
|
|
22
21
|
dimensions: int = 1536
|
|
23
22
|
encoding_format: Literal["float", "base64"] = "float"
|
|
24
23
|
user: Optional[str] = None
|
agno/models/anthropic/claude.py
CHANGED
|
@@ -102,8 +102,8 @@ class Claude(Model):
|
|
|
102
102
|
# Claude Sonnet 4.x family (versions before 4.5)
|
|
103
103
|
"claude-sonnet-4-20250514",
|
|
104
104
|
"claude-sonnet-4",
|
|
105
|
-
# Claude Opus 4.x family (versions before 4.1)
|
|
106
|
-
# (Add any Opus 4.x models released before 4.1 if they exist)
|
|
105
|
+
# Claude Opus 4.x family (versions before 4.1 and 4.5)
|
|
106
|
+
# (Add any Opus 4.x models released before 4.1/4.5 if they exist)
|
|
107
107
|
}
|
|
108
108
|
|
|
109
109
|
id: str = "claude-sonnet-4-5-20250929"
|
|
@@ -191,7 +191,9 @@ class Claude(Model):
|
|
|
191
191
|
return False
|
|
192
192
|
if self.id.startswith("claude-sonnet-4-") and not self.id.startswith("claude-sonnet-4-5"):
|
|
193
193
|
return False
|
|
194
|
-
if self.id.startswith("claude-opus-4-") and not
|
|
194
|
+
if self.id.startswith("claude-opus-4-") and not (
|
|
195
|
+
self.id.startswith("claude-opus-4-1") or self.id.startswith("claude-opus-4-5")
|
|
196
|
+
):
|
|
195
197
|
return False
|
|
196
198
|
|
|
197
199
|
return True
|
|
@@ -320,8 +322,11 @@ class Claude(Model):
|
|
|
320
322
|
|
|
321
323
|
return {"type": "json_schema", "schema": schema}
|
|
322
324
|
|
|
323
|
-
# Handle dict format
|
|
325
|
+
# Handle dict format
|
|
324
326
|
elif isinstance(response_format, dict):
|
|
327
|
+
# Claude only supports json_schema, not json_object
|
|
328
|
+
if response_format.get("type") == "json_object":
|
|
329
|
+
return None
|
|
325
330
|
return response_format
|
|
326
331
|
|
|
327
332
|
return None
|
agno/models/base.py
CHANGED
|
@@ -196,7 +196,8 @@ class Model(ABC):
|
|
|
196
196
|
)
|
|
197
197
|
sleep(delay)
|
|
198
198
|
else:
|
|
199
|
-
|
|
199
|
+
if self.retries > 0:
|
|
200
|
+
log_error(f"Model provider error after {self.retries + 1} attempts: {e}")
|
|
200
201
|
except RetryableModelProviderError as e:
|
|
201
202
|
current_count = retries_with_guidance_count
|
|
202
203
|
if current_count >= self.retry_with_guidance_limit:
|
|
@@ -238,7 +239,8 @@ class Model(ABC):
|
|
|
238
239
|
)
|
|
239
240
|
await asyncio.sleep(delay)
|
|
240
241
|
else:
|
|
241
|
-
|
|
242
|
+
if self.retries > 0:
|
|
243
|
+
log_error(f"Model provider error after {self.retries + 1} attempts: {e}")
|
|
242
244
|
except RetryableModelProviderError as e:
|
|
243
245
|
current_count = retries_with_guidance_count
|
|
244
246
|
if current_count >= self.retry_with_guidance_limit:
|
|
@@ -283,7 +285,8 @@ class Model(ABC):
|
|
|
283
285
|
)
|
|
284
286
|
sleep(delay)
|
|
285
287
|
else:
|
|
286
|
-
|
|
288
|
+
if self.retries > 0:
|
|
289
|
+
log_error(f"Model provider error after {self.retries + 1} attempts: {e}")
|
|
287
290
|
except RetryableModelProviderError as e:
|
|
288
291
|
current_count = retries_with_guidance_count
|
|
289
292
|
if current_count >= self.retry_with_guidance_limit:
|
|
@@ -330,7 +333,8 @@ class Model(ABC):
|
|
|
330
333
|
)
|
|
331
334
|
await asyncio.sleep(delay)
|
|
332
335
|
else:
|
|
333
|
-
|
|
336
|
+
if self.retries > 0:
|
|
337
|
+
log_error(f"Model provider error after {self.retries + 1} attempts: {e}")
|
|
334
338
|
except RetryableModelProviderError as e:
|
|
335
339
|
current_count = retries_with_guidance_count
|
|
336
340
|
if current_count >= self.retry_with_guidance_limit:
|
agno/models/metrics.py
CHANGED
|
@@ -13,6 +13,10 @@ class Metrics:
|
|
|
13
13
|
output_tokens: int = 0
|
|
14
14
|
total_tokens: int = 0
|
|
15
15
|
|
|
16
|
+
# Cost of the run
|
|
17
|
+
# Currently only supported by some providers
|
|
18
|
+
cost: Optional[float] = None
|
|
19
|
+
|
|
16
20
|
# Audio token usage
|
|
17
21
|
audio_input_tokens: int = 0
|
|
18
22
|
audio_output_tokens: int = 0
|
|
@@ -43,6 +47,7 @@ class Metrics:
|
|
|
43
47
|
metrics_dict = asdict(self)
|
|
44
48
|
# Remove the timer util if present
|
|
45
49
|
metrics_dict.pop("timer", None)
|
|
50
|
+
# Remove any None, 0, or empty dict values
|
|
46
51
|
metrics_dict = {
|
|
47
52
|
k: v
|
|
48
53
|
for k, v in metrics_dict.items()
|
|
@@ -65,6 +70,13 @@ class Metrics:
|
|
|
65
70
|
reasoning_tokens=self.reasoning_tokens + other.reasoning_tokens,
|
|
66
71
|
)
|
|
67
72
|
|
|
73
|
+
if self.cost is not None and other.cost is not None:
|
|
74
|
+
result.cost = self.cost + other.cost
|
|
75
|
+
elif self.cost is not None:
|
|
76
|
+
result.cost = self.cost
|
|
77
|
+
elif other.cost is not None:
|
|
78
|
+
result.cost = other.cost
|
|
79
|
+
|
|
68
80
|
# Handle provider_metrics
|
|
69
81
|
if self.provider_metrics or other.provider_metrics:
|
|
70
82
|
result.provider_metrics = {}
|
agno/models/openai/chat.py
CHANGED
|
@@ -945,4 +945,6 @@ class OpenAIChat(Model):
|
|
|
945
945
|
metrics.audio_output_tokens = completion_tokens_details.audio_tokens or 0
|
|
946
946
|
metrics.reasoning_tokens = completion_tokens_details.reasoning_tokens or 0
|
|
947
947
|
|
|
948
|
+
metrics.cost = getattr(response_usage, "cost", None)
|
|
949
|
+
|
|
948
950
|
return metrics
|
agno/models/openai/responses.py
CHANGED
|
@@ -234,8 +234,8 @@ class OpenAIResponses(Model):
|
|
|
234
234
|
"strict": self.strict_output,
|
|
235
235
|
}
|
|
236
236
|
else:
|
|
237
|
-
#
|
|
238
|
-
text_params["format"] =
|
|
237
|
+
# Pass through directly, user handles everything
|
|
238
|
+
text_params["format"] = response_format
|
|
239
239
|
|
|
240
240
|
# Add text parameter if there are any text-level params
|
|
241
241
|
if text_params:
|
agno/os/app.py
CHANGED
|
@@ -34,6 +34,7 @@ from agno.os.config import (
|
|
|
34
34
|
from agno.os.interfaces.base import BaseInterface
|
|
35
35
|
from agno.os.router import get_base_router
|
|
36
36
|
from agno.os.routers.agents import get_agent_router
|
|
37
|
+
from agno.os.routers.database import get_database_router
|
|
37
38
|
from agno.os.routers.evals import get_eval_router
|
|
38
39
|
from agno.os.routers.health import get_health_router
|
|
39
40
|
from agno.os.routers.home import get_home_router
|
|
@@ -225,6 +226,9 @@ class AgentOS:
|
|
|
225
226
|
self._initialize_teams()
|
|
226
227
|
self._initialize_workflows()
|
|
227
228
|
|
|
229
|
+
# Check for duplicate IDs
|
|
230
|
+
self._raise_if_duplicate_ids()
|
|
231
|
+
|
|
228
232
|
if self.tracing:
|
|
229
233
|
self._setup_tracing()
|
|
230
234
|
|
|
@@ -266,6 +270,9 @@ class AgentOS:
|
|
|
266
270
|
self._initialize_agents()
|
|
267
271
|
self._initialize_teams()
|
|
268
272
|
self._initialize_workflows()
|
|
273
|
+
|
|
274
|
+
# Check for duplicate IDs
|
|
275
|
+
self._raise_if_duplicate_ids()
|
|
269
276
|
self._auto_discover_databases()
|
|
270
277
|
self._auto_discover_knowledge_instances()
|
|
271
278
|
|
|
@@ -334,6 +341,31 @@ class AgentOS:
|
|
|
334
341
|
self.interfaces.append(a2a_interface)
|
|
335
342
|
self._add_router(app, a2a_interface.get_router())
|
|
336
343
|
|
|
344
|
+
def _raise_if_duplicate_ids(self) -> None:
|
|
345
|
+
"""Check for duplicate IDs within each entity type.
|
|
346
|
+
|
|
347
|
+
Raises:
|
|
348
|
+
ValueError: If duplicate IDs are found within the same entity type
|
|
349
|
+
"""
|
|
350
|
+
duplicate_ids: List[str] = []
|
|
351
|
+
|
|
352
|
+
for entities in [self.agents, self.teams, self.workflows]:
|
|
353
|
+
if not entities:
|
|
354
|
+
continue
|
|
355
|
+
seen_ids: set[str] = set()
|
|
356
|
+
for entity in entities:
|
|
357
|
+
entity_id = entity.id
|
|
358
|
+
if entity_id is None:
|
|
359
|
+
continue
|
|
360
|
+
if entity_id in seen_ids:
|
|
361
|
+
if entity_id not in duplicate_ids:
|
|
362
|
+
duplicate_ids.append(entity_id)
|
|
363
|
+
else:
|
|
364
|
+
seen_ids.add(entity_id)
|
|
365
|
+
|
|
366
|
+
if duplicate_ids:
|
|
367
|
+
raise ValueError(f"Duplicate IDs found in AgentOS: {', '.join(repr(id_) for id_ in duplicate_ids)}")
|
|
368
|
+
|
|
337
369
|
def _make_app(self, lifespan: Optional[Any] = None) -> FastAPI:
|
|
338
370
|
# Adjust the FastAPI app lifespan to handle MCP connections if relevant
|
|
339
371
|
app_lifespan = lifespan
|
|
@@ -543,6 +575,7 @@ class AgentOS:
|
|
|
543
575
|
get_metrics_router(dbs=self.dbs),
|
|
544
576
|
get_knowledge_router(knowledge_instances=self.knowledge_instances),
|
|
545
577
|
get_traces_router(dbs=self.dbs),
|
|
578
|
+
get_database_router(self, settings=self.settings),
|
|
546
579
|
]
|
|
547
580
|
|
|
548
581
|
for router in routers:
|
|
@@ -583,6 +616,18 @@ class AgentOS:
|
|
|
583
616
|
|
|
584
617
|
# Add JWT middleware if authorization is enabled
|
|
585
618
|
if self.authorization:
|
|
619
|
+
# Set authorization_enabled flag on settings so security key validation is skipped
|
|
620
|
+
self.settings.authorization_enabled = True
|
|
621
|
+
|
|
622
|
+
jwt_configured = bool(getenv("JWT_VERIFICATION_KEY") or getenv("JWT_JWKS_FILE"))
|
|
623
|
+
security_key_set = bool(self.settings.os_security_key)
|
|
624
|
+
if jwt_configured and security_key_set:
|
|
625
|
+
log_warning(
|
|
626
|
+
"Both JWT configuration (JWT_VERIFICATION_KEY or JWT_JWKS_FILE) and OS_SECURITY_KEY are set. "
|
|
627
|
+
"With authorization=True, only JWT authorization will be used. "
|
|
628
|
+
"Consider removing OS_SECURITY_KEY from your environment."
|
|
629
|
+
)
|
|
630
|
+
|
|
586
631
|
self._add_jwt_middleware(fastapi_app)
|
|
587
632
|
|
|
588
633
|
return fastapi_app
|
|
@@ -981,6 +1026,8 @@ class AgentOS:
|
|
|
981
1026
|
host: str = "localhost",
|
|
982
1027
|
port: int = 7777,
|
|
983
1028
|
reload: bool = False,
|
|
1029
|
+
reload_includes: Optional[List[str]] = None,
|
|
1030
|
+
reload_excludes: Optional[List[str]] = None,
|
|
984
1031
|
workers: Optional[int] = None,
|
|
985
1032
|
access_log: bool = False,
|
|
986
1033
|
**kwargs,
|
|
@@ -1000,8 +1047,12 @@ class AgentOS:
|
|
|
1000
1047
|
Align.center(f"[bold cyan]{public_endpoint}[/bold cyan]"),
|
|
1001
1048
|
Align.center(f"\n\n[bold dark_orange]OS running on:[/bold dark_orange] http://{host}:{port}"),
|
|
1002
1049
|
]
|
|
1003
|
-
if
|
|
1004
|
-
panel_group.append(
|
|
1050
|
+
if self.authorization:
|
|
1051
|
+
panel_group.append(
|
|
1052
|
+
Align.center("\n\n[bold chartreuse3]:lock: JWT Authorization Enabled[/bold chartreuse3]")
|
|
1053
|
+
)
|
|
1054
|
+
elif bool(self.settings.os_security_key):
|
|
1055
|
+
panel_group.append(Align.center("\n\n[bold chartreuse3]:lock: Security Key Enabled[/bold chartreuse3]"))
|
|
1005
1056
|
|
|
1006
1057
|
console = Console()
|
|
1007
1058
|
console.print(
|
|
@@ -1015,11 +1066,17 @@ class AgentOS:
|
|
|
1015
1066
|
)
|
|
1016
1067
|
)
|
|
1017
1068
|
|
|
1069
|
+
# Adding *.yaml to reload_includes to reload the app when the yaml config file changes.
|
|
1070
|
+
if reload and reload_includes is not None:
|
|
1071
|
+
reload_includes = ["*.yaml", "*.yml"]
|
|
1072
|
+
|
|
1018
1073
|
uvicorn.run(
|
|
1019
1074
|
app=app,
|
|
1020
1075
|
host=host,
|
|
1021
1076
|
port=port,
|
|
1022
1077
|
reload=reload,
|
|
1078
|
+
reload_includes=reload_includes,
|
|
1079
|
+
reload_excludes=reload_excludes,
|
|
1023
1080
|
workers=workers,
|
|
1024
1081
|
access_log=access_log,
|
|
1025
1082
|
lifespan="on",
|
agno/os/auth.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
from os import getenv
|
|
1
2
|
from typing import List, Set
|
|
2
3
|
|
|
3
4
|
from fastapi import Depends, HTTPException, Request
|
|
@@ -10,18 +11,43 @@ from agno.os.settings import AgnoAPISettings
|
|
|
10
11
|
security = HTTPBearer(auto_error=False)
|
|
11
12
|
|
|
12
13
|
|
|
14
|
+
def _is_jwt_configured() -> bool:
|
|
15
|
+
"""Check if JWT authentication is configured via environment variables.
|
|
16
|
+
|
|
17
|
+
This covers cases where JWT middleware is set up manually (not via authorization=True).
|
|
18
|
+
"""
|
|
19
|
+
return bool(getenv("JWT_VERIFICATION_KEY") or getenv("JWT_JWKS_FILE"))
|
|
20
|
+
|
|
21
|
+
|
|
13
22
|
def get_authentication_dependency(settings: AgnoAPISettings):
|
|
14
23
|
"""
|
|
15
24
|
Create an authentication dependency function for FastAPI routes.
|
|
16
25
|
|
|
26
|
+
This handles security key authentication (OS_SECURITY_KEY).
|
|
27
|
+
When JWT authorization is enabled (via authorization=True, JWT environment variables,
|
|
28
|
+
or manually added JWT middleware), this dependency is skipped as JWT middleware
|
|
29
|
+
handles authentication.
|
|
30
|
+
|
|
17
31
|
Args:
|
|
18
|
-
settings: The API settings containing the security key
|
|
32
|
+
settings: The API settings containing the security key and authorization flag
|
|
19
33
|
|
|
20
34
|
Returns:
|
|
21
35
|
A dependency function that can be used with FastAPI's Depends()
|
|
22
36
|
"""
|
|
23
37
|
|
|
24
|
-
async def auth_dependency(credentials: HTTPAuthorizationCredentials = Depends(security)) -> bool:
|
|
38
|
+
async def auth_dependency(request: Request, credentials: HTTPAuthorizationCredentials = Depends(security)) -> bool:
|
|
39
|
+
# If JWT authorization is enabled via settings (authorization=True on AgentOS)
|
|
40
|
+
if settings and settings.authorization_enabled:
|
|
41
|
+
return True
|
|
42
|
+
|
|
43
|
+
# Check if JWT middleware has already handled authentication
|
|
44
|
+
if getattr(request.state, "authenticated", False):
|
|
45
|
+
return True
|
|
46
|
+
|
|
47
|
+
# Also skip if JWT is configured via environment variables
|
|
48
|
+
if _is_jwt_configured():
|
|
49
|
+
return True
|
|
50
|
+
|
|
25
51
|
# If no security key is set, skip authentication entirely
|
|
26
52
|
if not settings or not settings.os_security_key:
|
|
27
53
|
return True
|
|
@@ -45,13 +71,24 @@ def validate_websocket_token(token: str, settings: AgnoAPISettings) -> bool:
|
|
|
45
71
|
"""
|
|
46
72
|
Validate a bearer token for WebSocket authentication (legacy os_security_key method).
|
|
47
73
|
|
|
74
|
+
When JWT authorization is enabled (via authorization=True or JWT environment variables),
|
|
75
|
+
this validation is skipped as JWT middleware handles authentication.
|
|
76
|
+
|
|
48
77
|
Args:
|
|
49
78
|
token: The bearer token to validate
|
|
50
|
-
settings: The API settings containing the security key
|
|
79
|
+
settings: The API settings containing the security key and authorization flag
|
|
51
80
|
|
|
52
81
|
Returns:
|
|
53
82
|
True if the token is valid or authentication is disabled, False otherwise
|
|
54
83
|
"""
|
|
84
|
+
# If JWT authorization is enabled, skip security key validation
|
|
85
|
+
if settings and settings.authorization_enabled:
|
|
86
|
+
return True
|
|
87
|
+
|
|
88
|
+
# Also skip if JWT is configured via environment variables (manual JWT middleware setup)
|
|
89
|
+
if _is_jwt_configured():
|
|
90
|
+
return True
|
|
91
|
+
|
|
55
92
|
# If no security key is set, skip authentication entirely
|
|
56
93
|
if not settings or not settings.os_security_key:
|
|
57
94
|
return True
|