Skip to content

Commit ab0fcd7

Browse files
authored
Merge branch 'release-1.12.0' into workflow-api-prod
2 parents 2a07971 + dea1cfb commit ab0fcd7

193 files changed

Lines changed: 15045 additions & 2896 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.secrets.baseline

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1263,7 +1263,7 @@
12631263
"filename": "src/backend/tests/unit/agentic/services/test_assistant_service_run_intent.py",
12641264
"hashed_secret": "e9a5f12a8ecbb3eb46eca5096b5c52aa5e7c9fdd",
12651265
"is_verified": false,
1266-
"line_number": 369,
1266+
"line_number": 386,
12671267
"is_secret": false
12681268
}
12691269
],
@@ -1337,7 +1337,7 @@
13371337
"filename": "src/backend/tests/unit/agentic/services/test_provider_service.py",
13381338
"hashed_secret": "c92b9809dacd9240dc85e86da6388e9b1bfc8a7d",
13391339
"is_verified": false,
1340-
"line_number": 118,
1340+
"line_number": 126,
13411341
"is_secret": false
13421342
}
13431343
],
@@ -6589,7 +6589,7 @@
65896589
"filename": "src/lfx/src/lfx/base/models/unified_models/model_catalog.py",
65906590
"hashed_secret": "665b1e3851eefefa3fb878654292f16597d25155",
65916591
"is_verified": false,
6592-
"line_number": 498,
6592+
"line_number": 502,
65936593
"is_secret": false
65946594
}
65956595
],

BUNDLE_API.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -511,6 +511,16 @@ the deserialize half is covered by
511511
exposes an immutable generation-tagged snapshot plus an eager catalog
512512
validation hook for deployment readiness. Existing provider manifests are
513513
unaffected and `BUNDLE_API_VERSION` remains `1`.
514+
- **Provider identity resolution and policy snapshots (additive).**
515+
`resolve_provider_id()` is the canonical resolver for registered names,
516+
display names, aliases, stable IDs, and deterministic legacy fallbacks.
517+
Model-provider policy services now expose cached synchronous `resolve()`,
518+
async `aresolve()`, single-provider `is_allowed()`, and `invalidate()`
519+
hooks while preserving the existing immutable snapshot and allow-all OSS
520+
behavior. The process-local snapshot cache has a bounded TTL, and async
521+
implementations can override `aget_allowed_provider_ids()` without
522+
bypassing it. Existing synchronous policy subclasses, including subclasses
523+
that did not call `super().__init__()`, remain source-compatible.
514524
- **New typed error codes (additive): `provider-invalid`, `provider-skipped`.**
515525
A malformed provider spec surfaces `provider-invalid`; a provider whose name
516526
collides with a built-in or already-loaded provider surfaces

Makefile

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
.PHONY: all init format_backend format lint build run_backend dev help tests coverage clean_python_cache clean_npm_cache clean_frontend_build clean_all run_clic load_test_setup load_test_setup_basic load_test_list_flows load_test_run load_test_langflow_quick load_test_stress load_test_example load_test_clean load_test_remote_setup load_test_remote_run load_test_help docs docs_build docs_install api_examples_local api_examples_local_syntax
1+
.PHONY: all init format_backend format lint build backend backend_base install_backend setup_env run_backend dev help tests coverage clean_python_cache clean_npm_cache clean_frontend_build clean_all run_clic load_test_setup load_test_setup_basic load_test_list_flows load_test_run load_test_langflow_quick load_test_stress load_test_example load_test_clean load_test_remote_setup load_test_remote_run load_test_help docs docs_build docs_install api_examples_local api_examples_local_syntax
22

33
# Configurations
44
VERSION=$(shell grep "^version" pyproject.toml | sed 's/.*\"\(.*\)\"$$/\1/')
@@ -23,6 +23,7 @@ workers ?= 1
2323
async ?= true
2424
lf ?= false
2525
ff ?= true
26+
UV_RUN_ARGS ?=
2627
all: help
2728

2829
######################
@@ -285,12 +286,18 @@ setup_env: ## set up the environment
285286

286287

287288

289+
backend_base: ## run the backend with only langflow-base installed
290+
@UV_PROJECT_ENVIRONMENT="$(CURDIR)/src/backend/base/.venv" \
291+
$(MAKE) backend \
292+
EXTRA_ARGS="$(strip --package langflow-base $(EXTRA_ARGS))" \
293+
UV_RUN_ARGS="$(strip --package langflow-base $(UV_RUN_ARGS))"
294+
288295

289296
backend: setup_env install_backend ## run the backend in development mode
290297
@-kill -9 $$(lsof -t -i:7860) || true
291298
ifdef login
292299
@echo "Running backend autologin is $(login)";
293-
LANGFLOW_AUTO_LOGIN=$(login) uv run uvicorn \
300+
LANGFLOW_AUTO_LOGIN=$(login) uv run$(if $(strip $(UV_RUN_ARGS)), $(strip $(UV_RUN_ARGS))) uvicorn \
294301
--factory langflow.main:create_app \
295302
--host 0.0.0.0 \
296303
--port $(port) \
@@ -300,7 +307,7 @@ ifdef login
300307
$(if $(workers),--workers $(workers),)
301308
else
302309
@echo "Running backend respecting the $(env) file";
303-
uv run uvicorn \
310+
uv run$(if $(strip $(UV_RUN_ARGS)), $(strip $(UV_RUN_ARGS))) uvicorn \
304311
--factory langflow.main:create_app \
305312
--host 0.0.0.0 \
306313
--port $(port) \
@@ -900,6 +907,7 @@ help_backend: ## show backend-specific commands
900907
@echo ''
901908
@echo "$(GREEN)Development:$(NC)"
902909
@echo " $(GREEN)make backend$(NC) - Run backend in development mode"
910+
@echo " $(GREEN)make backend_base$(NC) - Run backend with only langflow-base installed"
903911
@echo " $(GREEN)make run_cli$(NC) - Run Langflow CLI"
904912
@echo " $(GREEN)make run_clic$(NC) - Run CLI with fresh frontend build"
905913
@echo " $(GREEN)make run_cli_debug$(NC) - Run CLI in debug mode"

docs/docs/Deployment/deployment-block-custom-components.mdx

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,23 @@ LANGFLOW_CUSTOM_COMPONENT_ADMIN_ONLY=true
3131

3232
When set to `true`, non-superusers can still view and use custom components in flows, but they cannot create new custom components or edit custom component code.
3333

34+
## Interaction with catalog governance
35+
36+
Catalog governance takes precedence over the custom-code settings on the custom-component create, update, and code-validation endpoints. A catalog block has no superuser bypass.
37+
38+
| Submitted source | Catalog policy | Custom components | Admin-only mode | Result |
39+
|---|---|---|---|---|
40+
| Known server template | Blocked | Either | Either | Denied for every user |
41+
| Known server template | Allowed | Disabled | Either | Allowed using the trusted server source |
42+
| Known server template | Allowed | Enabled | Non-superuser | Allowed using the trusted server source |
43+
| Unknown custom source | Not blocked | Disabled | Either | Denied |
44+
| Unknown custom source | Not blocked | Enabled | Non-superuser | Denied |
45+
| Unknown custom source | Not blocked | Enabled | Superuser or admin-only disabled | Allowed |
46+
47+
For `POST /api/v1/validate/code`, this policy runs before the source is parsed or imports are inspected. In a restricted custom-code mode, known templates are validated from the trusted server copy, while unknown source follows the same disabled or admin-only rule as the component editor. On custom-component create and update, known blocked template source is rejected before the component is built, and the resolved component type is checked again before request-supplied frontend updates are applied.
48+
49+
An active catalog policy fails closed with a temporary `503` response while server template identities are still initializing. An empty catalog policy preserves the default behavior and does not require template identity lookups.
50+
3451
## Configure a custom component allow-list
3552

3653
`LANGFLOW_ALLOW_CUSTOM_COMPONENTS` works together with optional paths that define which component templates the server loads, and which code hashes are trusted.
@@ -62,4 +79,4 @@ Default is `true`, which preserves existing behavior. The setting has no effect
6279
For more information, see:
6380

6481
* [Environment variables](/environment-variables#visual-editor-and-playground-behavior)
65-
* [Security](/security)
82+
* [Security](/security)

src/backend/base/langflow/agentic/flows/flow_builder_assistant.py

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -323,13 +323,16 @@
323323
running — otherwise the run fails with "No model selected".
324324
325325
Pick the model in this STRICT priority order:
326-
1. **The model the user EXPLICITLY named WINS — always.** If the user asked
326+
1. **The model the user EXPLICITLY named WINS unless a runtime
327+
`[Model provider policy ...]` notice says its provider is unavailable.**
328+
If the user asked
327329
for a specific model ("use GPT-5.4", "use the OpenAI 5.4 model", "switch to
328330
claude-sonnet-4-5", "troque para gemini-2.5-pro"), use THAT model — never
329331
substitute a different version or the `preferred` model for it, even if the
330-
requested model is not in the `[Available language models ...]` block and
331-
even if a "preferred" model is offered. (e.g. user said "5.4" / "gpt-5.4"
332-
and preferred is "gpt-5.5" → you MUST set the 5.4 model, NOT gpt-5.5.)
332+
exact requested model is not listed and even if a "preferred" model is
333+
offered. (e.g. user said "5.4" / "gpt-5.4" and preferred is "gpt-5.5"
334+
→ you MUST set the 5.4 model, NOT gpt-5.5.) If a policy notice omits or
335+
rejects the provider, do not discover, configure, or run it.
333336
BUT set the **canonical model id** — the EXACT id as it appears in the
334337
provider catalog / `describe_component` / the `[Available language models]`
335338
block, NOT the user's loose wording. Provider model ids are CASE-SENSITIVE
@@ -343,19 +346,18 @@
343346
`preferred`; if there is none, pick ANY provider from "providers with
344347
credentials configured" (provider-agnostic — do NOT assume OpenAI; use
345348
whatever the user actually has keys for, e.g. Anthropic, Google, Groq).
346-
3. Only if no such block is present at all may you fall back to
347-
`provider="OpenAI", name="gpt-4o-mini"`.
349+
3. Only if neither an Available-language-model block nor a restrictive
350+
Model-provider-policy notice is present may you preserve the historical
351+
fallback `provider="OpenAI", name="gpt-4o-mini"`.
348352
349353
`configure_component(component_id="Agent-...", params='{"model": [{"provider": "<provider>", "name": "<name>"}]}')`.
350354
Never run a flow whose Agent has no model. NEVER claim in your reply that you
351355
used a model different from the one you actually set on the canvas — report the
352356
EXACT model you configured.
353357
354-
Common providers and example model names:
355-
- `OpenAI` — `gpt-4o`, `gpt-4o-mini`, `gpt-5`, `o1-mini`
356-
- `Anthropic` — `claude-sonnet-4-5-20250929`, `claude-haiku-4-5`
357-
- `Google Generative AI` — `gemini-2.5-flash`, `gemini-2.5-pro`
358-
- `Groq`, `Azure OpenAI`, `Ollama`, `IBM WatsonX`
358+
Provider and model names are deployment-specific. Treat the runtime
359+
`[Available language models ...]` block as the authority; never infer that a
360+
provider is available from these instructions or from an example below.
359361
360362
Add a SEPARATE model component (OpenAIModel etc.) only when the user
361363
EXPLICITLY says "add an OpenAIModel component" / "create a model node" — never

src/backend/base/langflow/agentic/mcp/server.py

Lines changed: 49 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55

66
import asyncio
77
import os
8+
from collections.abc import AsyncIterator
9+
from contextlib import asynccontextmanager
810
from typing import Any
911
from uuid import UUID
1012

@@ -44,6 +46,7 @@
4446
from langflow.services.deps import get_db_service, get_settings_service, session_scope
4547

4648
_services_initialized = False
49+
_policy_refresh_started = False
4750
_services_init_lock = asyncio.Lock()
4851

4952

@@ -54,21 +57,58 @@ async def _ensure_services() -> None:
5457
``initialize_services()`` (its startup/migration side effects are not
5558
safe to run twice).
5659
"""
57-
global _services_initialized # noqa: PLW0603
58-
if _services_initialized:
60+
global _policy_refresh_started, _services_initialized # noqa: PLW0603
61+
if _services_initialized and _policy_refresh_started:
5962
return
6063
async with _services_init_lock:
61-
if _services_initialized:
62-
return
63-
get_db_service()
64-
from langflow.services.utils import initialize_services
64+
if not _services_initialized:
65+
get_db_service()
66+
from langflow.services.utils import initialize_services
67+
68+
await initialize_services()
69+
# Mark service initialization immediately after it succeeds. If
70+
# refresh startup fails, a later MCP call retries only that worker
71+
# instead of repeating database migrations and registry setup.
72+
_services_initialized = True
73+
74+
if not _policy_refresh_started:
75+
from langflow.services.task.model_provider_policy_refresh import (
76+
model_provider_policy_refresh_worker,
77+
)
78+
79+
# Standalone stdio servers do not enter the FastAPI lifespan, but
80+
# they are long-lived model consumers and must converge after an
81+
# admin changes the install-wide provider ceiling.
82+
await model_provider_policy_refresh_worker.start()
83+
_policy_refresh_started = True
84+
85+
86+
async def _stop_policy_refresh() -> None:
87+
"""Stop the standalone refresh worker during MCP server shutdown."""
88+
global _policy_refresh_started # noqa: PLW0603
89+
if not _policy_refresh_started:
90+
return
91+
from langflow.services.task.model_provider_policy_refresh import (
92+
model_provider_policy_refresh_worker,
93+
)
94+
95+
try:
96+
await model_provider_policy_refresh_worker.stop()
97+
finally:
98+
_policy_refresh_started = False
99+
65100

66-
await initialize_services()
67-
_services_initialized = True
101+
@asynccontextmanager
102+
async def _service_lifespan(_server: FastMCP) -> AsyncIterator[dict]:
103+
"""Pair lazy standalone service startup with refresh-worker teardown."""
104+
try:
105+
yield {}
106+
finally:
107+
await _stop_policy_refresh()
68108

69109

70110
# Initialize FastMCP server
71-
mcp = FastMCP("langflow-agentic")
111+
mcp = FastMCP("langflow-agentic", lifespan=_service_lifespan)
72112

73113
DEFAULT_TEMPLATE_FIELDS = ["id", "name", "description", "tags", "endpoint_name", "icon"]
74114
DEFAULT_COMPONENT_FIELDS = ["name", "type", "display_name", "description"]

src/backend/base/langflow/agentic/services/agent_run_context.py

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,20 +18,40 @@ class AgentRunModel(TypedDict):
1818
provider: str | None
1919
model_name: str | None
2020
api_key_var: str | None
21+
allow_configuration: bool
22+
23+
24+
class RequestedAgentModel(TypedDict):
25+
provider: str
26+
model_name: str
27+
api_key_var: str | None
2128

2229

2330
_model_var: contextvars.ContextVar[AgentRunModel | None] = contextvars.ContextVar("agentic_run_model", default=None)
2431

25-
_requested_model_var: contextvars.ContextVar[AgentRunModel | None] = contextvars.ContextVar(
32+
_requested_model_var: contextvars.ContextVar[RequestedAgentModel | None] = contextvars.ContextVar(
2633
"agentic_requested_model", default=None
2734
)
2835

2936
_iterations_var: contextvars.ContextVar[int | None] = contextvars.ContextVar("agentic_run_iterations", default=None)
3037

3138

32-
def set_agent_run_model(provider: str | None, model_name: str | None, api_key_var: str | None) -> None:
33-
"""Bind the request's provider/model/api-key to the current context."""
34-
_model_var.set({"provider": provider, "model_name": model_name, "api_key_var": api_key_var})
39+
def set_agent_run_model(
40+
provider: str | None,
41+
model_name: str | None,
42+
api_key_var: str | None,
43+
*,
44+
allow_configuration: bool = True,
45+
) -> None:
46+
"""Bind the request model and whether it may be injected into the canvas."""
47+
_model_var.set(
48+
{
49+
"provider": provider,
50+
"model_name": model_name,
51+
"api_key_var": api_key_var,
52+
"allow_configuration": allow_configuration,
53+
}
54+
)
3555

3656

3757
def current_agent_run_model() -> AgentRunModel | None:
@@ -83,7 +103,7 @@ def set_requested_agent_model(provider: str | None, model_name: str | None, api_
83103
_requested_model_var.set(None)
84104

85105

86-
def current_requested_agent_model() -> AgentRunModel | None:
106+
def current_requested_agent_model() -> RequestedAgentModel | None:
87107
"""Return the user's explicitly-requested model (or ``None`` when unset)."""
88108
return _requested_model_var.get()
89109

0 commit comments

Comments
 (0)