fix: fetch memory of LLM node may cause out of flask context (#36253)

This commit is contained in:
非法操作 2026-05-17 00:38:48 +08:00 committed by GitHub
parent e7e6fe8813
commit 41b6f894c0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 43 additions and 7 deletions

View File

@ -6,11 +6,11 @@ from functools import lru_cache
from typing import TYPE_CHECKING, Any, cast, final, override
from sqlalchemy import select
from sqlalchemy.orm import Session
from configs import dify_config
from core.app.entities.app_invoke_entities import DIFY_RUN_CONTEXT_KEY, DifyRunContext
from core.app.llm.model_access import build_dify_model_access, fetch_model_config
from core.db.session_factory import session_factory
from core.helper.code_executor.code_executor import (
CodeExecutionError,
CodeExecutor,
@ -39,7 +39,6 @@ from core.workflow.nodes.agent.plugin_strategy_adapter import (
from core.workflow.nodes.agent.runtime_support import AgentRuntimeSupport
from core.workflow.system_variables import SystemVariableKey, get_system_text, system_variable_selector
from core.workflow.template_rendering import CodeExecutorJinja2TemplateRenderer
from extensions.ext_database import db
from graphon.entities.base_node_data import BaseNodeData
from graphon.entities.graph_config import NodeConfigDict, NodeConfigDictAdapter
from graphon.enums import BuiltinNodeTypes, NodeType
@ -229,10 +228,14 @@ def fetch_memory(
node_data_memory: MemoryConfig | None,
model_instance: ModelInstance,
) -> TokenBufferMemory | None:
"""Build prompt memory for node construction without requiring Flask-local state."""
if not node_data_memory or not conversation_id:
return None
with Session(db.engine, expire_on_commit=False) as session:
# Node construction can happen in graph initialization paths where Flask's
# app context is not active. Use the app-configured session factory instead
# of resolving db.engine through Flask-SQLAlchemy's current_app proxy.
with session_factory.create_session() as session:
stmt = select(Conversation).where(Conversation.app_id == app_id, Conversation.id == conversation_id)
conversation = session.scalar(stmt)
if not conversation:

View File

@ -109,9 +109,8 @@ class TestFetchMemory:
def scalar(self, _stmt):
return None
monkeypatch.setattr(node_factory, "db", SimpleNamespace(engine=sentinel.engine))
monkeypatch.setattr(node_factory, "session_factory", SimpleNamespace(create_session=FakeSession))
monkeypatch.setattr(node_factory, "select", MagicMock(return_value=FakeSelect()))
monkeypatch.setattr(node_factory, "Session", FakeSession)
result = node_factory.fetch_memory(
conversation_id="conversation-id",
@ -144,9 +143,8 @@ class TestFetchMemory:
return conversation
token_buffer_memory = MagicMock(return_value=memory)
monkeypatch.setattr(node_factory, "db", SimpleNamespace(engine=sentinel.engine))
monkeypatch.setattr(node_factory, "session_factory", SimpleNamespace(create_session=FakeSession))
monkeypatch.setattr(node_factory, "select", MagicMock(return_value=FakeSelect()))
monkeypatch.setattr(node_factory, "Session", FakeSession)
monkeypatch.setattr(node_factory, "TokenBufferMemory", token_buffer_memory)
result = node_factory.fetch_memory(
@ -162,6 +160,41 @@ class TestFetchMemory:
model_instance=sentinel.model_instance,
)
def test_uses_configured_session_factory_without_flask_app_context(self, monkeypatch: pytest.MonkeyPatch):
class FakeSelect:
def where(self, *_args):
return self
class FakeSession:
def __enter__(self):
return self
def __exit__(self, *_args):
return False
def scalar(self, _stmt):
return sentinel.conversation
class RaisingDB:
@property
def engine(self):
raise RuntimeError("Working outside of application context.")
token_buffer_memory = MagicMock(return_value=sentinel.memory)
monkeypatch.setattr(node_factory, "db", RaisingDB(), raising=False)
monkeypatch.setattr(node_factory, "session_factory", SimpleNamespace(create_session=FakeSession))
monkeypatch.setattr(node_factory, "select", MagicMock(return_value=FakeSelect()))
monkeypatch.setattr(node_factory, "TokenBufferMemory", token_buffer_memory)
result = node_factory.fetch_memory(
conversation_id="conversation-id",
app_id="app-id",
node_data_memory=object(),
model_instance=sentinel.model_instance,
)
assert result is sentinel.memory
class TestDifyGraphInitContext:
def test_to_graph_init_params_preserves_explicit_values(self):