mirror of
https://github.com/langflow-ai/langflow.git
synced 2026-07-23 17:31:33 +08:00
fix(api/v2): apply request tweaks on the streaming and background paths
The v2 workflows endpoint applied `tweaks` only on mode=sync. The stream and background paths build the graph via the v1 build-vertex loop (`generate_flow_events`), which never received the tweaks, so they were silently dropped. The confusing symptom: a model passed via tweaks surfaced as "A model selection is required", and any per-component override was ignored on non-sync runs. Thread `parsed.tweaks` into `generate_flow_events` and apply them to the built graph via `vertex.update_raw_params`. We do not use the lfx `process_tweaks_on_graph` helper because it only sets `vertex.params`, which does not persist to runtime (the same bug `lfx.base.tools.run_flow._process_tweaks_on_graph` works around). No-tweaks runs are unchanged (guarded by `if tweaks`). Adds a streaming regression test that overrides ChatInput via tweaks and asserts the value drives the run.
This commit is contained in:
@ -8,6 +8,7 @@ from collections.abc import AsyncIterator
|
||||
from fastapi import BackgroundTasks, HTTPException, Response
|
||||
from lfx.graph.graph.base import Graph
|
||||
from lfx.graph.utils import log_vertex_build
|
||||
from lfx.graph.vertex.base import Vertex
|
||||
from lfx.log.logger import logger
|
||||
from lfx.schema.schema import InputValueRequest
|
||||
from sqlmodel import select
|
||||
@ -365,6 +366,7 @@ async def generate_flow_events(
|
||||
source_flow_id: uuid.UUID | None = None,
|
||||
run_id: str | None = None,
|
||||
track_job_status: bool = True,
|
||||
tweaks: dict | None = None,
|
||||
) -> None:
|
||||
"""Generate events for flow building process.
|
||||
|
||||
@ -393,6 +395,22 @@ async def generate_flow_events(
|
||||
async with session_scope() as fresh_session:
|
||||
graph = await create_graph(fresh_session, flow_id_str, flow_name)
|
||||
|
||||
# Apply request tweaks to the built graph. The sync path applies
|
||||
# tweaks before Graph construction; the streaming/background path
|
||||
# builds from the DB (or request data), so tweaks must be applied
|
||||
# to the built graph here or they are silently dropped. We use
|
||||
# ``update_raw_params`` rather than the lfx ``process_tweaks_on_graph``
|
||||
# helper because that helper only sets ``vertex.params`` and does not
|
||||
# persist the override to runtime (mirrors the workaround in
|
||||
# ``lfx.base.tools.run_flow._process_tweaks_on_graph``).
|
||||
if tweaks:
|
||||
for vertex in graph.vertices:
|
||||
if not (isinstance(vertex, Vertex) and isinstance(vertex.id, str)):
|
||||
continue
|
||||
if node_tweaks := tweaks.get(vertex.id):
|
||||
node_tweaks = {k: v for k, v in node_tweaks.items() if k != "code"}
|
||||
vertex.update_raw_params(node_tweaks, overwrite=True)
|
||||
|
||||
graph.set_run_id(build_run_id)
|
||||
first_layer = sort_vertices(graph)
|
||||
|
||||
|
||||
@ -637,6 +637,7 @@ async def _stream_event_frames(
|
||||
flow_name=flow_name,
|
||||
source_flow_id=source_flow_id,
|
||||
track_job_status=track_job_status,
|
||||
tweaks=parsed.tweaks,
|
||||
)
|
||||
except asyncio.CancelledError:
|
||||
raise
|
||||
|
||||
@ -305,6 +305,63 @@ class TestAGUIStreaming:
|
||||
if flow:
|
||||
await session.delete(flow)
|
||||
|
||||
async def test_stream_applies_request_tweaks(
|
||||
self,
|
||||
client: AsyncClient,
|
||||
created_api_key,
|
||||
json_memory_chatbot_no_llm,
|
||||
):
|
||||
"""Request ``tweaks`` must reach the graph on the streaming path.
|
||||
|
||||
Regression: the stream/background path builds the graph via the v1
|
||||
build-vertex loop (``generate_flow_events``) and previously dropped
|
||||
``tweaks`` entirely, so only ``mode=sync`` applied them. Here we
|
||||
override the ChatInput's ``input_value`` via tweaks (with no top-level
|
||||
input to override it) and assert the tweaked text drives the run.
|
||||
Stream and background share ``generate_flow_events``, so this covers
|
||||
both non-sync paths.
|
||||
"""
|
||||
raw = json.loads(json_memory_chatbot_no_llm)
|
||||
flow_data = raw.get("data", raw)
|
||||
chat_input_id = next(n["id"] for n in flow_data["nodes"] if n.get("data", {}).get("type") == "ChatInput")
|
||||
flow_id = uuid4()
|
||||
async with session_scope() as session:
|
||||
flow = Flow(
|
||||
id=flow_id,
|
||||
name="AG-UI Tweaks Flow",
|
||||
data=flow_data,
|
||||
user_id=created_api_key.user_id,
|
||||
)
|
||||
session.add(flow)
|
||||
await session.flush()
|
||||
|
||||
tweaked = "TWEAKED_VIA_TWEAKS_123"
|
||||
try:
|
||||
response = await client.post(
|
||||
"api/v2/workflows",
|
||||
json={
|
||||
"flow_id": str(flow_id),
|
||||
"mode": "stream",
|
||||
"stream_protocol": "agui",
|
||||
# No top-level input_value and no session_id, so the build
|
||||
# loop receives no chat-input override and the tweak is the
|
||||
# only source of the ChatInput value. Without the fix the
|
||||
# flow default is used and the tweaked text never appears.
|
||||
"tweaks": {chat_input_id: {"input_value": tweaked}},
|
||||
},
|
||||
headers={"x-api-key": created_api_key.api_key},
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
body = response.text
|
||||
assert "RUN_ERROR" not in body
|
||||
assert tweaked in body
|
||||
finally:
|
||||
async with session_scope() as session:
|
||||
flow = await session.get(Flow, flow_id)
|
||||
if flow:
|
||||
await session.delete(flow)
|
||||
|
||||
|
||||
class TestAGUISyncExecution:
|
||||
"""mode=sync runs the flow inline and folds outputs into the response."""
|
||||
|
||||
Reference in New Issue
Block a user