diff --git a/api/core/agent/agent_app_runner.py b/api/core/agent/agent_app_runner.py index 2e7884eaba..6f8ea7ec98 100644 --- a/api/core/agent/agent_app_runner.py +++ b/api/core/agent/agent_app_runner.py @@ -1,10 +1,10 @@ import logging from collections.abc import Generator from copy import deepcopy -from typing import Any +from typing import Any, cast from core.agent.base_agent_runner import BaseAgentRunner -from core.agent.entities import AgentEntity, AgentLog, AgentResult +from core.agent.entities import AgentEntity, AgentLog, AgentResult, ExecutionContext from core.agent.patterns.strategy_factory import StrategyFactory from core.app.apps.base_app_queue_manager import PublishFrom from core.app.entities.queue_entities import QueueAgentThoughtEvent, QueueMessageEndEvent, QueueMessageFileEvent @@ -25,12 +25,31 @@ from graphon.model_runtime.entities import ( UserPromptMessage, ) from graphon.model_runtime.entities.message_entities import ImagePromptMessageContent, PromptMessageContentUnionTypes +from graphon.model_runtime.entities.model_entities import ModelFeature +from graphon.model_runtime.model_providers.__base.large_language_model import LargeLanguageModel from models.model import Message logger = logging.getLogger(__name__) class AgentAppRunner(BaseAgentRunner): + + @property + def model_features(self) -> list[ModelFeature]: + llm_model = cast(LargeLanguageModel, self.model_instance.model_type_instance) + model_schema = llm_model.get_model_schema(self.model_instance.model_name, self.model_instance.credentials) + if not model_schema: + return [] + return list(model_schema.features or []) + + def build_execution_context(self) -> ExecutionContext: + return ExecutionContext( + user_id=self.user_id, + app_id=self.application_generate_entity.app_config.app_id, + conversation_id=self.conversation.id if self.conversation else None, + message_id=self.message.id if self.message else None, + tenant_id=self.tenant_id, + ) def _create_tool_invoke_hook(self, message: Message): """ Create a tool invoke hook that uses ToolEngine.agent_invoke. diff --git a/api/models/__init__.py b/api/models/__init__.py index fcae07f948..846296d657 100644 --- a/api/models/__init__.py +++ b/api/models/__init__.py @@ -98,6 +98,7 @@ from .trigger import ( TriggerSubscription, WorkflowSchedulePlan, ) +from .comment import WorkflowComment, WorkflowCommentMention, WorkflowCommentReply from .web import PinnedConversation, SavedMessage from .workflow import ( ConversationVariable, @@ -205,6 +206,9 @@ __all__ = [ "UploadFile", "Whitelist", "Workflow", + "WorkflowComment", + "WorkflowCommentMention", + "WorkflowCommentReply", "WorkflowAppLog", "WorkflowAppLogCreatedFrom", "WorkflowArchiveLog", diff --git a/api/services/workflow/entities.py b/api/services/workflow/entities.py index 2af0d1fd90..1709b8a623 100644 --- a/api/services/workflow/entities.py +++ b/api/services/workflow/entities.py @@ -152,6 +152,29 @@ class TriggerLogResponse(BaseModel): model_config = ConfigDict(use_enum_values=True) +class NestedNodeParameterSchema(BaseModel): + """Schema for a single parameter in a nested node.""" + + name: str + type: str = "string" + description: str = "" + + +class NestedNodeGraphRequest(BaseModel): + """Request for generating a nested node graph.""" + + parent_node_id: str + parameter_key: str + context_source: list[str] = Field(default_factory=list) + parameter_schema: NestedNodeParameterSchema + + +class NestedNodeGraphResponse(BaseModel): + """Response containing the generated nested node graph.""" + + graph: dict[str, Any] + + class WorkflowScheduleCFSPlanEntity(BaseModel): """ CFS plan entity. diff --git a/api/tests/unit_tests/core/app/apps/agent_chat/test_agent_chat_app_runner.py b/api/tests/unit_tests/core/app/apps/agent_chat/test_agent_chat_app_runner.py index 93b2f9c531..36b944e3f9 100644 --- a/api/tests/unit_tests/core/app/apps/agent_chat/test_agent_chat_app_runner.py +++ b/api/tests/unit_tests/core/app/apps/agent_chat/test_agent_chat_app_runner.py @@ -196,7 +196,8 @@ class TestAgentChatAppRunnerRun: runner_instance.run.assert_called_once() runner._handle_invoke_result.assert_called_once() - def test_run_invalid_llm_mode_raises(self, runner, mocker): + def test_run_invalid_llm_mode_proceeds(self, runner, mocker): + """With unified AgentAppRunner, invalid LLM mode no longer raises ValueError.""" app_record = mocker.MagicMock(id="app1", tenant_id="tenant") app_config = mocker.MagicMock(app_id="app1", tenant_id="tenant", prompt_template=mocker.MagicMock()) app_config.agent = AgentEntity(provider="p", model="m", strategy=AgentEntity.Strategy.CHAIN_OF_THOUGHT) @@ -239,8 +240,16 @@ class TestAgentChatAppRunnerRun: side_effect=[app_record, conversation, message], ) - with pytest.raises(ValueError): - runner.run(generate_entity, mocker.MagicMock(), conversation, message) + runner_cls = mocker.MagicMock() + mocker.patch("core.app.apps.agent_chat.app_runner.AgentAppRunner", runner_cls) + runner_instance = mocker.MagicMock() + runner_cls.return_value = runner_instance + runner_instance.run.return_value = [] + mocker.patch.object(runner, "_handle_invoke_result") + + runner.run(generate_entity, mocker.MagicMock(), conversation, message) + + runner_instance.run.assert_called_once() def test_run_function_calling_strategy_selected_by_features(self, runner, mocker): app_record = mocker.MagicMock(id="app1", tenant_id="tenant") @@ -366,7 +375,8 @@ class TestAgentChatAppRunnerRun: with pytest.raises(ValueError): runner.run(generate_entity, mocker.MagicMock(), mocker.MagicMock(id="conv"), mocker.MagicMock(id="msg")) - def test_run_invalid_agent_strategy_raises(self, runner, mocker): + def test_run_invalid_agent_strategy_defaults_to_react(self, runner, mocker): + """With StrategyFactory, invalid strategy defaults to ReAct instead of raising ValueError.""" app_record = mocker.MagicMock(id="app1", tenant_id="tenant") app_config = mocker.MagicMock(app_id="app1", tenant_id="tenant", prompt_template=mocker.MagicMock()) app_config.agent = mocker.MagicMock(strategy="invalid", provider="p", model="m") @@ -409,5 +419,13 @@ class TestAgentChatAppRunnerRun: side_effect=[app_record, conversation, message], ) - with pytest.raises(ValueError): - runner.run(generate_entity, mocker.MagicMock(), conversation, message) + runner_cls = mocker.MagicMock() + mocker.patch("core.app.apps.agent_chat.app_runner.AgentAppRunner", runner_cls) + runner_instance = mocker.MagicMock() + runner_cls.return_value = runner_instance + runner_instance.run.return_value = [] + mocker.patch.object(runner, "_handle_invoke_result") + + runner.run(generate_entity, mocker.MagicMock(), conversation, message) + + runner_instance.run.assert_called_once()