diff --git a/VERSION b/VERSION index ffd7385f..828febb2 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -26.2.0.dev7 +26.4.0.dev0 diff --git a/docs/pyagentspec/source/_components/all_components.json b/docs/pyagentspec/source/_components/all_components.json index a0ce07bd..c4b35a6f 100644 --- a/docs/pyagentspec/source/_components/all_components.json +++ b/docs/pyagentspec/source/_components/all_components.json @@ -103,6 +103,10 @@ "path": "pyagentspec.llms.llmconfig.LlmConfig", "name": "pyagentspec.llms.LlmConfig" }, + { + "path": "pyagentspec.llms.dbmsvectorchainllmconfig.DbmsVectorChainLlmConfig", + "name": "pyagentspec.llms.DbmsVectorChainLlmConfig" + }, { "path": "pyagentspec.llms.llmgenerationconfig.LlmGenerationConfig", "name": "pyagentspec.llms.LlmGenerationConfig" diff --git a/docs/pyagentspec/source/_components/llm_config_tabs.rst b/docs/pyagentspec/source/_components/llm_config_tabs.rst index 7053601c..3eea6a26 100644 --- a/docs/pyagentspec/source/_components/llm_config_tabs.rst +++ b/docs/pyagentspec/source/_components/llm_config_tabs.rst @@ -16,6 +16,20 @@ model_id="model-id", ) + .. tab:: DBMS Vector Chain + + .. code-block:: python + + from pyagentspec.llms import DbmsVectorChainLlmConfig + + llm_config = DbmsVectorChainLlmConfig( + name="Database OpenAI", + model_id="gpt-4o", + provider="openai", + url="https://api.openai.com/v1/chat/completions", + credential_name="MY_OPENAI_CREDENTIAL", + ) + .. tab:: OCI GenAI .. code-block:: python diff --git a/docs/pyagentspec/source/agentspec/index.rst b/docs/pyagentspec/source/agentspec/index.rst index 40192be6..628ed2f1 100644 --- a/docs/pyagentspec/source/agentspec/index.rst +++ b/docs/pyagentspec/source/agentspec/index.rst @@ -25,7 +25,8 @@ You can download the Agent Spec technical report at the following :download:`lin :maxdepth: 2 Introduction, motivation & vision - Language specification (v26.1.2 latest) + Language specification (v26.3.0 latest) + Language specification (v26.1.2) Language specification (v26.1.0) Language specification (v25.4.1) Positioning in the agentic ecosystem @@ -39,7 +40,8 @@ You can download the Agent Spec technical report at the following :download:`lin Introduction, motivation & vision Language specification (under development) - Language specification (v26.1.2 latest release) + Language specification (v26.3.0 latest release) + Language specification (v26.1.2) Language specification (v26.1.0) Language specification (v25.4.1) Positioning in the agentic ecosystem diff --git a/docs/pyagentspec/source/agentspec/json_spec/agentspec_json_spec_26_2_0.json b/docs/pyagentspec/source/agentspec/json_spec/agentspec_json_spec_26_2_0.json index 1b3518fd..d390712d 100644 --- a/docs/pyagentspec/source/agentspec/json_spec/agentspec_json_spec_26_2_0.json +++ b/docs/pyagentspec/source/agentspec/json_spec/agentspec_json_spec_26_2_0.json @@ -214,6 +214,16 @@ } ] }, + "DbmsVectorChainLlmConfig": { + "anyOf": [ + { + "$ref": "#/$defs/BaseDbmsVectorChainLlmConfig" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, "EndNode": { "anyOf": [ { @@ -2638,6 +2648,146 @@ ], "x-abstract-component": true }, + "BaseDbmsVectorChainLlmConfig": { + "additionalProperties": false, + "description": "Configure an LLM executed by Oracle Database through DBMS_VECTOR_CHAIN.", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "model_id": { + "minLength": 1, + "title": "Model Id", + "type": "string" + }, + "provider": { + "minLength": 1, + "title": "Provider", + "type": "string" + }, + "url": { + "minLength": 1, + "title": "Url", + "type": "string" + }, + "default_generation_parameters": { + "anyOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/LlmGenerationConfig" + } + ], + "default": null + }, + "retry_policy": { + "anyOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/RetryPolicy" + } + ], + "default": null + }, + "host": { + "anyOf": [ + { + "const": "local", + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Host" + }, + "credential_name": { + "anyOf": [ + { + "minLength": 1, + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Credential Name" + }, + "transfer_timeout": { + "anyOf": [ + { + "minimum": 0, + "type": "integer" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Transfer Timeout" + }, + "connection_config": { + "anyOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/OracleDatabaseConnectionConfig" + } + ], + "default": null + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "DbmsVectorChainLlmConfig" + } + }, + "required": [ + "model_id", + "name", + "provider", + "url" + ], + "title": "DbmsVectorChainLlmConfig", + "type": "object", + "x-abstract-component": false + }, "BaseEndNode": { "additionalProperties": false, "description": "End nodes denote the end of the execution of a flow.\n\nThere might be several end nodes in a flow, in which case the executor of the flow\nshould be able to track which one was reached and pass that back to the caller.\n\n- **Inputs**\n The list of inputs of the step. If both input and output properties are specified they\n must be an exact match\n\n If None is given, ``pyagentspec`` copies the outputs provided, if any. Otherwise, no input is exposed.\n- **Outputs**\n The list of outputs that should be exposed by the flow. If both input and output properties\n are specified they must be an exact match\n\n If None is given, ``pyagentspec`` copies the inputs provided, if any. Otherwise, no output is exposed.\n- **Branches**\n None.\n\nExamples\n--------\n>>> from pyagentspec.agent import Agent\n>>> from pyagentspec.flows.edges import ControlFlowEdge, DataFlowEdge\n>>> from pyagentspec.flows.flow import Flow\n>>> from pyagentspec.flows.nodes import AgentNode, BranchingNode, StartNode, EndNode\n>>> from pyagentspec.property import Property\n>>> languages_to_branch_name = {\n... \"english\": \"ENGLISH\",\n... \"spanish\": \"SPANISH\",\n... \"italian\": \"ITALIAN\",\n... }\n>>> language_property = Property(\n... json_schema={\"title\": \"language\", \"type\": \"string\"}\n... )\n>>> agent = Agent(\n... name=\"Language detector agent\",\n... llm_config=llm_config,\n... system_prompt=(\n... \"Your task is to understand the language spoken by the user.\"\n... \"Please output only the language in lowercase and submit.\"\n... ),\n... outputs=[language_property],\n... )\n>>> start_node = StartNode(name=\"start\")\n>>> english_end_node = EndNode(\n... name=\"english end\", branch_name=languages_to_branch_name[\"english\"]\n... )\n>>> spanish_end_node = EndNode(\n... name=\"spanish end\", branch_name=languages_to_branch_name[\"spanish\"]\n... )\n>>> italian_end_node = EndNode(\n... name=\"italian end\", branch_name=languages_to_branch_name[\"italian\"]\n... )\n>>> unknown_end_node = EndNode(name=\"unknown language end\", branch_name=\"unknown\")\n>>> branching_node = BranchingNode(\n... name=\"language check\",\n... mapping=languages_to_branch_name,\n... inputs=[language_property]\n... )\n>>> agent_node = AgentNode(\n... name=\"User input agent node\",\n... agent=agent,\n... )\n>>> assistant = Flow(\n... name=\"Check access flow\",\n... start_node=start_node,\n... nodes=[\n... start_node,\n... agent_node,\n... branching_node,\n... english_end_node,\n... spanish_end_node,\n... italian_end_node,\n... unknown_end_node,\n... ],\n... control_flow_connections=[\n... ControlFlowEdge(\n... name=\"start_to_agent\", from_node=start_node, to_node=agent_node\n... ),\n... ControlFlowEdge(\n... name=\"agent_to_branching\", from_node=agent_node, to_node=branching_node\n... ),\n... ControlFlowEdge(\n... name=\"branching_to_english_end\",\n... from_node=branching_node,\n... from_branch=languages_to_branch_name[\"english\"],\n... to_node=english_end_node,\n... ),\n... ControlFlowEdge(\n... name=\"branching_to_spanish_end\",\n... from_node=branching_node,\n... from_branch=languages_to_branch_name[\"spanish\"],\n... to_node=spanish_end_node,\n... ),\n... ControlFlowEdge(\n... name=\"branching_to_italian_end\",\n... from_node=branching_node,\n... from_branch=languages_to_branch_name[\"italian\"],\n... to_node=italian_end_node,\n... ),\n... ControlFlowEdge(\n... name=\"branching_to_unknown_end\",\n... from_node=branching_node,\n... from_branch=BranchingNode.DEFAULT_BRANCH,\n... to_node=unknown_end_node,\n... ),\n... ],\n... data_flow_connections=[\n... DataFlowEdge(\n... name=\"language_edge\",\n... source_node=agent_node,\n... source_output=\"language\",\n... destination_node=branching_node,\n... destination_input=\"language\",\n... ),\n... ],\n... )", @@ -3386,6 +3536,9 @@ }, "BaseLlmConfig": { "anyOf": [ + { + "$ref": "#/$defs/DbmsVectorChainLlmConfig" + }, { "$ref": "#/$defs/GeminiConfig" }, @@ -7732,6 +7885,9 @@ { "$ref": "#/$defs/BaseDatastore" }, + { + "$ref": "#/$defs/BaseDbmsVectorChainLlmConfig" + }, { "$ref": "#/$defs/BaseEndNode" }, @@ -8192,6 +8348,21 @@ } } }, + "VersionedDbmsVectorChainLlmConfig": { + "anyOf": [ + { + "$ref": "#/$defs/BaseDbmsVectorChainLlmConfig" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, "VersionedEndNode": { "anyOf": [ { @@ -9077,6 +9248,9 @@ { "$ref": "#/$defs/VersionedDatastore" }, + { + "$ref": "#/$defs/VersionedDbmsVectorChainLlmConfig" + }, { "$ref": "#/$defs/VersionedEndNode" }, diff --git a/docs/pyagentspec/source/agentspec/json_spec/agentspec_json_spec_26_3_0.json b/docs/pyagentspec/source/agentspec/json_spec/agentspec_json_spec_26_3_0.json new file mode 100644 index 00000000..4d566690 --- /dev/null +++ b/docs/pyagentspec/source/agentspec/json_spec/agentspec_json_spec_26_3_0.json @@ -0,0 +1,9417 @@ +{ + "$defs": { + "A2AAgent": { + "anyOf": [ + { + "$ref": "#/$defs/BaseA2AAgent" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "A2AConnectionConfig": { + "anyOf": [ + { + "$ref": "#/$defs/BaseA2AConnectionConfig" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "A2ASessionParameters": { + "description": "Class to specify parameters of the A2A session.", + "properties": { + "timeout": { + "default": 60.0, + "title": "Timeout", + "type": "number" + }, + "poll_interval": { + "default": 2.0, + "title": "Poll Interval", + "type": "number" + }, + "max_retries": { + "default": 5, + "title": "Max Retries", + "type": "integer" + } + }, + "title": "A2ASessionParameters", + "type": "object" + }, + "Agent": { + "anyOf": [ + { + "$ref": "#/$defs/BaseAgent" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "AgentNode": { + "anyOf": [ + { + "$ref": "#/$defs/BaseAgentNode" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "AgentSpecVersionEnum": { + "description": "An Enumeration for different versions of Agent Spec.", + "enum": [ + "25.4.1", + "25.4.2", + "26.1.0", + "26.1.2", + "26.3.0" + ], + "title": "AgentSpecVersionEnum", + "type": "string" + }, + "AgentSpecializationParameters": { + "anyOf": [ + { + "$ref": "#/$defs/BaseAgentSpecializationParameters" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "AgenticComponent": { + "anyOf": [ + { + "$ref": "#/$defs/BaseAgenticComponent" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "ApiNode": { + "anyOf": [ + { + "$ref": "#/$defs/BaseApiNode" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "AuthConfig": { + "anyOf": [ + { + "$ref": "#/$defs/BaseAuthConfig" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "BranchingNode": { + "anyOf": [ + { + "$ref": "#/$defs/BaseBranchingNode" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "BuiltinTool": { + "anyOf": [ + { + "$ref": "#/$defs/BaseBuiltinTool" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "CatchExceptionNode": { + "anyOf": [ + { + "$ref": "#/$defs/BaseCatchExceptionNode" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "ClientTool": { + "anyOf": [ + { + "$ref": "#/$defs/BaseClientTool" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "ClientTransport": { + "anyOf": [ + { + "$ref": "#/$defs/BaseClientTransport" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "ComponentWithIO": { + "anyOf": [ + { + "$ref": "#/$defs/BaseComponentWithIO" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "ControlFlowEdge": { + "anyOf": [ + { + "$ref": "#/$defs/BaseControlFlowEdge" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "ConversationSummarizationTransform": { + "anyOf": [ + { + "$ref": "#/$defs/BaseConversationSummarizationTransform" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "DataFlowEdge": { + "anyOf": [ + { + "$ref": "#/$defs/BaseDataFlowEdge" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "Datastore": { + "anyOf": [ + { + "$ref": "#/$defs/BaseDatastore" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "DbmsVectorChainLlmConfig": { + "anyOf": [ + { + "$ref": "#/$defs/BaseDbmsVectorChainLlmConfig" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "EndNode": { + "anyOf": [ + { + "$ref": "#/$defs/BaseEndNode" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "Flow": { + "anyOf": [ + { + "$ref": "#/$defs/BaseFlow" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "FlowNode": { + "anyOf": [ + { + "$ref": "#/$defs/BaseFlowNode" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "GeminiAIStudioAuthConfig": { + "anyOf": [ + { + "$ref": "#/$defs/BaseGeminiAIStudioAuthConfig" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "GeminiAuthConfig": { + "anyOf": [ + { + "$ref": "#/$defs/BaseGeminiAuthConfig" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "GeminiConfig": { + "anyOf": [ + { + "$ref": "#/$defs/BaseGeminiConfig" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "GeminiVertexAIAuthConfig": { + "anyOf": [ + { + "$ref": "#/$defs/BaseGeminiVertexAIAuthConfig" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "HandoffMode": { + "description": "Controls how agents in a Swarm may delegate work to one another.\n\nThis setting determines whether an agent is equipped with:\n\n * *send_message* \u2014 a tool for asking another agent to perform a sub-task and reply back.\n\n * *handoff_conversation* \u2014 a tool for transferring the full user\u2013agent conversation to another agent.\n\nDepending on the selected mode, agents have different capabilities for delegation and collaboration.", + "enum": [ + "always", + "never", + "optional" + ], + "title": "HandoffMode", + "type": "string" + }, + "InMemoryCollectionDatastore": { + "anyOf": [ + { + "$ref": "#/$defs/BaseInMemoryCollectionDatastore" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "InputMessageNode": { + "anyOf": [ + { + "$ref": "#/$defs/BaseInputMessageNode" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "LlmConfig": { + "anyOf": [ + { + "$ref": "#/$defs/BaseLlmConfig" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "LlmGenerationConfig": { + "additionalProperties": true, + "description": "A configuration object defining LLM generation parameters.\n\nParameters include number of tokens, sampling parameters, etc.", + "properties": { + "max_tokens": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Max Tokens" + }, + "temperature": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Temperature" + }, + "top_p": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Top P" + } + }, + "title": "LlmGenerationConfig", + "type": "object" + }, + "LlmNode": { + "anyOf": [ + { + "$ref": "#/$defs/BaseLlmNode" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "MCPTool": { + "anyOf": [ + { + "$ref": "#/$defs/BaseMCPTool" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "MCPToolBox": { + "anyOf": [ + { + "$ref": "#/$defs/BaseMCPToolBox" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "MCPToolSpec": { + "anyOf": [ + { + "$ref": "#/$defs/BaseMCPToolSpec" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "MTlsOracleDatabaseConnectionConfig": { + "anyOf": [ + { + "$ref": "#/$defs/BaseMTlsOracleDatabaseConnectionConfig" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "ManagerWorkers": { + "anyOf": [ + { + "$ref": "#/$defs/BaseManagerWorkers" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "MapNode": { + "anyOf": [ + { + "$ref": "#/$defs/BaseMapNode" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "MessageSummarizationTransform": { + "anyOf": [ + { + "$ref": "#/$defs/BaseMessageSummarizationTransform" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "MessageTransform": { + "anyOf": [ + { + "$ref": "#/$defs/BaseMessageTransform" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "ModelProvider": { + "description": "Provider of the model. It is used to ensure the requests to this model respect\nthe format expected by the provider.", + "enum": [ + "COHERE", + "GROK", + "META", + "OTHER", + "XAI" + ], + "title": "ModelProvider", + "type": "string" + }, + "Node": { + "anyOf": [ + { + "$ref": "#/$defs/BaseNode" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "OAuthClientConfig": { + "anyOf": [ + { + "$ref": "#/$defs/BaseOAuthClientConfig" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "OAuthConfig": { + "anyOf": [ + { + "$ref": "#/$defs/BaseOAuthConfig" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "OAuthEndpoints": { + "description": "Explicit OAuth endpoint configuration.\n\nUse this component when endpoint discovery is not available or not desired.\nThis groups the relevant endpoints required to execute OAuth authorization\ncode flows and token refresh.", + "properties": { + "authorization_endpoint": { + "title": "Authorization Endpoint", + "type": "string" + }, + "token_endpoint": { + "title": "Token Endpoint", + "type": "string" + }, + "refresh_endpoint": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Refresh Endpoint" + }, + "revocation_endpoint": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Revocation Endpoint" + }, + "userinfo_endpoint": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Userinfo Endpoint" + } + }, + "required": [ + "authorization_endpoint", + "token_endpoint" + ], + "title": "OAuthEndpoints", + "type": "object" + }, + "OciAPIType": { + "description": "Enumeration of API Types.", + "enum": [ + "oci", + "openai_chat_completions", + "openai_responses" + ], + "title": "OciAPIType", + "type": "string" + }, + "OciAgent": { + "anyOf": [ + { + "$ref": "#/$defs/BaseOciAgent" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "OciClientConfig": { + "anyOf": [ + { + "$ref": "#/$defs/BaseOciClientConfig" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "OciClientConfigWithApiKey": { + "anyOf": [ + { + "$ref": "#/$defs/BaseOciClientConfigWithApiKey" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "OciClientConfigWithInstancePrincipal": { + "anyOf": [ + { + "$ref": "#/$defs/BaseOciClientConfigWithInstancePrincipal" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "OciClientConfigWithResourcePrincipal": { + "anyOf": [ + { + "$ref": "#/$defs/BaseOciClientConfigWithResourcePrincipal" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "OciClientConfigWithSecurityToken": { + "anyOf": [ + { + "$ref": "#/$defs/BaseOciClientConfigWithSecurityToken" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "OciGenAiConfig": { + "anyOf": [ + { + "$ref": "#/$defs/BaseOciGenAiConfig" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "OllamaConfig": { + "anyOf": [ + { + "$ref": "#/$defs/BaseOllamaConfig" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "OpenAIAPIType": { + "description": "Enumeration of OpenAI API Types.\n\nchat completions: Chat Completions API\nresponses: Responses API", + "enum": [ + "chat_completions", + "responses" + ], + "title": "OpenAIAPIType", + "type": "string" + }, + "OpenAiCompatibleConfig": { + "anyOf": [ + { + "$ref": "#/$defs/BaseOpenAiCompatibleConfig" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "OpenAiConfig": { + "anyOf": [ + { + "$ref": "#/$defs/BaseOpenAiConfig" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "OracleDatabaseConnectionConfig": { + "anyOf": [ + { + "$ref": "#/$defs/BaseOracleDatabaseConnectionConfig" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "OracleDatabaseDatastore": { + "anyOf": [ + { + "$ref": "#/$defs/BaseOracleDatabaseDatastore" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "OutputMessageNode": { + "anyOf": [ + { + "$ref": "#/$defs/BaseOutputMessageNode" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "PKCEMethod": { + "enum": [ + "S256", + "plain" + ], + "title": "PKCEMethod", + "type": "string" + }, + "PKCEPolicy": { + "description": "Policy configuration for Proof Key for Code Exchange (PKCE).\n\nPKCE mitigates authorization code interception and injection attacks in\nauthorization code flows. Some protocols (such as MCP OAuth) require PKCE.", + "properties": { + "required": { + "default": true, + "title": "Required", + "type": "boolean" + }, + "method": { + "$ref": "#/$defs/PKCEMethod", + "default": "S256" + } + }, + "title": "PKCEPolicy", + "type": "object" + }, + "ParallelFlowNode": { + "anyOf": [ + { + "$ref": "#/$defs/BaseParallelFlowNode" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "ParallelMapNode": { + "anyOf": [ + { + "$ref": "#/$defs/BaseParallelMapNode" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "PostgresDatabaseConnectionConfig": { + "anyOf": [ + { + "$ref": "#/$defs/BasePostgresDatabaseConnectionConfig" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "PostgresDatabaseDatastore": { + "anyOf": [ + { + "$ref": "#/$defs/BasePostgresDatabaseDatastore" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "Property": { + "description": "This object must be a valid JSON Schema", + "type": "object" + }, + "ReductionMethod": { + "description": "Enumerator for the types of reduction available in the MapNode.", + "enum": [ + "append", + "average", + "max", + "min", + "sum" + ], + "title": "ReductionMethod", + "type": "string" + }, + "RemoteAgent": { + "anyOf": [ + { + "$ref": "#/$defs/BaseRemoteAgent" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "RemoteTool": { + "anyOf": [ + { + "$ref": "#/$defs/BaseRemoteTool" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "RemoteTransport": { + "anyOf": [ + { + "$ref": "#/$defs/BaseRemoteTransport" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "RetryPolicy": { + "additionalProperties": false, + "properties": { + "max_attempts": { + "default": 2, + "minimum": 0, + "title": "Max Attempts", + "type": "integer" + }, + "request_timeout": { + "anyOf": [ + { + "exclusiveMinimum": 0, + "type": "number" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Request Timeout" + }, + "initial_retry_delay": { + "default": 1.0, + "minimum": 0, + "title": "Initial Retry Delay", + "type": "number" + }, + "max_retry_delay": { + "default": 8.0, + "minimum": 0, + "title": "Max Retry Delay", + "type": "number" + }, + "backoff_factor": { + "default": 2.0, + "exclusiveMinimum": 0, + "title": "Backoff Factor", + "type": "number" + }, + "jitter": { + "anyOf": [ + { + "enum": [ + "decorrelated", + "equal", + "full", + "full_and_equal_for_throttle" + ], + "type": "string" + }, + { + "type": "null" + } + ], + "default": "full_and_equal_for_throttle", + "title": "Jitter" + }, + "service_error_retry_on_any_5xx": { + "default": true, + "title": "Service Error Retry On Any 5Xx", + "type": "boolean" + }, + "recoverable_statuses": { + "additionalProperties": { + "items": { + "type": "string" + }, + "type": "array" + }, + "title": "Recoverable Statuses", + "type": "object" + } + }, + "title": "RetryPolicy", + "type": "object" + }, + "SSETransport": { + "anyOf": [ + { + "$ref": "#/$defs/BaseSSETransport" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "SSEmTLSTransport": { + "anyOf": [ + { + "$ref": "#/$defs/BaseSSEmTLSTransport" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "ScopePolicy": { + "enum": [ + "fixed", + "use_challenge_or_supported" + ], + "title": "ScopePolicy", + "type": "string" + }, + "ServerTool": { + "anyOf": [ + { + "$ref": "#/$defs/BaseServerTool" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "ServingMode": { + "description": "Serving mode to use for the GenAI service", + "enum": [ + "DEDICATED", + "ON_DEMAND" + ], + "title": "ServingMode", + "type": "string" + }, + "SessionParameters": { + "description": "Class to specify parameters of the MCP client session.", + "properties": { + "read_timeout_seconds": { + "default": 60.0, + "title": "Read Timeout Seconds", + "type": "number" + } + }, + "title": "SessionParameters", + "type": "object" + }, + "SpecializedAgent": { + "anyOf": [ + { + "$ref": "#/$defs/BaseSpecializedAgent" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "StartNode": { + "anyOf": [ + { + "$ref": "#/$defs/BaseStartNode" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "StdioTransport": { + "anyOf": [ + { + "$ref": "#/$defs/BaseStdioTransport" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "StreamableHTTPTransport": { + "anyOf": [ + { + "$ref": "#/$defs/BaseStreamableHTTPTransport" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "StreamableHTTPmTLSTransport": { + "anyOf": [ + { + "$ref": "#/$defs/BaseStreamableHTTPmTLSTransport" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "Swarm": { + "anyOf": [ + { + "$ref": "#/$defs/BaseSwarm" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "TlsOracleDatabaseConnectionConfig": { + "anyOf": [ + { + "$ref": "#/$defs/BaseTlsOracleDatabaseConnectionConfig" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "TlsPostgresDatabaseConnectionConfig": { + "anyOf": [ + { + "$ref": "#/$defs/BaseTlsPostgresDatabaseConnectionConfig" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "Tool": { + "anyOf": [ + { + "$ref": "#/$defs/BaseTool" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "ToolBox": { + "anyOf": [ + { + "$ref": "#/$defs/BaseToolBox" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "ToolNode": { + "anyOf": [ + { + "$ref": "#/$defs/BaseToolNode" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "VllmConfig": { + "anyOf": [ + { + "$ref": "#/$defs/BaseVllmConfig" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "BaseA2AAgent": { + "additionalProperties": false, + "description": "Component which communicates with a remote server agent using the A2A Protocol.", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "inputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Inputs" + }, + "outputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Outputs" + }, + "agent_url": { + "title": "Agent Url", + "type": "string" + }, + "connection_config": { + "$ref": "#/$defs/A2AConnectionConfig" + }, + "session_parameters": { + "$ref": "#/$defs/A2ASessionParameters", + "default": { + "timeout": 60.0, + "poll_interval": 2.0, + "max_retries": 5 + } + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "A2AAgent" + } + }, + "required": [ + "agent_url", + "connection_config", + "name" + ], + "title": "A2AAgent", + "type": "object", + "x-abstract-component": false + }, + "BaseA2AConnectionConfig": { + "additionalProperties": false, + "description": "Class to specify configuration settings for establishing a connection in A2A communication.", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "timeout": { + "default": 600.0, + "title": "Timeout", + "type": "number" + }, + "headers": { + "anyOf": [ + { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Headers" + }, + "verify": { + "default": true, + "title": "Verify", + "type": "boolean" + }, + "key_file": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Key File" + }, + "cert_file": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Cert File" + }, + "ssl_ca_cert": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Ssl Ca Cert" + }, + "retry_policy": { + "anyOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/RetryPolicy" + } + ], + "default": null + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "A2AConnectionConfig" + } + }, + "required": [ + "name" + ], + "title": "A2AConnectionConfig", + "type": "object", + "x-abstract-component": false + }, + "BaseAgent": { + "additionalProperties": false, + "description": "An agent is a component that can do several rounds of conversation to solve a task.\n\nIt can be executed by itself, or be executed in a flow using an AgentNode.\n\n\nExamples\n--------\n>>> from pyagentspec.agent import Agent\n>>> from pyagentspec.property import Property\n>>> expertise_property=Property(\n... json_schema={\"title\": \"domain_of_expertise\", \"type\": \"string\"}\n... )\n>>> system_prompt = '''You are an expert in {{domain_of_expertise}}.\n... Please help the users with their requests.'''\n>>> agent = Agent(\n... name=\"Adaptive expert agent\",\n... system_prompt=system_prompt,\n... llm_config=llm_config,\n... inputs=[expertise_property],\n... )", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "inputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Inputs" + }, + "outputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Outputs" + }, + "llm_config": { + "$ref": "#/$defs/LlmConfig" + }, + "system_prompt": { + "title": "System Prompt", + "type": "string" + }, + "tools": { + "items": { + "$ref": "#/$defs/Tool" + }, + "title": "Tools", + "type": "array" + }, + "toolboxes": { + "items": { + "$ref": "#/$defs/ToolBox" + }, + "title": "Toolboxes", + "type": "array" + }, + "human_in_the_loop": { + "default": true, + "title": "Human In The Loop", + "type": "boolean" + }, + "transforms": { + "items": { + "$ref": "#/$defs/MessageTransform" + }, + "title": "Transforms", + "type": "array" + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "Agent" + } + }, + "required": [ + "llm_config", + "name", + "system_prompt" + ], + "title": "Agent", + "type": "object", + "x-abstract-component": false + }, + "BaseAgentNode": { + "additionalProperties": false, + "description": "The agent execution node is a node that will execute an agent as part of a flow.\n\nIf branches are configured, the agent will be prompted to select a branch before the agent node\ncompletes to transition to another node of the Flow.\n\n- **Inputs**\n Inferred from the definition of the agent to execute.\n- **Outputs**\n Inferred from the definition of the agent to execute.\n- **Branches**\n One, the default next.\n\nExamples\n--------\n>>> from pyagentspec.flows.flow import Flow\n>>> from pyagentspec.flows.edges import ControlFlowEdge, DataFlowEdge\n>>> from pyagentspec.agent import Agent\n>>> from pyagentspec.property import Property\n>>> from pyagentspec.flows.nodes import AgentNode, StartNode, EndNode\n>>> from pyagentspec.tools import ServerTool\n>>> query_property = Property(json_schema={\"title\": \"query\", \"type\": \"string\"})\n>>> search_results_property = Property(\n... json_schema={\"title\": \"search_results\", \"type\": \"array\", \"items\": {\"type\": \"string\"}}\n... )\n>>> search_tool = ServerTool(\n... name=\"search_tool\",\n... description=(\n... \"This tool runs a web search with the given query \"\n... \"and returns the most relevant results\"\n... ),\n... inputs=[query_property],\n... outputs=[search_results_property],\n... )\n>>> agent = Agent(\n... name=\"Search agent\",\n... llm_config=llm_config,\n... system_prompt=(\n... \"Your task is to gather the required information for the user: {{query}}\"\n... ),\n... tools=[search_tool],\n... outputs=[search_results_property],\n... )\n>>> start_node = StartNode(name=\"start\", inputs=[query_property])\n>>> end_node = EndNode(name=\"end\", outputs=[search_results_property])\n>>> agent_node = AgentNode(\n... name=\"Search agent node\",\n... agent=agent,\n... )\n>>> flow = Flow(\n... name=\"Search agent flow\",\n... start_node=start_node,\n... nodes=[start_node, agent_node, end_node],\n... control_flow_connections=[\n... ControlFlowEdge(name=\"start_to_agent\", from_node=start_node, to_node=agent_node),\n... ControlFlowEdge(name=\"agent_to_end\", from_node=agent_node, to_node=end_node),\n... ],\n... data_flow_connections=[\n... DataFlowEdge(\n... name=\"query_edge\",\n... source_node=start_node,\n... source_output=\"query\",\n... destination_node=agent_node,\n... destination_input=\"query\",\n... ),\n... DataFlowEdge(\n... name=\"search_results_edge\",\n... source_node=agent_node,\n... source_output=\"search_results\",\n... destination_node=end_node,\n... destination_input=\"search_results\"\n... ),\n... ],\n... )", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "inputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Inputs" + }, + "outputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Outputs" + }, + "branches": { + "items": { + "type": "string" + }, + "title": "Branches", + "type": "array" + }, + "agent": { + "$ref": "#/$defs/AgenticComponent" + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "AgentNode" + } + }, + "required": [ + "agent", + "name" + ], + "title": "AgentNode", + "type": "object", + "x-abstract-component": false + }, + "BaseAgentSpecializationParameters": { + "additionalProperties": false, + "description": "Parameters used to specialize an agent for a certain goal or task.", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "inputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Inputs" + }, + "outputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Outputs" + }, + "additional_instructions": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Additional Instructions" + }, + "additional_tools": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Tool" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Additional Tools" + }, + "human_in_the_loop": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Human In The Loop" + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "AgentSpecializationParameters" + } + }, + "required": [ + "name" + ], + "title": "AgentSpecializationParameters", + "type": "object", + "x-abstract-component": false + }, + "BaseAgenticComponent": { + "anyOf": [ + { + "$ref": "#/$defs/A2AAgent" + }, + { + "$ref": "#/$defs/Agent" + }, + { + "$ref": "#/$defs/Flow" + }, + { + "$ref": "#/$defs/ManagerWorkers" + }, + { + "$ref": "#/$defs/OciAgent" + }, + { + "$ref": "#/$defs/RemoteAgent" + }, + { + "$ref": "#/$defs/SpecializedAgent" + }, + { + "$ref": "#/$defs/Swarm" + } + ], + "x-abstract-component": true + }, + "BaseApiNode": { + "additionalProperties": false, + "description": "Make an API call.\n\nThis node is intended to be a part of a Flow.\n\n- **Inputs**\n Inferred from the json spec retrieved from API Spec URI, if available and reachable.\n Otherwise, users have to manually specify them.\n- **Outputs**\n Inferred from the json spec retrieved from API Spec URI, if available and reachable.\n Otherwise, users should manually specify them.\n\n If None is given, ``pyagentspec`` infers a generic property of any type named ``response``.\n- **Branches**\n One, the default next.\n\n\nExamples\n--------\n>>> from pyagentspec.flows.nodes import ApiNode\n>>> from pyagentspec.property import Property\n>>> weather_result_property = Property(\n... json_schema={\n... \"title\": \"zurich_weather\",\n... \"type\": \"object\",\n... \"properties\": {\n... \"temperature\": {\n... \"type\": \"number\",\n... \"description\": \"Temperature in celsius degrees\",\n... },\n... \"weather\": {\"type\": \"string\"}\n... },\n... }\n... )\n>>> call_current_weather_step = ApiNode(\n... name=\"Weather API call node\",\n... url=\"https://example.com/weather\",\n... http_method = \"GET\",\n... query_params={\n... \"location\": \"zurich\",\n... },\n... outputs=[weather_result_property]\n... )\n>>>\n>>> item_id_property = Property(\n... json_schema={\"title\": \"item_id\", \"type\": \"string\"}\n... )\n>>> order_id_property = Property(\n... json_schema={\"title\": \"order_id\", \"type\": \"string\"}\n... )\n>>> store_id_property = Property(\n... json_schema={\"title\": \"store_id\", \"type\": \"string\"}\n... )\n>>> session_id_property = Property(\n... json_schema={\"title\": \"session_id\", \"type\": \"string\"}\n... )\n>>> create_order_step = ApiNode(\n... name=\"Orders api call node\",\n... url=\"https://example.com/orders/{{ order_id }}\",\n... http_method=\"POST\",\n... # sending an object which will automatically be transformed into JSON\n... data={\n... # define a static body parameter\n... \"topic_id\": 12345,\n... # define a templated body parameter.\n... # The value for {{ item_id }} will be taken from the IO system at runtime\n... \"item_id\": \"{{ item_id }}\",\n... },\n... query_params={\n... # provide one templated query parameter called \"store_id\"\n... # which will take its value from the IO system from key \"store_id\"\n... \"store_id\": \"{{ store_id }}\",\n... },\n... headers={\n... # set header session_id. the value is coming from the IO system\n... \"session_id\": \"{{ session_id }}\",\n... },\n... inputs=[item_id_property, order_id_property, store_id_property, session_id_property],\n... )", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "inputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Inputs" + }, + "outputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Outputs" + }, + "branches": { + "items": { + "type": "string" + }, + "title": "Branches", + "type": "array" + }, + "url": { + "title": "Url", + "type": "string" + }, + "http_method": { + "title": "Http Method", + "type": "string" + }, + "api_spec_uri": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Api Spec Uri" + }, + "data": { + "anyOf": [ + { + "type": "string" + }, + { + "format": "binary", + "type": "string" + }, + {} + ], + "title": "Data" + }, + "query_params": { + "additionalProperties": true, + "title": "Query Params", + "type": "object" + }, + "headers": { + "additionalProperties": true, + "title": "Headers", + "type": "object" + }, + "sensitive_headers": { + "additionalProperties": true, + "title": "Sensitive Headers", + "type": "object" + }, + "url_allow_list": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Url Allow List" + }, + "retry_policy": { + "anyOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/RetryPolicy" + } + ], + "default": null + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "ApiNode" + } + }, + "required": [ + "http_method", + "name", + "url" + ], + "title": "ApiNode", + "type": "object", + "x-abstract-component": false + }, + "BaseAuthConfig": { + "anyOf": [ + { + "$ref": "#/$defs/OAuthConfig" + } + ], + "x-abstract-component": true + }, + "BaseBranchingNode": { + "additionalProperties": false, + "description": "Select the next node to transition to based on a mapping.\n\nThe input is used as key for the mapping. If the input does not correspond to any of the keys\nof the mapping the branch 'default' will be selected. This node is intended to be a part of a\nFlow.\n\n- **Inputs**\n The input value that should be used as key for the mapping.\n\n If None is given, ``pyagentspec`` infers a string property named ``branching_mapping_key``.\n- **Outputs**\n None.\n- **Branches**\n One for each value in the mapping, plus a branch called ``default``,\n which is the branch taken by the flow when mapping fails\n (i.e., the input does not match any key in the mapping).\n\nExamples\n--------\n>>> from pyagentspec.agent import Agent\n>>> from pyagentspec.flows.edges import ControlFlowEdge, DataFlowEdge\n>>> from pyagentspec.flows.flow import Flow\n>>> from pyagentspec.flows.nodes import AgentNode, BranchingNode, StartNode, EndNode\n>>> from pyagentspec.property import Property\n>>> CORRECT_PASSWORD_BRANCH = \"PASSWORD_OK\"\n>>> password_property = Property(\n... json_schema={\"title\": \"password\", \"type\": \"string\"}\n... )\n>>> agent = Agent(\n... name=\"User input agent\",\n... llm_config=llm_config,\n... system_prompt=(\n... \"Your task is to ask the password to the user. \"\n... \"Once you get it, submit it and end.\"\n... ),\n... outputs=[password_property],\n... )\n>>> start_node = StartNode(name=\"start\")\n>>> access_granted_end_node = EndNode(\n... name=\"access granted end\", branch_name=\"ACCESS_GRANTED\"\n... )\n>>> access_denied_end_node = EndNode(\n... name=\"access denied end\", branch_name=\"ACCESS_DENIED\"\n... )\n>>> branching_node = BranchingNode(\n... name=\"password check\",\n... mapping={\"123456\": CORRECT_PASSWORD_BRANCH},\n... inputs=[password_property]\n... )\n>>> agent_node = AgentNode(\n... name=\"User input agent node\",\n... agent=agent,\n... )\n>>> assistant = Flow(\n... name=\"Check access flow\",\n... start_node=start_node,\n... nodes=[\n... start_node,\n... agent_node,\n... branching_node,\n... access_granted_end_node,\n... access_denied_end_node\n... ],\n... control_flow_connections=[\n... ControlFlowEdge(\n... name=\"start_to_agent\",\n... from_node=start_node,\n... to_node=agent_node\n... ),\n... ControlFlowEdge(\n... name=\"agent_to_branching\",\n... from_node=agent_node,\n... to_node=branching_node\n... ),\n... ControlFlowEdge(\n... name=\"branching_to_access_granted\",\n... from_node=branching_node,\n... from_branch=CORRECT_PASSWORD_BRANCH,\n... to_node=access_granted_end_node,\n... ),\n... ControlFlowEdge(\n... name=\"branching_to_access_denied\",\n... from_node=branching_node,\n... from_branch=BranchingNode.DEFAULT_BRANCH,\n... to_node=access_denied_end_node,\n... ),\n... ],\n... data_flow_connections=[\n... DataFlowEdge(\n... name=\"password_edge\",\n... source_node=agent_node,\n... source_output=\"password\",\n... destination_node=branching_node,\n... destination_input=\"password\",\n... ),\n... ],\n... )", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "inputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Inputs" + }, + "outputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Outputs" + }, + "branches": { + "items": { + "type": "string" + }, + "title": "Branches", + "type": "array" + }, + "mapping": { + "additionalProperties": { + "type": "string" + }, + "title": "Mapping", + "type": "object" + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "BranchingNode" + } + }, + "required": [ + "mapping", + "name" + ], + "title": "BranchingNode", + "type": "object", + "x-abstract-component": false + }, + "BaseBuiltinTool": { + "additionalProperties": false, + "description": "A tool that is built into and executed by the orchestrator", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "inputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Inputs" + }, + "outputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Outputs" + }, + "requires_confirmation": { + "default": false, + "title": "Requires Confirmation", + "type": "boolean" + }, + "tool_type": { + "title": "Tool Type", + "type": "string" + }, + "configuration": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Configuration" + }, + "executor_name": { + "anyOf": [ + { + "type": "string" + }, + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Executor Name" + }, + "tool_version": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Tool Version" + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "BuiltinTool" + } + }, + "required": [ + "name", + "tool_type" + ], + "title": "BuiltinTool", + "type": "object", + "x-abstract-component": false + }, + "BaseCatchExceptionNode": { + "additionalProperties": false, + "description": "Node to execute a Flow and catch exceptions.\n\n- If no exception is caught, the node will transition to the branches of its subflow.\n- If an exception is caught, it will transition to an exception branch.\n\nInputs\n------\nSame as the inputs from the ``subflow``.\n\nOutputs\n-------\n\n- The outputs of the ``subflow``.\n If an exception is raised, the default values of each output property are used.\n- An additional output named ``caught_exception_info`` with type ``string | null``\n and default value ``null``. Executors may populate it with a non-sensitive\n error description when an exception is caught.\n\nBranches\n--------\n\n- The branches of the ``subflow``\n- One additional branch named ``caught_exception_branch``\n\nSecurity Considerations\n-----------------------\n\nSee security considerations regarding exception catching in\nthe :ref:`Security Considerations `", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "inputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Inputs" + }, + "outputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Outputs" + }, + "branches": { + "items": { + "type": "string" + }, + "title": "Branches", + "type": "array" + }, + "subflow": { + "$ref": "#/$defs/Flow" + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "CatchExceptionNode" + } + }, + "required": [ + "name", + "subflow" + ], + "title": "CatchExceptionNode", + "type": "object", + "x-abstract-component": false + }, + "BaseClientTool": { + "additionalProperties": false, + "description": "A tool that needs to be run by the client application.", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "inputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Inputs" + }, + "outputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Outputs" + }, + "requires_confirmation": { + "default": false, + "title": "Requires Confirmation", + "type": "boolean" + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "ClientTool" + } + }, + "required": [ + "name" + ], + "title": "ClientTool", + "type": "object", + "x-abstract-component": false + }, + "BaseClientTransport": { + "anyOf": [ + { + "$ref": "#/$defs/RemoteTransport" + }, + { + "$ref": "#/$defs/SSETransport" + }, + { + "$ref": "#/$defs/SSEmTLSTransport" + }, + { + "$ref": "#/$defs/StdioTransport" + }, + { + "$ref": "#/$defs/StreamableHTTPTransport" + }, + { + "$ref": "#/$defs/StreamableHTTPmTLSTransport" + } + ], + "x-abstract-component": true + }, + "BaseComponentWithIO": { + "anyOf": [ + { + "$ref": "#/$defs/A2AAgent" + }, + { + "$ref": "#/$defs/Agent" + }, + { + "$ref": "#/$defs/AgentNode" + }, + { + "$ref": "#/$defs/AgentSpecializationParameters" + }, + { + "$ref": "#/$defs/AgenticComponent" + }, + { + "$ref": "#/$defs/ApiNode" + }, + { + "$ref": "#/$defs/BranchingNode" + }, + { + "$ref": "#/$defs/BuiltinTool" + }, + { + "$ref": "#/$defs/CatchExceptionNode" + }, + { + "$ref": "#/$defs/ClientTool" + }, + { + "$ref": "#/$defs/EndNode" + }, + { + "$ref": "#/$defs/Flow" + }, + { + "$ref": "#/$defs/FlowNode" + }, + { + "$ref": "#/$defs/InputMessageNode" + }, + { + "$ref": "#/$defs/LlmNode" + }, + { + "$ref": "#/$defs/MCPTool" + }, + { + "$ref": "#/$defs/MCPToolSpec" + }, + { + "$ref": "#/$defs/ManagerWorkers" + }, + { + "$ref": "#/$defs/MapNode" + }, + { + "$ref": "#/$defs/Node" + }, + { + "$ref": "#/$defs/OciAgent" + }, + { + "$ref": "#/$defs/OutputMessageNode" + }, + { + "$ref": "#/$defs/ParallelFlowNode" + }, + { + "$ref": "#/$defs/ParallelMapNode" + }, + { + "$ref": "#/$defs/RemoteAgent" + }, + { + "$ref": "#/$defs/RemoteTool" + }, + { + "$ref": "#/$defs/ServerTool" + }, + { + "$ref": "#/$defs/SpecializedAgent" + }, + { + "$ref": "#/$defs/StartNode" + }, + { + "$ref": "#/$defs/Swarm" + }, + { + "$ref": "#/$defs/Tool" + }, + { + "$ref": "#/$defs/ToolNode" + } + ], + "x-abstract-component": true + }, + "BaseControlFlowEdge": { + "additionalProperties": false, + "description": "A control flow edge specifies a possible transition from a node to another in a flow.\n\nA single node can have several potential next nodes, in which case several control flow edges\nshould be present in the control flow connections of that flow.", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "from_node": { + "$ref": "#/$defs/Node" + }, + "from_branch": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "From Branch" + }, + "to_node": { + "$ref": "#/$defs/Node" + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "ControlFlowEdge" + } + }, + "required": [ + "from_node", + "name", + "to_node" + ], + "title": "ControlFlowEdge", + "type": "object", + "x-abstract-component": false + }, + "BaseConversationSummarizationTransform": { + "additionalProperties": false, + "description": "Summarizes conversations exceeding a configured size threshold using an LLM and caches\nconversation summaries in a ``Datastore``.\n\nThis is useful to reduce long conversation history into a concise context for downstream LLM calls.\n\nExamples\n--------\n>>> from pyagentspec.transforms import ConversationSummarizationTransform\n>>> summarization_transform = ConversationSummarizationTransform(\n... name=\"conversation-summarizer\",\n... llm=llm_config,\n... max_num_messages=30,\n... min_num_messages=10\n... )", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "llm": { + "$ref": "#/$defs/LlmConfig" + }, + "max_num_messages": { + "anyOf": [ + { + "exclusiveMinimum": 0, + "type": "integer" + }, + { + "type": "null" + } + ], + "default": 50, + "title": "Max Num Messages" + }, + "max_num_characters": { + "anyOf": [ + { + "exclusiveMinimum": 0, + "type": "integer" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Max Num Characters" + }, + "min_num_messages": { + "default": 10, + "exclusiveMinimum": 0, + "title": "Min Num Messages", + "type": "integer" + }, + "summarization_instructions": { + "default": "Please make a summary of this conversation. Include relevant information and keep it short. Your response will replace the messages, so just output the summary directly, no introduction needed.", + "title": "Summarization Instructions", + "type": "string" + }, + "summarized_conversation_template": { + "default": "Summarized conversation: {{summary}}", + "title": "Summarized Conversation Template", + "type": "string" + }, + "max_cache_size": { + "anyOf": [ + { + "exclusiveMinimum": 0, + "type": "integer" + }, + { + "type": "null" + } + ], + "default": 10000, + "title": "Max Cache Size" + }, + "max_cache_lifetime": { + "anyOf": [ + { + "exclusiveMinimum": 0, + "type": "integer" + }, + { + "type": "null" + } + ], + "default": 14400, + "title": "Max Cache Lifetime" + }, + "cache_collection_name": { + "default": "summarized_conversations_cache", + "title": "Cache Collection Name", + "type": "string" + }, + "datastore": { + "anyOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/InMemoryCollectionDatastore" + }, + { + "$ref": "#/$defs/OracleDatabaseDatastore" + }, + { + "$ref": "#/$defs/PostgresDatabaseDatastore" + } + ], + "title": "Datastore" + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "ConversationSummarizationTransform" + } + }, + "required": [ + "llm", + "name" + ], + "title": "ConversationSummarizationTransform", + "type": "object", + "x-abstract-component": false + }, + "BaseDataFlowEdge": { + "additionalProperties": false, + "description": "A data flow edge specifies how the output of a node propagates as input of another node.\n\nAn outputs can be propagated as input of several nodes.", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "source_node": { + "$ref": "#/$defs/Node" + }, + "source_output": { + "title": "Source Output", + "type": "string" + }, + "destination_node": { + "$ref": "#/$defs/Node" + }, + "destination_input": { + "title": "Destination Input", + "type": "string" + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "DataFlowEdge" + } + }, + "required": [ + "destination_input", + "destination_node", + "name", + "source_node", + "source_output" + ], + "title": "DataFlowEdge", + "type": "object", + "x-abstract-component": false + }, + "BaseDatastore": { + "anyOf": [ + { + "$ref": "#/$defs/InMemoryCollectionDatastore" + }, + { + "$ref": "#/$defs/OracleDatabaseDatastore" + }, + { + "$ref": "#/$defs/PostgresDatabaseDatastore" + } + ], + "x-abstract-component": true + }, + "BaseDbmsVectorChainLlmConfig": { + "additionalProperties": false, + "description": "Configure an LLM executed by Oracle Database through DBMS_VECTOR_CHAIN.", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "model_id": { + "minLength": 1, + "title": "Model Id", + "type": "string" + }, + "provider": { + "minLength": 1, + "title": "Provider", + "type": "string" + }, + "url": { + "minLength": 1, + "title": "Url", + "type": "string" + }, + "default_generation_parameters": { + "anyOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/LlmGenerationConfig" + } + ], + "default": null + }, + "retry_policy": { + "anyOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/RetryPolicy" + } + ], + "default": null + }, + "host": { + "anyOf": [ + { + "const": "local", + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Host" + }, + "credential_name": { + "anyOf": [ + { + "minLength": 1, + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Credential Name" + }, + "transfer_timeout": { + "anyOf": [ + { + "minimum": 0, + "type": "integer" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Transfer Timeout" + }, + "connection_config": { + "anyOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/OracleDatabaseConnectionConfig" + } + ], + "default": null + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "DbmsVectorChainLlmConfig" + } + }, + "required": [ + "model_id", + "name", + "provider", + "url" + ], + "title": "DbmsVectorChainLlmConfig", + "type": "object", + "x-abstract-component": false + }, + "BaseEndNode": { + "additionalProperties": false, + "description": "End nodes denote the end of the execution of a flow.\n\nThere might be several end nodes in a flow, in which case the executor of the flow\nshould be able to track which one was reached and pass that back to the caller.\n\n- **Inputs**\n The list of inputs of the step. If both input and output properties are specified they\n must be an exact match\n\n If None is given, ``pyagentspec`` copies the outputs provided, if any. Otherwise, no input is exposed.\n- **Outputs**\n The list of outputs that should be exposed by the flow. If both input and output properties\n are specified they must be an exact match\n\n If None is given, ``pyagentspec`` copies the inputs provided, if any. Otherwise, no output is exposed.\n- **Branches**\n None.\n\nExamples\n--------\n>>> from pyagentspec.agent import Agent\n>>> from pyagentspec.flows.edges import ControlFlowEdge, DataFlowEdge\n>>> from pyagentspec.flows.flow import Flow\n>>> from pyagentspec.flows.nodes import AgentNode, BranchingNode, StartNode, EndNode\n>>> from pyagentspec.property import Property\n>>> languages_to_branch_name = {\n... \"english\": \"ENGLISH\",\n... \"spanish\": \"SPANISH\",\n... \"italian\": \"ITALIAN\",\n... }\n>>> language_property = Property(\n... json_schema={\"title\": \"language\", \"type\": \"string\"}\n... )\n>>> agent = Agent(\n... name=\"Language detector agent\",\n... llm_config=llm_config,\n... system_prompt=(\n... \"Your task is to understand the language spoken by the user.\"\n... \"Please output only the language in lowercase and submit.\"\n... ),\n... outputs=[language_property],\n... )\n>>> start_node = StartNode(name=\"start\")\n>>> english_end_node = EndNode(\n... name=\"english end\", branch_name=languages_to_branch_name[\"english\"]\n... )\n>>> spanish_end_node = EndNode(\n... name=\"spanish end\", branch_name=languages_to_branch_name[\"spanish\"]\n... )\n>>> italian_end_node = EndNode(\n... name=\"italian end\", branch_name=languages_to_branch_name[\"italian\"]\n... )\n>>> unknown_end_node = EndNode(name=\"unknown language end\", branch_name=\"unknown\")\n>>> branching_node = BranchingNode(\n... name=\"language check\",\n... mapping=languages_to_branch_name,\n... inputs=[language_property]\n... )\n>>> agent_node = AgentNode(\n... name=\"User input agent node\",\n... agent=agent,\n... )\n>>> assistant = Flow(\n... name=\"Check access flow\",\n... start_node=start_node,\n... nodes=[\n... start_node,\n... agent_node,\n... branching_node,\n... english_end_node,\n... spanish_end_node,\n... italian_end_node,\n... unknown_end_node,\n... ],\n... control_flow_connections=[\n... ControlFlowEdge(\n... name=\"start_to_agent\", from_node=start_node, to_node=agent_node\n... ),\n... ControlFlowEdge(\n... name=\"agent_to_branching\", from_node=agent_node, to_node=branching_node\n... ),\n... ControlFlowEdge(\n... name=\"branching_to_english_end\",\n... from_node=branching_node,\n... from_branch=languages_to_branch_name[\"english\"],\n... to_node=english_end_node,\n... ),\n... ControlFlowEdge(\n... name=\"branching_to_spanish_end\",\n... from_node=branching_node,\n... from_branch=languages_to_branch_name[\"spanish\"],\n... to_node=spanish_end_node,\n... ),\n... ControlFlowEdge(\n... name=\"branching_to_italian_end\",\n... from_node=branching_node,\n... from_branch=languages_to_branch_name[\"italian\"],\n... to_node=italian_end_node,\n... ),\n... ControlFlowEdge(\n... name=\"branching_to_unknown_end\",\n... from_node=branching_node,\n... from_branch=BranchingNode.DEFAULT_BRANCH,\n... to_node=unknown_end_node,\n... ),\n... ],\n... data_flow_connections=[\n... DataFlowEdge(\n... name=\"language_edge\",\n... source_node=agent_node,\n... source_output=\"language\",\n... destination_node=branching_node,\n... destination_input=\"language\",\n... ),\n... ],\n... )", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "inputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Inputs" + }, + "outputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Outputs" + }, + "branches": { + "items": { + "type": "string" + }, + "title": "Branches", + "type": "array" + }, + "branch_name": { + "default": "next", + "title": "Branch Name", + "type": "string" + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "EndNode" + } + }, + "required": [ + "name" + ], + "title": "EndNode", + "type": "object", + "x-abstract-component": false + }, + "BaseFlow": { + "additionalProperties": false, + "description": "A flow is a component to model sequences of operations to do in a precised order.\n\nThe operations and sequence is defined by the nodes and transitions associated to the flow.\nSteps can be deterministic, or for some use LLMs.\n\nExample\n-------\n>>> from pyagentspec.property import Property\n>>> from pyagentspec.flows.flow import Flow\n>>> from pyagentspec.flows.edges import ControlFlowEdge, DataFlowEdge\n>>> from pyagentspec.flows.nodes import LlmNode, StartNode, EndNode\n>>> prompt_property = Property(\n... json_schema={\"title\": \"prompt\", \"type\": \"string\"}\n... )\n>>> llm_output_property = Property(\n... json_schema={\"title\": \"llm_output\", \"type\": \"string\"}\n... )\n>>> start_node = StartNode(name=\"start\", inputs=[prompt_property])\n>>> end_node = EndNode(name=\"end\", outputs=[llm_output_property])\n>>> llm_node = LlmNode(\n... name=\"simple llm node\",\n... llm_config=llm_config,\n... prompt_template=\"{{prompt}}\",\n... inputs=[prompt_property],\n... outputs=[llm_output_property],\n... )\n>>> flow = Flow(\n... name=\"Simple prompting flow\",\n... start_node=start_node,\n... nodes=[start_node, llm_node, end_node],\n... control_flow_connections=[\n... ControlFlowEdge(name=\"start_to_llm\", from_node=start_node, to_node=llm_node),\n... ControlFlowEdge(name=\"llm_to_end\", from_node=llm_node, to_node=end_node),\n... ],\n... data_flow_connections=[\n... DataFlowEdge(\n... name=\"prompt_edge\",\n... source_node=start_node,\n... source_output=\"prompt\",\n... destination_node=llm_node,\n... destination_input=\"prompt\",\n... ),\n... DataFlowEdge(\n... name=\"llm_output_edge\",\n... source_node=llm_node,\n... source_output=\"llm_output\",\n... destination_node=end_node,\n... destination_input=\"llm_output\"\n... ),\n... ],\n... )", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "inputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Inputs" + }, + "outputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Outputs" + }, + "start_node": { + "$ref": "#/$defs/Node" + }, + "nodes": { + "items": { + "$ref": "#/$defs/Node" + }, + "title": "Nodes", + "type": "array" + }, + "control_flow_connections": { + "items": { + "$ref": "#/$defs/ControlFlowEdge" + }, + "title": "Control Flow Connections", + "type": "array" + }, + "data_flow_connections": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/DataFlowEdge" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Data Flow Connections" + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "Flow" + } + }, + "required": [ + "control_flow_connections", + "name", + "nodes", + "start_node" + ], + "title": "Flow", + "type": "object", + "x-abstract-component": false + }, + "BaseFlowNode": { + "additionalProperties": false, + "description": "The flow node executes a subflow as part of a flow.\n\n- **Inputs**\n Inferred from the inner structure. It's the sets of inputs\n required by the StartNode of the inner flow.\n- **Outputs**\n Inferred from the inner structure. It's the union of the\n sets of outputs exposed by the EndNodes of the inner flow.\n- **Branches**\n Inferred from the inner flow, one per each different value of the attribute\n ``branch_name`` of the nodes of type EndNode in the inner flow.\n\nExample\n-------\nThe ``FlowNode`` is particularly suitable when subflows can be reused inside a project.\nLet's see an example with a flow that estimates numerical value\nusing the \"wisdowm of the crowd\" effect:\n\n>>> from pyagentspec.property import Property\n>>> from pyagentspec.flows.flow import Flow\n>>> from pyagentspec.flows.edges import ControlFlowEdge, DataFlowEdge\n>>> from pyagentspec.flows.nodes import MapNode, LlmNode, ToolNode, StartNode, EndNode\n>>> from pyagentspec.tools import ServerTool\n>>> duplication_tool = ServerTool(\n... name=\"duplication_tool\",\n... description=\"\",\n... inputs=[\n... Property(\n... json_schema={\"title\": \"element\", \"description\": \"\", \"type\": \"string\"}\n... ),\n... Property(\n... json_schema={\"title\": \"n\", \"description\": \"\", \"type\": \"integer\"}\n... ),\n... ],\n... outputs=[\n... Property(\n... json_schema={\n... \"title\": \"flow_iterable_queries\",\n... \"type\": \"array\",\n... \"items\": {\"type\": \"string\"}\n... },\n... )\n... ],\n... )\n>>> reduce_tool = ServerTool(\n... name=\"reduce_tool\",\n... description=\"\",\n... inputs=[\n... Property(\n... json_schema={\"title\": \"elements\", \"type\": \"array\", \"items\": {\"type\": \"string\"}}\n... ),\n... ],\n... outputs=[Property(json_schema={\"title\": \"flow_processed_query\", \"type\": \"string\"})],\n... )\n>>> # Defining a simple prompt\n>>> REASONING_PROMPT_TEMPLATE = '''Provide your best numerical estimate for: {{user_input}}\n... Your answer should be a single number.\n... Do not include any units, reasoning, or extra text.'''\n>>> # Defining the subflow for the map step\n>>> user_input_property = Property(\n... json_schema={\"title\": \"user_input\", \"type\": \"string\"}\n... )\n>>> flow_processed_query_property = Property(\n... json_schema={\"title\": \"flow_processed_query\", \"type\": \"string\"}\n... )\n>>> start_node = StartNode(name=\"start\", inputs=[user_input_property])\n>>> end_node = EndNode(name=\"end\", outputs=[flow_processed_query_property])\n>>> llm_node = LlmNode(\n... name=\"reasoning llm node\",\n... llm_config=llm_config,\n... prompt_template=REASONING_PROMPT_TEMPLATE,\n... inputs=[user_input_property],\n... outputs=[flow_processed_query_property],\n... )\n>>> inner_map_flow = Flow(\n... name=\"Map flow\",\n... start_node=start_node,\n... nodes=[start_node, llm_node, end_node],\n... control_flow_connections=[\n... ControlFlowEdge(name=\"start_to_llm\", from_node=start_node, to_node=llm_node),\n... ControlFlowEdge(name=\"llm_to_end\", from_node=llm_node, to_node=end_node),\n... ],\n... data_flow_connections=[\n... DataFlowEdge(\n... name=\"query_edge\",\n... source_node=start_node,\n... source_output=\"user_input\",\n... destination_node=llm_node,\n... destination_input=\"user_input\",\n... ),\n... DataFlowEdge(\n... name=\"search_results_edge\",\n... source_node=llm_node,\n... source_output=\"flow_processed_query\",\n... destination_node=end_node,\n... destination_input=\"flow_processed_query\"\n... ),\n... ],\n... )\n>>> user_query_property = Property(\n... json_schema={\"title\": \"user_query\", \"type\": \"string\"}\n... )\n>>> n_repeat_property = Property(\n... json_schema={\"title\": \"n_repeat\", \"type\": \"integer\"}\n... )\n>>> flow_iterable_queries_property = Property(\n... json_schema={\n... \"title\": \"iterated_user_input\",\n... \"type\": \"array\",\n... \"items\": {\"type\": \"string\"},\n... }\n... )\n>>> flow_processed_queries_property = Property(\n... json_schema={\n... \"title\": \"collected_flow_processed_query\",\n... \"type\": \"array\",\n... \"items\": {\"type\": \"string\"},\n... }\n... )\n>>> start_node = StartNode(name=\"start\", inputs=[user_query_property, n_repeat_property])\n>>> end_node = EndNode(name=\"end\", outputs=[flow_processed_query_property])\n>>> duplication_node = ToolNode(\n... name=\"duplication_tool node\",\n... tool=duplication_tool,\n... )\n>>> reduce_node = ToolNode(\n... name=\"reduce_tool node\",\n... tool=reduce_tool,\n... )\n>>> map_node = MapNode(\n... name=\"map node\",\n... subflow=inner_map_flow,\n... inputs=[flow_iterable_queries_property],\n... outputs=[flow_processed_queries_property],\n... )\n>>> mapreduce_flow = Flow(\n... name=\"Map-reduce flow\",\n... start_node=start_node,\n... nodes=[start_node, duplication_node, map_node, reduce_node, end_node],\n... control_flow_connections=[\n... ControlFlowEdge(\n... name=\"start_to_duplication\", from_node=start_node, to_node=duplication_node\n... ),\n... ControlFlowEdge(\n... name=\"duplication_to_map\", from_node=duplication_node, to_node=map_node\n... ),\n... ControlFlowEdge(name=\"map_to_reduce\", from_node=map_node, to_node=reduce_node),\n... ControlFlowEdge(name=\"reduce_to_end\", from_node=reduce_node, to_node=end_node),\n... ],\n... data_flow_connections=[\n... DataFlowEdge(\n... name=\"query_edge\",\n... source_node=start_node,\n... source_output=\"user_query\",\n... destination_node=duplication_node,\n... destination_input=\"element\",\n... ),\n... DataFlowEdge(\n... name=\"n_repeat_edge\",\n... source_node=start_node,\n... source_output=\"n_repeat\",\n... destination_node=duplication_node,\n... destination_input=\"n\",\n... ),\n... DataFlowEdge(\n... name=\"flow_iterables_edge\",\n... source_node=duplication_node,\n... source_output=\"flow_iterable_queries\",\n... destination_node=map_node,\n... destination_input=\"iterated_user_input\",\n... ),\n... DataFlowEdge(\n... name=\"flow_processed_queries_edge\",\n... source_node=map_node,\n... source_output=\"collected_flow_processed_query\",\n... destination_node=reduce_node,\n... destination_input=\"elements\",\n... ),\n... DataFlowEdge(\n... name=\"flow_processed_query_edge\",\n... source_node=reduce_node,\n... source_output=\"flow_processed_query\",\n... destination_node=end_node,\n... destination_input=\"flow_processed_query\",\n... ),\n... ],\n... )\n\nOnce the subflow is created we can simply integrate it with the ``FlowNode``:\n\n>>> from pyagentspec.flows.nodes import FlowNode, AgentNode\n>>> from pyagentspec.agent import Agent\n>>> start_node = StartNode(name=\"start\")\n>>> end_node = EndNode(name=\"end\", outputs=[flow_processed_query_property])\n>>> flow_node = FlowNode(name=\"flow node\", subflow=mapreduce_flow)\n>>> agent = Agent(\n... name=\"User interaction agent\",\n... llm_config=llm_config,\n... system_prompt=(\n... \"Your task is to gather from the user the query and the number of times \"\n... \"it should be asked to an LLM. Once you have this information, submit and exit.\"\n... ),\n... outputs=[user_query_property, n_repeat_property],\n... )\n>>> agent_node = AgentNode(name=\"flow node\", agent=agent)\n>>> flow = Flow(\n... name=\"Map flow\",\n... start_node=start_node,\n... nodes=[start_node, agent_node, flow_node, end_node],\n... control_flow_connections=[\n... ControlFlowEdge(name=\"start_to_agent\", from_node=start_node, to_node=agent_node),\n... ControlFlowEdge(name=\"agent_to_flow\", from_node=agent_node, to_node=flow_node),\n... ControlFlowEdge(name=\"flow_to_end\", from_node=flow_node, to_node=end_node),\n... ],\n... data_flow_connections=[\n... DataFlowEdge(\n... name=\"query_edge\",\n... source_node=agent_node,\n... source_output=\"user_query\",\n... destination_node=flow_node,\n... destination_input=\"user_query\",\n... ),\n... DataFlowEdge(\n... name=\"n_rep_edge\",\n... source_node=agent_node,\n... source_output=\"n_repeat\",\n... destination_node=flow_node,\n... destination_input=\"n_repeat\"\n... ),\n... DataFlowEdge(\n... name=\"n_rep_edge\",\n... source_node=flow_node,\n... source_output=\"flow_processed_query\",\n... destination_node=end_node,\n... destination_input=\"flow_processed_query\"\n... ),\n... ],\n... )", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "inputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Inputs" + }, + "outputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Outputs" + }, + "branches": { + "items": { + "type": "string" + }, + "title": "Branches", + "type": "array" + }, + "subflow": { + "$ref": "#/$defs/Flow" + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "FlowNode" + } + }, + "required": [ + "name", + "subflow" + ], + "title": "FlowNode", + "type": "object", + "x-abstract-component": false + }, + "BaseGeminiAIStudioAuthConfig": { + "additionalProperties": false, + "description": "Authentication settings for Gemini via Google AI Studio.", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "api_key": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Api Key" + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "GeminiAIStudioAuthConfig" + } + }, + "required": [ + "name" + ], + "title": "GeminiAIStudioAuthConfig", + "type": "object", + "x-abstract-component": false + }, + "BaseGeminiAuthConfig": { + "anyOf": [ + { + "$ref": "#/$defs/GeminiAIStudioAuthConfig" + }, + { + "$ref": "#/$defs/GeminiVertexAIAuthConfig" + } + ], + "x-abstract-component": true + }, + "BaseGeminiConfig": { + "additionalProperties": false, + "description": "Configure a connection to a Gemini LLM (AI Studio or Vertex AI).", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "model_id": { + "title": "Model Id", + "type": "string" + }, + "provider": { + "const": "google", + "default": "google", + "title": "Provider", + "type": "string" + }, + "api_provider": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Api Provider" + }, + "api_type": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Api Type" + }, + "url": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Url" + }, + "api_key": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Api Key" + }, + "default_generation_parameters": { + "anyOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/LlmGenerationConfig" + } + ], + "default": null + }, + "retry_policy": { + "anyOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/RetryPolicy" + } + ], + "default": null + }, + "auth": { + "$ref": "#/$defs/GeminiAuthConfig" + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "GeminiConfig" + } + }, + "required": [ + "auth", + "model_id", + "name" + ], + "title": "GeminiConfig", + "type": "object", + "x-abstract-component": false + }, + "BaseGeminiVertexAIAuthConfig": { + "additionalProperties": false, + "description": "Authentication settings for Gemini via Google Vertex AI.", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "project_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Project Id" + }, + "location": { + "default": "global", + "title": "Location", + "type": "string" + }, + "credentials": { + "anyOf": [ + { + "type": "string" + }, + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Credentials" + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "GeminiVertexAIAuthConfig" + } + }, + "required": [ + "name" + ], + "title": "GeminiVertexAIAuthConfig", + "type": "object", + "x-abstract-component": false + }, + "BaseInMemoryCollectionDatastore": { + "additionalProperties": false, + "description": "In-memory datastore for testing and development purposes.", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "datastore_schema": { + "additionalProperties": { + "$ref": "#/$defs/Property" + }, + "title": "Datastore Schema", + "type": "object" + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "InMemoryCollectionDatastore" + } + }, + "required": [ + "datastore_schema", + "name" + ], + "title": "InMemoryCollectionDatastore", + "type": "object", + "x-abstract-component": false + }, + "BaseInputMessageNode": { + "additionalProperties": false, + "description": "This node interrupts the execution of the flow in order to wait for a user input, and restarts after receiving it.\nAn agent message, if given, is appended to the conversation before waiting for input.\nUser input is appended to the conversation as a user message, and it is returned as a string property from the node.\n\n- **Inputs**\n One per variable in the message\n- **Outputs**\n One string property that represents the content of the input user message.\n\n If None is given, ``pyagentspec`` infers a string property named ``user_input``.\n- **Branches**\n One, the default next.\n\nExamples\n--------\n>>> from pyagentspec.flows.edges import ControlFlowEdge, DataFlowEdge\n>>> from pyagentspec.flows.flow import Flow\n>>> from pyagentspec.flows.nodes import StartNode, EndNode, InputMessageNode, OutputMessageNode, LlmNode\n>>> from pyagentspec.property import StringProperty\n>>> start_node = StartNode(name=\"start\")\n>>> prompt_node = OutputMessageNode(name=\"ask_input\", message=\"What is the paragraph you want to rephrase?\")\n>>> input_node = InputMessageNode(name=\"user_input\", outputs=[StringProperty(title=\"user_input\")])\n>>> llm_node = LlmNode(\n... name=\"rephrase\",\n... llm_config=llm_config,\n... prompt_template=\"Rephrase {{user_input}}\",\n... outputs=[StringProperty(title=\"rephrased_user_input\")],\n... )\n>>> output_node = OutputMessageNode(name=\"ask_input\", message=\"{{rephrased_user_input}}\")\n>>> end_node = EndNode(name=\"end\")\n>>> flow = Flow(\n... name=\"rephrase_paragraph_flow\",\n... start_node=start_node,\n... nodes=[start_node, prompt_node, input_node, llm_node, output_node, end_node],\n... control_flow_connections=[\n... ControlFlowEdge(name=\"ce1\", from_node=start_node, to_node=prompt_node),\n... ControlFlowEdge(name=\"ce2\", from_node=prompt_node, to_node=input_node),\n... ControlFlowEdge(name=\"ce3\", from_node=input_node, to_node=llm_node),\n... ControlFlowEdge(name=\"ce4\", from_node=llm_node, to_node=output_node),\n... ControlFlowEdge(name=\"ce5\", from_node=output_node, to_node=end_node),\n... ],\n... data_flow_connections=[\n... DataFlowEdge(\n... name=\"de1\",\n... source_node=input_node,\n... source_output=\"user_input\",\n... destination_node=llm_node,\n... destination_input=\"user_input\",\n... ),\n... DataFlowEdge(\n... name=\"de2\",\n... source_node=llm_node,\n... source_output=\"rephrased_user_input\",\n... destination_node=output_node,\n... destination_input=\"rephrased_user_input\",\n... ),\n... ]\n... )", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "inputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Inputs" + }, + "outputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Outputs" + }, + "branches": { + "items": { + "type": "string" + }, + "title": "Branches", + "type": "array" + }, + "message": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Message" + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "InputMessageNode" + } + }, + "required": [ + "name" + ], + "title": "InputMessageNode", + "type": "object", + "x-abstract-component": false + }, + "BaseLlmConfig": { + "anyOf": [ + { + "$ref": "#/$defs/DbmsVectorChainLlmConfig" + }, + { + "$ref": "#/$defs/GeminiConfig" + }, + { + "$ref": "#/$defs/OciGenAiConfig" + }, + { + "$ref": "#/$defs/OllamaConfig" + }, + { + "$ref": "#/$defs/OpenAiCompatibleConfig" + }, + { + "$ref": "#/$defs/OpenAiConfig" + }, + { + "$ref": "#/$defs/VllmConfig" + }, + { + "additionalProperties": false, + "description": "A LLM configuration defines how to connect to a LLM to do generation requests.\n\nThis class can be used directly with the ``provider``, ``api_provider``, and ``api_type``\nfields to describe any LLM without a dedicated subclass. Concrete subclasses provide\nadditional configuration for specific LLM providers.", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "model_id": { + "title": "Model Id", + "type": "string" + }, + "provider": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Provider" + }, + "api_provider": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Api Provider" + }, + "api_type": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Api Type" + }, + "url": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Url" + }, + "api_key": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Api Key" + }, + "default_generation_parameters": { + "anyOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/LlmGenerationConfig" + } + ], + "default": null + }, + "retry_policy": { + "anyOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/RetryPolicy" + } + ], + "default": null + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "LlmConfig" + } + }, + "required": [ + "model_id", + "name" + ], + "title": "LlmConfig", + "type": "object" + } + ], + "x-abstract-component": false + }, + "BaseLlmNode": { + "additionalProperties": false, + "description": "Execute a prompt template with a given LLM.\n\nThis node is intended to be a part of a Flow.\n\n- **Inputs**\n One per placeholder in the prompt template.\n- **Outputs**\n The output text generated by the LLM.\n\n If None is given, ``pyagentspec`` infers a string property named ``generated_text``.\n- **Branches**\n One, the default next.\n\nExample\n-------\n>>> from pyagentspec.property import Property\n>>> from pyagentspec.flows.flow import Flow\n>>> from pyagentspec.flows.edges import ControlFlowEdge, DataFlowEdge\n>>> from pyagentspec.flows.nodes import LlmNode, StartNode, EndNode\n>>> country_property = Property(\n... json_schema={\"title\": \"country\", \"type\": \"string\"}\n... )\n>>> capital_property = Property(\n... json_schema={\"title\": \"capital\", \"type\": \"string\"}\n... )\n>>> start_node = StartNode(name=\"start\", inputs=[country_property])\n>>> end_node = EndNode(name=\"end\", outputs=[capital_property])\n>>> llm_node = LlmNode(\n... name=\"simple llm node\",\n... llm_config=llm_config,\n... prompt_template=\"What is the capital of {{ country }}?\",\n... inputs=[country_property],\n... outputs=[capital_property],\n... )\n>>> flow = Flow(\n... name=\"Get the country's capital flow\",\n... start_node=start_node,\n... nodes=[start_node, llm_node, end_node],\n... control_flow_connections=[\n... ControlFlowEdge(name=\"start_to_llm\", from_node=start_node, to_node=llm_node),\n... ControlFlowEdge(name=\"llm_to_end\", from_node=llm_node, to_node=end_node),\n... ],\n... data_flow_connections=[\n... DataFlowEdge(\n... name=\"country_edge\",\n... source_node=start_node,\n... source_output=\"country\",\n... destination_node=llm_node,\n... destination_input=\"country\",\n... ),\n... DataFlowEdge(\n... name=\"capital_edge\",\n... source_node=llm_node,\n... source_output=\"capital\",\n... destination_node=end_node,\n... destination_input=\"capital\"\n... ),\n... ],\n... )", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "inputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Inputs" + }, + "outputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Outputs" + }, + "branches": { + "items": { + "type": "string" + }, + "title": "Branches", + "type": "array" + }, + "llm_config": { + "$ref": "#/$defs/LlmConfig" + }, + "prompt_template": { + "title": "Prompt Template", + "type": "string" + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "LlmNode" + } + }, + "required": [ + "llm_config", + "name", + "prompt_template" + ], + "title": "LlmNode", + "type": "object", + "x-abstract-component": false + }, + "BaseMCPTool": { + "additionalProperties": false, + "description": "Class for tools exposed by MCP servers", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "inputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Inputs" + }, + "outputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Outputs" + }, + "requires_confirmation": { + "default": false, + "title": "Requires Confirmation", + "type": "boolean" + }, + "client_transport": { + "$ref": "#/$defs/ClientTransport" + }, + "retry_policy": { + "anyOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/RetryPolicy" + } + ], + "default": null + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "MCPTool" + } + }, + "required": [ + "client_transport", + "name" + ], + "title": "MCPTool", + "type": "object", + "x-abstract-component": false + }, + "BaseMCPToolBox": { + "additionalProperties": false, + "description": "Class to dynamically expose a list of tools from a MCP Server.", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "requires_confirmation": { + "default": false, + "title": "Requires Confirmation", + "type": "boolean" + }, + "client_transport": { + "$ref": "#/$defs/ClientTransport" + }, + "retry_policy": { + "anyOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/RetryPolicy" + } + ], + "default": null + }, + "tool_filter": { + "anyOf": [ + { + "items": { + "anyOf": [ + { + "type": "string" + }, + { + "$ref": "#/$defs/MCPToolSpec" + } + ] + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Tool Filter" + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "MCPToolBox" + } + }, + "required": [ + "client_transport", + "name" + ], + "title": "MCPToolBox", + "type": "object", + "x-abstract-component": false + }, + "BaseMCPToolSpec": { + "additionalProperties": false, + "description": "Specification of MCP tool", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "inputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Inputs" + }, + "outputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Outputs" + }, + "requires_confirmation": { + "default": false, + "title": "Requires Confirmation", + "type": "boolean" + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "MCPToolSpec" + } + }, + "required": [ + "name" + ], + "title": "MCPToolSpec", + "type": "object", + "x-abstract-component": false + }, + "BaseMTlsOracleDatabaseConnectionConfig": { + "additionalProperties": false, + "description": "Mutual-TLS Connection Configuration to Oracle Database.", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "user": { + "title": "User", + "type": "string" + }, + "password": { + "title": "Password", + "type": "string" + }, + "dsn": { + "title": "Dsn", + "type": "string" + }, + "config_dir": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Config Dir" + }, + "protocol": { + "default": "tcps", + "enum": [ + "tcp", + "tcps" + ], + "title": "Protocol", + "type": "string" + }, + "wallet_location": { + "title": "Wallet Location", + "type": "string" + }, + "wallet_password": { + "title": "Wallet Password", + "type": "string" + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "MTlsOracleDatabaseConnectionConfig" + } + }, + "required": [ + "dsn", + "name", + "password", + "user", + "wallet_location", + "wallet_password" + ], + "title": "MTlsOracleDatabaseConnectionConfig", + "type": "object", + "x-abstract-component": false + }, + "BaseManagerWorkers": { + "additionalProperties": false, + "description": "Defines a ``ManagerWorkers`` conversational component.\n\nA ``ManagerWorkers`` is a multi-agent conversational component in which a group manager\nassigns tasks to the workers. The group manager and workers can be instantiated from\nany ``AgenticComponent`` type.\n\nExamples\n--------\n>>> from pyagentspec.agent import Agent\n>>> from pyagentspec.managerworkers import ManagerWorkers\n>>> manager_agent = Agent(\n... name=\"manager_agent\",\n... description=\"Agent that manages a group of math agents\",\n... llm_config=llm_config,\n... system_prompt=\"You are the manager of a group of math agents\"\n... )\n>>> multiplication_agent = Agent(\n... name=\"multiplication_agent\",\n... description=\"Agent that can do multiplication\",\n... llm_config=llm_config,\n... system_prompt=\"You can do multiplication.\"\n... )\n>>> division_agent = Agent(\n... name=\"division_agent\",\n... description=\"Agent that can do division\",\n... llm_config=llm_config,\n... system_prompt=\"You can do division.\"\n... )\n>>> group = ManagerWorkers(\n... name=\"managerworkers\",\n... group_manager=manager_agent,\n... workers=[multiplication_agent, division_agent],\n... )", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "inputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Inputs" + }, + "outputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Outputs" + }, + "group_manager": { + "$ref": "#/$defs/AgenticComponent" + }, + "workers": { + "items": { + "$ref": "#/$defs/AgenticComponent" + }, + "title": "Workers", + "type": "array" + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "ManagerWorkers" + } + }, + "required": [ + "group_manager", + "name", + "workers" + ], + "title": "ManagerWorkers", + "type": "object", + "x-abstract-component": false + }, + "BaseMapNode": { + "additionalProperties": false, + "description": "The map node executes a subflow on each element of a given input as part of a flow.\n\n- **Inputs**\n Inferred from the inner structure. It's the sets of inputs\n required by the StartNode of the inner flow.\n The names of the inputs will be the ones of the inner flow,\n complemented with the ``iterated_`` prefix. Their type is\n ``Union[inner_type, List[inner_type]]``, where ``inner_type``\n is the type of the respective input in the inner flow.\n\n If None is given, ``pyagentspec`` infers input properties directly from the inner flow,\n specifying title and type according to the rules defined above.\n\n- **Outputs**\n Inferred from the inner structure. It's the union of the\n sets of outputs exposed by the EndNodes of the inner flow,\n combined with the reducer method of each output.\n The names of the outputs will be the ones of the inner flow,\n complemented with the ``collected_`` prefix. Their type depends\n on the ``reduce`` method specified for that output:\n\n - ``List`` of the respective output type in case of ``append``\n - same type of the respective output type in case of ``sum``, ``avg``\n\n If None is given, ``pyagentspec`` infers outputs by exposing\n an output property for each entry in the ``reducers`` dictionary, specifying title\n and type according to the rules defined above.\n\n- **Branches**\n One, the default next.\n\nExamples\n--------\nIn this example we will create a flow that returns\nan L2-normalized version of a given list of numbers.\n\n>>> from pyagentspec.property import Property\n>>> from pyagentspec.flows.edges import ControlFlowEdge, DataFlowEdge\n>>> from pyagentspec.flows.flow import Flow\n>>> from pyagentspec.flows.nodes import EndNode, StartNode, MapNode, ToolNode\n>>> from pyagentspec.tools import ServerTool\n\nFirst, we define a MapNode that returns the square of all the elements in a list.\nIt will be used to compute the L2-norm.\n\n>>> x_property = Property(json_schema={\"title\": \"x\", \"type\": \"number\"})\n>>> x_square_property = Property(\n... json_schema={\"title\": \"x_square\", \"type\": \"number\"}\n... )\n>>> square_tool = ServerTool(\n... name=\"compute_square_tool\",\n... description=\"Computes the square of a number\",\n... inputs=[x_property],\n... outputs=[x_square_property],\n... )\n>>> list_of_x_property = Property(\n... json_schema={\"title\": \"x_list\", \"type\": \"array\", \"items\": {\"type\": \"number\"}}\n... )\n>>> start_node = StartNode(name=\"start\", inputs=[x_property])\n>>> end_node = EndNode(name=\"end\", outputs=[x_square_property])\n>>> square_tool_node = ToolNode(name=\"square tool node\", tool=square_tool)\n>>> square_number_flow = Flow(\n... name=\"Square number flow\",\n... start_node=start_node,\n... nodes=[start_node, square_tool_node, end_node],\n... control_flow_connections=[\n... ControlFlowEdge(\n... name=\"start_to_tool\", from_node=start_node, to_node=square_tool_node\n... ),\n... ControlFlowEdge(\n... name=\"tool_to_end\", from_node=square_tool_node, to_node=end_node\n... ),\n... ],\n... data_flow_connections=[\n... DataFlowEdge(\n... name=\"x_edge\",\n... source_node=start_node,\n... source_output=\"x\",\n... destination_node=square_tool_node,\n... destination_input=\"x\",\n... ),\n... DataFlowEdge(\n... name=\"x_square_edge\",\n... source_node=square_tool_node,\n... source_output=\"x_square\",\n... destination_node=end_node,\n... destination_input=\"x_square\",\n... ),\n... ],\n... )\n>>> list_of_x_square_property = Property(\n... json_schema={\"title\": \"x_square_list\", \"type\": \"array\", \"items\": {\"type\": \"number\"}}\n... )\n>>> square_numbers_map_node = MapNode(\n... name=\"square number map node\",\n... subflow=square_number_flow,\n... )\n\nNow we define the MapNode responsible for normalizing the given list of input numbers.\nThe denominator is the same for all of the numbers,\nwe are going to map only the numerators (i.e., the input numbers).\n\n>>> numerator_property = Property(\n... json_schema={\"title\": \"numerator\", \"type\": \"number\"}\n... )\n>>> denominator_property = Property(\n... json_schema={\"title\": \"denominator\", \"type\": \"number\"}\n... )\n>>> result_property = Property(\n... json_schema={\"title\": \"result\", \"type\": \"number\"}\n... )\n>>> division_tool = ServerTool(\n... name=\"division_tool\",\n... description=\"Computes the ratio between two numbers\",\n... inputs=[numerator_property, denominator_property],\n... outputs=[result_property],\n... )\n>>> start_node = StartNode(name=\"start\", inputs=[numerator_property, denominator_property])\n>>> end_node = EndNode(name=\"end\", outputs=[result_property])\n>>> divide_node = ToolNode(name=\"divide node\", tool=division_tool)\n>>> normalize_flow = Flow(\n... name=\"Normalize flow\",\n... start_node=start_node,\n... nodes=[start_node, divide_node, end_node],\n... control_flow_connections=[\n... ControlFlowEdge(name=\"start_to_tool\", from_node=start_node, to_node=divide_node),\n... ControlFlowEdge(name=\"tool_to_end\", from_node=divide_node, to_node=end_node),\n... ],\n... data_flow_connections=[\n... DataFlowEdge(\n... name=\"numerator_edge\",\n... source_node=start_node,\n... source_output=\"numerator\",\n... destination_node=divide_node,\n... destination_input=\"numerator\",\n... ),\n... DataFlowEdge(\n... name=\"denominator_edge\",\n... source_node=start_node,\n... source_output=\"denominator\",\n... destination_node=divide_node,\n... destination_input=\"denominator\",\n... ),\n... DataFlowEdge(\n... name=\"result_edge\",\n... source_node=divide_node,\n... source_output=\"result\",\n... destination_node=end_node,\n... destination_input=\"result\",\n... ),\n... ],\n... )\n\nFinally, we define the overall flow:\n\n- The list of inputs is squared\n- The squared list is summed and root squared\n- The list of inputs is normalized based on the outcomes of the previous 2 steps\n\n>>> squared_sum_property = Property(\n... json_schema={\"title\": \"squared_sum\", \"type\": \"number\"}\n... )\n>>> normalized_list_of_x_property = Property(\n... json_schema={\n... \"title\": \"x_list_normalized\",\n... \"type\": \"array\",\n... \"items\": {\"type\": \"number\"},\n... }\n... )\n>>> normalize_map_node = MapNode(\n... name=\"normalize map node\",\n... subflow=normalize_flow,\n... )\n>>> squared_sum_tool = ServerTool(\n... name=\"squared_sum_tool\",\n... description=\"Computes the squared sum of a list of numbers\",\n... inputs=[list_of_x_property],\n... outputs=[squared_sum_property],\n... )\n>>> start_node = StartNode(name=\"start\", inputs=[list_of_x_property])\n>>> end_node = EndNode(name=\"end\", outputs=[normalized_list_of_x_property])\n>>> squared_sum_tool_node = ToolNode(name=\"squared sum tool node\", tool=squared_sum_tool)\n>>> flow = Flow(\n... name=\"L2 normalize flow\",\n... start_node=start_node,\n... nodes=[\n... start_node,\n... square_numbers_map_node,\n... squared_sum_tool_node,\n... normalize_map_node,\n... end_node,\n... ],\n... control_flow_connections=[\n... ControlFlowEdge(\n... name=\"start_to_square_numbers\",\n... from_node=start_node,\n... to_node=square_numbers_map_node\n... ),\n... ControlFlowEdge(\n... name=\"square_numbers_to_squared_sum_tool\",\n... from_node=square_numbers_map_node,\n... to_node=squared_sum_tool_node\n... ),\n... ControlFlowEdge(\n... name=\"squared_sum_tool_to_normalize\",\n... from_node=squared_sum_tool_node,\n... to_node=normalize_map_node\n... ),\n... ControlFlowEdge(\n... name=\"normalize_to_end\",\n... from_node=normalize_map_node,\n... to_node=end_node\n... ),\n... ],\n... data_flow_connections=[\n... DataFlowEdge(\n... name=\"list_of_x_edge\",\n... source_node=start_node,\n... source_output=\"x_list\",\n... destination_node=square_numbers_map_node,\n... destination_input=\"iterated_x\",\n... ),\n... DataFlowEdge(\n... name=\"x_square_list_edge\",\n... source_node=square_numbers_map_node,\n... source_output=\"collected_x_square\",\n... destination_node=squared_sum_tool_node,\n... destination_input=\"x_list\",\n... ),\n... DataFlowEdge(\n... name=\"numerator_edge\",\n... source_node=start_node,\n... source_output=\"x_list\",\n... destination_node=normalize_map_node,\n... destination_input=\"iterated_numerator\",\n... ),\n... DataFlowEdge(\n... name=\"denominator_edge\",\n... source_node=squared_sum_tool_node,\n... source_output=\"squared_sum\",\n... destination_node=normalize_map_node,\n... destination_input=\"iterated_denominator\",\n... ),\n... DataFlowEdge(\n... name=\"x_list_normalized_edge\",\n... source_node=normalize_map_node,\n... source_output=\"collected_result\",\n... destination_node=end_node,\n... destination_input=\"x_list_normalized\",\n... ),\n... ],\n... )", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "inputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Inputs" + }, + "outputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Outputs" + }, + "branches": { + "items": { + "type": "string" + }, + "title": "Branches", + "type": "array" + }, + "subflow": { + "$ref": "#/$defs/Flow" + }, + "reducers": { + "anyOf": [ + { + "additionalProperties": { + "$ref": "#/$defs/ReductionMethod" + }, + "type": "object" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Reducers" + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "MapNode" + } + }, + "required": [ + "name", + "subflow" + ], + "title": "MapNode", + "type": "object", + "x-abstract-component": false + }, + "BaseMessageSummarizationTransform": { + "additionalProperties": false, + "description": "Summarizes oversized messages using an LLM and optionally caches summaries.\n\nThis is useful for long conversations where the context can become too large for the LLM to handle.\n\nExamples\n--------\n>>> from pyagentspec.transforms import MessageSummarizationTransform\n>>> summarization_transform = MessageSummarizationTransform(\n... name=\"message-summarizer\",\n... llm=llm_config,\n... max_message_size=30_000\n... )", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "llm": { + "$ref": "#/$defs/LlmConfig" + }, + "max_message_size": { + "default": 20000, + "title": "Max Message Size", + "type": "integer" + }, + "summarization_instructions": { + "default": "Please make a summary of this message. Include relevant information and keep it short. Your response will replace the message, so just output the summary directly, no introduction needed.", + "title": "Summarization Instructions", + "type": "string" + }, + "summarized_message_template": { + "default": "Summarized message: {{summary}}", + "title": "Summarized Message Template", + "type": "string" + }, + "max_cache_size": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "default": 10000, + "title": "Max Cache Size" + }, + "max_cache_lifetime": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "default": 14400, + "title": "Max Cache Lifetime" + }, + "cache_collection_name": { + "default": "summarized_messages_cache", + "title": "Cache Collection Name", + "type": "string" + }, + "datastore": { + "anyOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/InMemoryCollectionDatastore" + }, + { + "$ref": "#/$defs/OracleDatabaseDatastore" + }, + { + "$ref": "#/$defs/PostgresDatabaseDatastore" + } + ], + "title": "Datastore" + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "MessageSummarizationTransform" + } + }, + "required": [ + "llm", + "name" + ], + "title": "MessageSummarizationTransform", + "type": "object", + "x-abstract-component": false + }, + "BaseMessageTransform": { + "anyOf": [ + { + "$ref": "#/$defs/ConversationSummarizationTransform" + }, + { + "$ref": "#/$defs/MessageSummarizationTransform" + } + ], + "x-abstract-component": true + }, + "BaseNode": { + "anyOf": [ + { + "$ref": "#/$defs/AgentNode" + }, + { + "$ref": "#/$defs/ApiNode" + }, + { + "$ref": "#/$defs/BranchingNode" + }, + { + "$ref": "#/$defs/CatchExceptionNode" + }, + { + "$ref": "#/$defs/EndNode" + }, + { + "$ref": "#/$defs/FlowNode" + }, + { + "$ref": "#/$defs/InputMessageNode" + }, + { + "$ref": "#/$defs/LlmNode" + }, + { + "$ref": "#/$defs/MapNode" + }, + { + "$ref": "#/$defs/OutputMessageNode" + }, + { + "$ref": "#/$defs/ParallelFlowNode" + }, + { + "$ref": "#/$defs/ParallelMapNode" + }, + { + "$ref": "#/$defs/StartNode" + }, + { + "$ref": "#/$defs/ToolNode" + } + ], + "x-abstract-component": true + }, + "BaseOAuthClientConfig": { + "additionalProperties": false, + "description": "OAuth client identity / registration configuration.\n\nThis configuration describes how the runtime establishes the OAuth client\nidentity to use with the authorization server. It supports:\n- Pre-registered clients (static client_id/client_secret)\n- Client ID Metadata Documents (URL-formatted client_id)\n- Dynamic client registration (RFC 7591)", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "min_agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum", + "default": "26.1.2" + }, + "type": { + "enum": [ + "client_id_metadata_document", + "dynamic_registration", + "pre_registered" + ], + "title": "Type", + "type": "string" + }, + "client_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Client Id" + }, + "client_secret": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Client Secret" + }, + "token_endpoint_auth_method": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Token Endpoint Auth Method" + }, + "client_id_metadata_url": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Client Id Metadata Url" + }, + "registration_endpoint": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Registration Endpoint" + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "OAuthClientConfig" + } + }, + "required": [ + "name", + "type" + ], + "title": "OAuthClientConfig", + "type": "object", + "x-abstract-component": false + }, + "BaseOAuthConfig": { + "additionalProperties": false, + "description": "Configure OAuth-based authentication for a tool or transport.\n\nOAuthConfig is a generic configuration that can be used for both MCP servers\nand non-MCP remote API tools. It supports discovery-based configuration (via\n``issuer``) and explicit endpoints (via ``endpoints``).", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "min_agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum", + "default": "26.1.2" + }, + "issuer": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Issuer" + }, + "endpoints": { + "anyOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/OAuthEndpoints" + } + ], + "default": null + }, + "client": { + "$ref": "#/$defs/OAuthClientConfig" + }, + "redirect_uri": { + "title": "Redirect Uri", + "type": "string" + }, + "scopes": { + "anyOf": [ + { + "type": "string" + }, + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Scopes" + }, + "scope_policy": { + "anyOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/ScopePolicy" + } + ], + "default": null + }, + "pkce": { + "anyOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/PKCEPolicy" + } + ], + "default": null + }, + "resource": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Resource" + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "OAuthConfig" + } + }, + "required": [ + "client", + "name", + "redirect_uri" + ], + "title": "OAuthConfig", + "type": "object", + "x-abstract-component": false + }, + "BaseOciAgent": { + "additionalProperties": false, + "description": "An agent is a component that can do several rounds of conversation to solve a task.\n\nThe agent is defined on the OCI console and this is only a wrapper to connect to it.\nIt can be executed by itself, or be executed in a flow using an AgentNode.", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "inputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Inputs" + }, + "outputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Outputs" + }, + "agent_endpoint_id": { + "title": "Agent Endpoint Id", + "type": "string" + }, + "client_config": { + "$ref": "#/$defs/OciClientConfig" + }, + "retry_policy": { + "anyOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/RetryPolicy" + } + ], + "default": null + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "OciAgent" + } + }, + "required": [ + "agent_endpoint_id", + "client_config", + "name" + ], + "title": "OciAgent", + "type": "object", + "x-abstract-component": false + }, + "BaseOciClientConfig": { + "anyOf": [ + { + "$ref": "#/$defs/OciClientConfigWithApiKey" + }, + { + "$ref": "#/$defs/OciClientConfigWithInstancePrincipal" + }, + { + "$ref": "#/$defs/OciClientConfigWithResourcePrincipal" + }, + { + "$ref": "#/$defs/OciClientConfigWithSecurityToken" + } + ], + "x-abstract-component": true + }, + "BaseOciClientConfigWithApiKey": { + "additionalProperties": false, + "description": "OCI client config class for authentication using API_KEY and a config file.", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "service_endpoint": { + "title": "Service Endpoint", + "type": "string" + }, + "auth_type": { + "const": "API_KEY", + "default": "API_KEY", + "title": "Auth Type", + "type": "string" + }, + "auth_profile": { + "title": "Auth Profile", + "type": "string" + }, + "auth_file_location": { + "title": "Auth File Location", + "type": "string" + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "OciClientConfigWithApiKey" + } + }, + "required": [ + "auth_file_location", + "auth_profile", + "name", + "service_endpoint" + ], + "title": "OciClientConfigWithApiKey", + "type": "object", + "x-abstract-component": false + }, + "BaseOciClientConfigWithInstancePrincipal": { + "additionalProperties": false, + "description": "OCI client config class for authentication using INSTANCE_PRINCIPAL.", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "service_endpoint": { + "title": "Service Endpoint", + "type": "string" + }, + "auth_type": { + "const": "INSTANCE_PRINCIPAL", + "default": "INSTANCE_PRINCIPAL", + "title": "Auth Type", + "type": "string" + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "OciClientConfigWithInstancePrincipal" + } + }, + "required": [ + "name", + "service_endpoint" + ], + "title": "OciClientConfigWithInstancePrincipal", + "type": "object", + "x-abstract-component": false + }, + "BaseOciClientConfigWithResourcePrincipal": { + "additionalProperties": false, + "description": "OCI client config class for authentication using RESOURCE_PRINCIPAL.", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "service_endpoint": { + "title": "Service Endpoint", + "type": "string" + }, + "auth_type": { + "const": "RESOURCE_PRINCIPAL", + "default": "RESOURCE_PRINCIPAL", + "title": "Auth Type", + "type": "string" + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "OciClientConfigWithResourcePrincipal" + } + }, + "required": [ + "name", + "service_endpoint" + ], + "title": "OciClientConfigWithResourcePrincipal", + "type": "object", + "x-abstract-component": false + }, + "BaseOciClientConfigWithSecurityToken": { + "additionalProperties": false, + "description": "OCI client config class for authentication using SECURITY_TOKEN.", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "service_endpoint": { + "title": "Service Endpoint", + "type": "string" + }, + "auth_type": { + "const": "SECURITY_TOKEN", + "default": "SECURITY_TOKEN", + "title": "Auth Type", + "type": "string" + }, + "auth_profile": { + "title": "Auth Profile", + "type": "string" + }, + "auth_file_location": { + "title": "Auth File Location", + "type": "string" + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "OciClientConfigWithSecurityToken" + } + }, + "required": [ + "auth_file_location", + "auth_profile", + "name", + "service_endpoint" + ], + "title": "OciClientConfigWithSecurityToken", + "type": "object", + "x-abstract-component": false + }, + "BaseOciGenAiConfig": { + "additionalProperties": false, + "description": "Class to configure a connection to a OCI GenAI hosted model.\n\nRequires to specify the model id and the client configuration to the OCI GenAI service.", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "model_id": { + "title": "Model Id", + "type": "string" + }, + "provider": { + "anyOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/ModelProvider" + } + ], + "default": null + }, + "api_provider": { + "const": "oci", + "default": "oci", + "title": "Api Provider", + "type": "string" + }, + "api_type": { + "$ref": "#/$defs/OciAPIType", + "default": "oci" + }, + "url": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Url" + }, + "api_key": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Api Key" + }, + "default_generation_parameters": { + "anyOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/LlmGenerationConfig" + } + ], + "default": null + }, + "retry_policy": { + "anyOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/RetryPolicy" + } + ], + "default": null + }, + "compartment_id": { + "title": "Compartment Id", + "type": "string" + }, + "serving_mode": { + "$ref": "#/$defs/ServingMode", + "default": "ON_DEMAND" + }, + "client_config": { + "$ref": "#/$defs/OciClientConfig" + }, + "conversation_store_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Conversation Store Id" + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "OciGenAiConfig" + } + }, + "required": [ + "client_config", + "compartment_id", + "model_id", + "name" + ], + "title": "OciGenAiConfig", + "type": "object", + "x-abstract-component": false + }, + "BaseOllamaConfig": { + "additionalProperties": false, + "description": "Class to configure a connection to a local model ran with Ollama.\n\nRequires to specify the url and port at which the model is running.", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "model_id": { + "title": "Model Id", + "type": "string" + }, + "provider": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Provider" + }, + "api_provider": { + "const": "ollama", + "default": "ollama", + "title": "Api Provider", + "type": "string" + }, + "api_type": { + "$ref": "#/$defs/OpenAIAPIType", + "default": "chat_completions" + }, + "url": { + "title": "Url", + "type": "string" + }, + "api_key": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Api Key" + }, + "default_generation_parameters": { + "anyOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/LlmGenerationConfig" + } + ], + "default": null + }, + "retry_policy": { + "anyOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/RetryPolicy" + } + ], + "default": null + }, + "key_file": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Key File" + }, + "cert_file": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Cert File" + }, + "ca_file": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Ca File" + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "OllamaConfig" + } + }, + "required": [ + "model_id", + "name", + "url" + ], + "title": "OllamaConfig", + "type": "object", + "x-abstract-component": false + }, + "BaseOpenAiCompatibleConfig": { + "anyOf": [ + { + "$ref": "#/$defs/OllamaConfig" + }, + { + "$ref": "#/$defs/VllmConfig" + }, + { + "additionalProperties": false, + "description": "Class to configure a connection to an LLM that is compatible with OpenAI completions APIs.\n\nRequires to specify the url of the APIs to contact.", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "model_id": { + "title": "Model Id", + "type": "string" + }, + "provider": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Provider" + }, + "api_provider": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Api Provider" + }, + "api_type": { + "$ref": "#/$defs/OpenAIAPIType", + "default": "chat_completions" + }, + "url": { + "title": "Url", + "type": "string" + }, + "api_key": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Api Key" + }, + "default_generation_parameters": { + "anyOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/LlmGenerationConfig" + } + ], + "default": null + }, + "retry_policy": { + "anyOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/RetryPolicy" + } + ], + "default": null + }, + "key_file": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Key File" + }, + "cert_file": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Cert File" + }, + "ca_file": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Ca File" + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "OpenAiCompatibleConfig" + } + }, + "required": [ + "model_id", + "name", + "url" + ], + "title": "OpenAiCompatibleConfig", + "type": "object" + } + ], + "x-abstract-component": false + }, + "BaseOpenAiConfig": { + "additionalProperties": false, + "description": "Class to configure a connection to a OpenAI LLM.\n\nRequires to specify the identity of the model to use.", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "model_id": { + "title": "Model Id", + "type": "string" + }, + "provider": { + "const": "openai", + "default": "openai", + "title": "Provider", + "type": "string" + }, + "api_provider": { + "const": "openai", + "default": "openai", + "title": "Api Provider", + "type": "string" + }, + "api_type": { + "$ref": "#/$defs/OpenAIAPIType", + "default": "chat_completions" + }, + "url": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Url" + }, + "api_key": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Api Key" + }, + "default_generation_parameters": { + "anyOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/LlmGenerationConfig" + } + ], + "default": null + }, + "retry_policy": { + "anyOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/RetryPolicy" + } + ], + "default": null + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "OpenAiConfig" + } + }, + "required": [ + "model_id", + "name" + ], + "title": "OpenAiConfig", + "type": "object", + "x-abstract-component": false + }, + "BaseOracleDatabaseConnectionConfig": { + "anyOf": [ + { + "$ref": "#/$defs/MTlsOracleDatabaseConnectionConfig" + }, + { + "$ref": "#/$defs/TlsOracleDatabaseConnectionConfig" + } + ], + "x-abstract-component": true + }, + "BaseOracleDatabaseDatastore": { + "additionalProperties": false, + "description": "Datastore that uses Oracle Database as the storage mechanism.", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "datastore_schema": { + "additionalProperties": { + "$ref": "#/$defs/Property" + }, + "title": "Datastore Schema", + "type": "object" + }, + "connection_config": { + "$ref": "#/$defs/OracleDatabaseConnectionConfig" + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "OracleDatabaseDatastore" + } + }, + "required": [ + "connection_config", + "datastore_schema", + "name" + ], + "title": "OracleDatabaseDatastore", + "type": "object", + "x-abstract-component": false + }, + "BaseOutputMessageNode": { + "additionalProperties": false, + "description": "This node appends an agent message to the ongoing flow conversation.\n\n- **Inputs**\n One per variable in the message.\n- **Outputs**\n No outputs.\n- **Branches**\n One, the default next.\n\nExamples\n--------\n>>> from pyagentspec.flows.edges import ControlFlowEdge, DataFlowEdge\n>>> from pyagentspec.flows.flow import Flow\n>>> from pyagentspec.flows.nodes import StartNode, EndNode, InputMessageNode, OutputMessageNode, LlmNode\n>>> from pyagentspec.property import StringProperty\n>>> start_node = StartNode(name=\"start\")\n>>> prompt_node = OutputMessageNode(name=\"ask_input\", message=\"What is the paragraph you want to rephrase?\")\n>>> input_node = InputMessageNode(name=\"user_input\", outputs=[StringProperty(title=\"user_input\")])\n>>> llm_node = LlmNode(\n... name=\"rephrase\",\n... llm_config=llm_config,\n... prompt_template=\"Rephrase {{user_input}}\",\n... outputs=[StringProperty(title=\"rephrased_user_input\")],\n... )\n>>> output_node = OutputMessageNode(name=\"ask_input\", message=\"{{rephrased_user_input}}\")\n>>> end_node = EndNode(name=\"end\")\n>>> flow = Flow(\n... name=\"rephrase_paragraph_flow\",\n... start_node=start_node,\n... nodes=[start_node, prompt_node, input_node, llm_node, output_node, end_node],\n... control_flow_connections=[\n... ControlFlowEdge(name=\"ce1\", from_node=start_node, to_node=prompt_node),\n... ControlFlowEdge(name=\"ce2\", from_node=prompt_node, to_node=input_node),\n... ControlFlowEdge(name=\"ce3\", from_node=input_node, to_node=llm_node),\n... ControlFlowEdge(name=\"ce4\", from_node=llm_node, to_node=output_node),\n... ControlFlowEdge(name=\"ce5\", from_node=output_node, to_node=end_node),\n... ],\n... data_flow_connections=[\n... DataFlowEdge(\n... name=\"de1\",\n... source_node=input_node,\n... source_output=\"user_input\",\n... destination_node=llm_node,\n... destination_input=\"user_input\",\n... ),\n... DataFlowEdge(\n... name=\"de2\",\n... source_node=llm_node,\n... source_output=\"rephrased_user_input\",\n... destination_node=output_node,\n... destination_input=\"rephrased_user_input\",\n... ),\n... ]\n... )", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "inputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Inputs" + }, + "outputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Outputs" + }, + "branches": { + "items": { + "type": "string" + }, + "title": "Branches", + "type": "array" + }, + "message": { + "title": "Message", + "type": "string" + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "OutputMessageNode" + } + }, + "required": [ + "message", + "name" + ], + "title": "OutputMessageNode", + "type": "object", + "x-abstract-component": false + }, + "BaseParallelFlowNode": { + "additionalProperties": false, + "description": "The parallel flow node executes multiple subflows in parallel.\n\n- **Inputs**\n Inferred from the inner structure. It's the union of the sets of inputs of the inner flows.\n Inputs of different inner flows that have the same name are merged if they have the same type.\n\n- **Outputs**\n Inferred from the inner structure. It's the union of the outputs of the inner flows.\n Outputs of different inner flows that have the same name are not allowed.\n\n- **Branches**\n One, the default next.\n\nExamples\n--------", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "inputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Inputs" + }, + "outputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Outputs" + }, + "branches": { + "items": { + "type": "string" + }, + "title": "Branches", + "type": "array" + }, + "subflows": { + "items": { + "$ref": "#/$defs/Flow" + }, + "title": "Subflows", + "type": "array" + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "ParallelFlowNode" + } + }, + "required": [ + "name" + ], + "title": "ParallelFlowNode", + "type": "object", + "x-abstract-component": false + }, + "BaseParallelMapNode": { + "additionalProperties": false, + "description": "The parallel map node executes a subflow on each element of a given input in a parallel manner.\n\n- **Inputs**\n Inferred from the inner structure. It's the sets of inputs\n required by the StartNode of the inner flow.\n The names of the inputs will be the ones of the inner flow,\n complemented with the ``iterated_`` prefix. Their type is\n ``Union[inner_type, List[inner_type]]``, where ``inner_type``\n is the type of the respective input in the inner flow.\n\n If None is given, ``pyagentspec`` infers input properties directly from the inner flow,\n specifying title and type according to the rules defined above.\n\n- **Outputs**\n Inferred from the inner structure. It's the union of the\n sets of outputs exposed by the EndNodes of the inner flow,\n combined with the reducer method of each output.\n The names of the outputs will be the ones of the inner flow,\n complemented with the ``collected_`` prefix. Their type depends\n on the ``reduce`` method specified for that output:\n\n - ``List`` of the respective output type in case of ``append``\n - same type of the respective output type in case of ``sum``, ``avg``\n\n If None is given, ``pyagentspec`` infers outputs by exposing\n an output property for each entry in the ``reducers`` dictionary, specifying title\n and type according to the rules defined above.\n\n- **Branches**\n One, the default next.\n\nExamples\n--------\nIn this example we create a flow that generates a summary of the given articles that talk about LLMs.\n\n>>> from pyagentspec.property import BooleanProperty, StringProperty, ListProperty\n>>> from pyagentspec.flows.edges import ControlFlowEdge, DataFlowEdge\n>>> from pyagentspec.flows.flow import Flow\n>>> from pyagentspec.flows.nodes import EndNode, StartNode, ParallelMapNode, LlmNode, BranchingNode\n>>> from pyagentspec.flows.node import Node\n\nFirst we define the flow that determines if an article talks about LLMs or not:\n\n- if it does, we return it, so that we will use it for our summary\n- if it does not, we don't return its text\n\n>>> def create_data_flow_edge(source_node: Node, destination_node: Node, property_name: str) -> DataFlowEdge:\n... return DataFlowEdge(\n... name=f\"{source_node.name}_{destination_node.name}_{property_name}_edge\",\n... source_node=source_node,\n... source_output=property_name,\n... destination_node=destination_node,\n... destination_input=property_name,\n... )\n>>>\n>>> article_property = StringProperty(title=\"article\")\n>>> is_llm_article_property = StringProperty(title=\"is_article\")\n>>> llm_node = LlmNode(\n... name=\"check_if_article_talks_about_llms_node\",\n... prompt_template=\"Look at this article: {{article}}. Does it talk about LLMs? Answer `yes` or `no`.\",\n... llm_config=llm_config,\n... inputs=[article_property],\n... outputs=[is_llm_article_property],\n... )\n>>>\n>>> branching_node = BranchingNode(\n... name=\"decide_if_we_should_return_the_article\",\n... mapping={\"yes\": \"yes\"},\n... inputs=[is_llm_article_property],\n... )\n>>>\n>>> start_node = StartNode(name=\"start\", inputs=[article_property])\n>>> end_node_with_output = EndNode(name=\"end_with_output\", outputs=[article_property])\n>>> end_node_without_output = EndNode(name=\"end_without_output\", outputs=[])\n>>>\n>>> check_if_article_is_about_llm_flow = Flow(\n... name=\"is_article_about_llm_flow\",\n... start_node=start_node,\n... nodes=[start_node, llm_node, end_node_with_output, end_node_without_output, branching_node],\n... control_flow_connections=[\n... ControlFlowEdge(name=\"start_to_llm\", from_node=start_node, to_node=llm_node),\n... ControlFlowEdge(name=\"llm_to_branching\", from_node=llm_node, to_node=branching_node),\n... ControlFlowEdge(name=\"branching_to_end_with\", from_node=branching_node, to_node=end_node_with_output),\n... ControlFlowEdge(name=\"branching_to_end_without\", from_node=branching_node, to_node=end_node_without_output),\n... ],\n... data_flow_connections=[\n... create_data_flow_edge(start_node, llm_node, article_property.title),\n... create_data_flow_edge(llm_node, branching_node, is_llm_article_property.title),\n... create_data_flow_edge(start_node, end_node_with_output, article_property.title),\n... ],\n... outputs=[StringProperty(title=article_property.title, default=\"\")],\n... )\n\nWe put this flow into a ``ParallelMapNode``, so that we can perform the check\nin parallel on multiple articles at the same time.\n\n>>> list_of_articles_property = ListProperty(title=\"iterated_article\", item_type=article_property)\n>>> list_of_articles_about_llm_property = ListProperty(title=\"collected_article\", item_type=article_property)\n>>> parallel_article_check_node = ParallelMapNode(\n... name=\"parallel_check_if_articles_talk_about_llms_node\",\n... subflow=check_if_article_is_about_llm_flow,\n... inputs=[list_of_articles_property],\n... outputs=[list_of_articles_about_llm_property],\n... )\n\nFinally, we create the flow that takes the list of articles as input, filters them through the\n``ParallelMapNode`` we have just created, and we generate the summary with the remaining articles.\n\n>>> summary_property = StringProperty(title=\"summary\")\n>>> summary_llm_node = LlmNode(\n... name=\"generate_summary_of_llm_articles_node\",\n... prompt_template=\"Summarize the following articles that talk about LLMs: {{collected_article}}\",\n... llm_config=llm_config,\n... inputs=[list_of_articles_about_llm_property],\n... outputs=[summary_property],\n... )\n>>> start_node = StartNode(name=\"start\", inputs=[list_of_articles_property])\n>>> end_node = EndNode(name=\"end\", outputs=[summary_property])\n>>> generate_summary_of_llm_articles_flow = Flow(\n... name=\"Generate summary of articles about LLMs flow\",\n... start_node=start_node,\n... nodes=[start_node, parallel_article_check_node, summary_llm_node, end_node],\n... control_flow_connections=[\n... ControlFlowEdge(name=\"start_to_parallelmap\", from_node=start_node, to_node=parallel_article_check_node),\n... ControlFlowEdge(name=\"parallelmap_to_llm\", from_node=parallel_article_check_node, to_node=summary_llm_node),\n... ControlFlowEdge(name=\"llm_to_end\", from_node=summary_llm_node, to_node=end_node),\n... ],\n... data_flow_connections=[\n... create_data_flow_edge(start_node, parallel_article_check_node, list_of_articles_property.title),\n... create_data_flow_edge(parallel_article_check_node, summary_llm_node, list_of_articles_about_llm_property.title),\n... create_data_flow_edge(summary_llm_node, end_node, summary_property.title),\n... ],\n... )", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "inputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Inputs" + }, + "outputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Outputs" + }, + "branches": { + "items": { + "type": "string" + }, + "title": "Branches", + "type": "array" + }, + "subflow": { + "$ref": "#/$defs/Flow" + }, + "reducers": { + "anyOf": [ + { + "additionalProperties": { + "$ref": "#/$defs/ReductionMethod" + }, + "type": "object" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Reducers" + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "ParallelMapNode" + } + }, + "required": [ + "name", + "subflow" + ], + "title": "ParallelMapNode", + "type": "object", + "x-abstract-component": false + }, + "BasePostgresDatabaseConnectionConfig": { + "anyOf": [ + { + "$ref": "#/$defs/TlsPostgresDatabaseConnectionConfig" + } + ], + "x-abstract-component": true + }, + "BasePostgresDatabaseDatastore": { + "additionalProperties": false, + "description": "Datastore that uses PostgreSQL Database as the storage mechanism.", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "datastore_schema": { + "additionalProperties": { + "$ref": "#/$defs/Property" + }, + "title": "Datastore Schema", + "type": "object" + }, + "connection_config": { + "$ref": "#/$defs/PostgresDatabaseConnectionConfig" + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "PostgresDatabaseDatastore" + } + }, + "required": [ + "connection_config", + "datastore_schema", + "name" + ], + "title": "PostgresDatabaseDatastore", + "type": "object", + "x-abstract-component": false + }, + "BaseRemoteAgent": { + "anyOf": [ + { + "$ref": "#/$defs/A2AAgent" + }, + { + "$ref": "#/$defs/OciAgent" + } + ], + "x-abstract-component": true + }, + "BaseRemoteTool": { + "additionalProperties": false, + "description": "A tool that is run remotely and called through REST.", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "inputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Inputs" + }, + "outputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Outputs" + }, + "requires_confirmation": { + "default": false, + "title": "Requires Confirmation", + "type": "boolean" + }, + "url": { + "title": "Url", + "type": "string" + }, + "http_method": { + "title": "Http Method", + "type": "string" + }, + "api_spec_uri": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Api Spec Uri" + }, + "data": { + "anyOf": [ + { + "type": "string" + }, + { + "format": "binary", + "type": "string" + }, + {} + ], + "title": "Data" + }, + "query_params": { + "additionalProperties": true, + "title": "Query Params", + "type": "object" + }, + "headers": { + "additionalProperties": true, + "title": "Headers", + "type": "object" + }, + "sensitive_headers": { + "additionalProperties": true, + "title": "Sensitive Headers", + "type": "object" + }, + "url_allow_list": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Url Allow List" + }, + "retry_policy": { + "anyOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/RetryPolicy" + } + ], + "default": null + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "RemoteTool" + } + }, + "required": [ + "http_method", + "name", + "url" + ], + "title": "RemoteTool", + "type": "object", + "x-abstract-component": false + }, + "BaseRemoteTransport": { + "anyOf": [ + { + "$ref": "#/$defs/SSETransport" + }, + { + "$ref": "#/$defs/SSEmTLSTransport" + }, + { + "$ref": "#/$defs/StreamableHTTPTransport" + }, + { + "$ref": "#/$defs/StreamableHTTPmTLSTransport" + } + ], + "x-abstract-component": true + }, + "BaseSSETransport": { + "anyOf": [ + { + "$ref": "#/$defs/SSEmTLSTransport" + }, + { + "additionalProperties": false, + "description": "Transport implementation that connects to an MCP server via Server-Sent Events.", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "session_parameters": { + "$ref": "#/$defs/SessionParameters" + }, + "url": { + "title": "Url", + "type": "string" + }, + "auth": { + "anyOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/AuthConfig" + } + ], + "default": null + }, + "headers": { + "anyOf": [ + { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Headers" + }, + "sensitive_headers": { + "anyOf": [ + { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Sensitive Headers" + }, + "retry_policy": { + "anyOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/RetryPolicy" + } + ], + "default": null + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "SSETransport" + } + }, + "required": [ + "name", + "url" + ], + "title": "SSETransport", + "type": "object" + } + ], + "x-abstract-component": false + }, + "BaseSSEmTLSTransport": { + "additionalProperties": false, + "description": "Transport layer for SSE with mTLS (mutual Transport Layer Security).\n\nThis transport establishes a secure, mutually authenticated TLS connection to the MCP server using client\ncertificates. Production deployments MUST use this transport to ensure both client and server identities\nare verified.\n\nNotes\n-----\n- Users MUST provide a valid client certificate (PEM format) and private key.\n- Users MUST provide (or trust) the correct certificate authority (CA) for the server they're connecting to.\n- The client certificate/key and CA certificate paths can be managed via secrets, config files, or secure\n environment variables in any production system.\n- Executors should ensure that these files are rotated and managed securely.", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "session_parameters": { + "$ref": "#/$defs/SessionParameters" + }, + "url": { + "title": "Url", + "type": "string" + }, + "auth": { + "anyOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/AuthConfig" + } + ], + "default": null + }, + "headers": { + "anyOf": [ + { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Headers" + }, + "sensitive_headers": { + "anyOf": [ + { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Sensitive Headers" + }, + "retry_policy": { + "anyOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/RetryPolicy" + } + ], + "default": null + }, + "key_file": { + "title": "Key File", + "type": "string" + }, + "cert_file": { + "title": "Cert File", + "type": "string" + }, + "ca_file": { + "title": "Ca File", + "type": "string" + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "SSEmTLSTransport" + } + }, + "required": [ + "ca_file", + "cert_file", + "key_file", + "name", + "url" + ], + "title": "SSEmTLSTransport", + "type": "object", + "x-abstract-component": false + }, + "BaseServerTool": { + "additionalProperties": false, + "description": "A tool that is registered to and executed by the orchestrator.", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "inputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Inputs" + }, + "outputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Outputs" + }, + "requires_confirmation": { + "default": false, + "title": "Requires Confirmation", + "type": "boolean" + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "ServerTool" + } + }, + "required": [ + "name" + ], + "title": "ServerTool", + "type": "object", + "x-abstract-component": false + }, + "BaseSpecializedAgent": { + "additionalProperties": false, + "description": "A specialized agent is an agent that uses an existing generic agent and\nspecializes it to solve a given task.\n\nIt can be executed anywhere an Agent can be executed.\n\nExamples\n--------\n>>> from pyagentspec.agent import Agent\n>>> from pyagentspec.property import StringProperty\n>>> from pyagentspec.specialized_agent import AgentSpecializationParameters, SpecializedAgent\n>>> from pyagentspec.tools import ServerTool\n>>> expertise_property = StringProperty(title=\"domain_of_expertise\")\n>>> system_prompt = '''You are an expert in {{domain_of_expertise}}.\n... Please help the users with their requests.'''\n>>> agent = Agent(\n... name=\"Adaptive expert agent\",\n... system_prompt=system_prompt,\n... llm_config=llm_config,\n... inputs=[expertise_property],\n... )\n>>> websearch_tool = ServerTool(\n... name=\"websearch_tool\",\n... description=\"Search the web for information\",\n... inputs=[StringProperty(title=\"query\")],\n... outputs=[StringProperty(title=\"search_result\")],\n... )\n>>> agent_specialization_parameters = AgentSpecializationParameters(\n... name=\"essay_agent\",\n... additional_instructions=\"Your goal is to help the user write an essay around the domain of expertise.\",\n... additional_tools=[websearch_tool]\n... )", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "inputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Inputs" + }, + "outputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Outputs" + }, + "agent": { + "$ref": "#/$defs/Agent" + }, + "agent_specialization_parameters": { + "$ref": "#/$defs/AgentSpecializationParameters" + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "SpecializedAgent" + } + }, + "required": [ + "agent", + "agent_specialization_parameters", + "name" + ], + "title": "SpecializedAgent", + "type": "object", + "x-abstract-component": false + }, + "BaseStartNode": { + "additionalProperties": false, + "description": "Start nodes denote the start of the execution of a flow.\n\n- **Inputs**\n The list of inputs that should be the inputs of the flow. If both input and output\n properties are specified they must be an exact match\n\n If None is given, ``pyagentspec`` copies the outputs provided, if any. Otherwise, no input is exposed.\n- **Outputs**\n The list of outputs of the step. If both input and output properties are specified they\n must be an exact match\n\n If None is given, ``pyagentspec`` copies the inputs provided, if any. Otherwise, no output is exposed.\n- **Branches**\n One, the default next.\n\nExamples\n--------\n>>> from pyagentspec.property import Property\n>>> from pyagentspec.flows.edges import ControlFlowEdge, DataFlowEdge\n>>> from pyagentspec.flows.flow import Flow\n>>> from pyagentspec.flows.nodes import EndNode, LlmNode, StartNode\n>>> user_question_property = Property(\n... json_schema=dict(\n... title=\"user_question\",\n... description=\"The user question.\",\n... type=\"string\",\n... )\n... )\n>>> answer_property = Property(json_schema=dict(title=\"answer\", type=\"string\"))\n>>> start_node = StartNode(name=\"start\", inputs=[user_question_property])\n>>> end_node = EndNode(name=\"end\", outputs=[answer_property])\n>>> llm_node = LlmNode(\n... name=\"llm node\",\n... prompt_template=\"Answer the user question: {{user_question}}\",\n... llm_config=llm_config,\n... )\n>>> flow = Flow(\n... name=\"flow\",\n... start_node=start_node,\n... nodes=[start_node, llm_node, end_node],\n... control_flow_connections=[\n... ControlFlowEdge(name=\"start_to_llm\", from_node=start_node, to_node=llm_node),\n... ControlFlowEdge(name=\"llm_to_end\", from_node=llm_node, to_node=end_node),\n... ],\n... data_flow_connections=[\n... DataFlowEdge(\n... name=\"query_edge\",\n... source_node=start_node,\n... source_output=\"user_question\",\n... destination_node=llm_node,\n... destination_input=\"user_question\",\n... ),\n... DataFlowEdge(\n... name=\"answer_edge\",\n... source_node=llm_node,\n... source_output=\"generated_text\",\n... destination_node=end_node,\n... destination_input=\"answer\"\n... ),\n... ],\n... )", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "inputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Inputs" + }, + "outputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Outputs" + }, + "branches": { + "items": { + "type": "string" + }, + "title": "Branches", + "type": "array" + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "StartNode" + } + }, + "required": [ + "name" + ], + "title": "StartNode", + "type": "object", + "x-abstract-component": false + }, + "BaseStdioTransport": { + "additionalProperties": false, + "description": "Base transport for connecting to an MCP server via subprocess with stdio.\n\nThis is a base class that can be subclassed for specific command-based\ntransports like Python, Node, Uvx, etc.\n\n.. warning::\n Stdio should be used for local prototyping only.", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "session_parameters": { + "$ref": "#/$defs/SessionParameters" + }, + "command": { + "title": "Command", + "type": "string" + }, + "args": { + "items": { + "type": "string" + }, + "title": "Args", + "type": "array" + }, + "env": { + "anyOf": [ + { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Env" + }, + "cwd": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Cwd" + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "StdioTransport" + } + }, + "required": [ + "command", + "name" + ], + "title": "StdioTransport", + "type": "object", + "x-abstract-component": false + }, + "BaseStreamableHTTPTransport": { + "anyOf": [ + { + "$ref": "#/$defs/StreamableHTTPmTLSTransport" + }, + { + "additionalProperties": false, + "description": "Transport implementation that connects to an MCP server via Streamable HTTP.", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "session_parameters": { + "$ref": "#/$defs/SessionParameters" + }, + "url": { + "title": "Url", + "type": "string" + }, + "auth": { + "anyOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/AuthConfig" + } + ], + "default": null + }, + "headers": { + "anyOf": [ + { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Headers" + }, + "sensitive_headers": { + "anyOf": [ + { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Sensitive Headers" + }, + "retry_policy": { + "anyOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/RetryPolicy" + } + ], + "default": null + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "StreamableHTTPTransport" + } + }, + "required": [ + "name", + "url" + ], + "title": "StreamableHTTPTransport", + "type": "object" + } + ], + "x-abstract-component": false + }, + "BaseStreamableHTTPmTLSTransport": { + "additionalProperties": false, + "description": "Transport layer for streamable HTTP with mTLS (mutual Transport Layer Security).\n\nThis transport establishes a secure, mutually authenticated TLS connection to the MCP server using client\ncertificates. Production deployments MUST use this transport to ensure both client and server identities\nare verified.\n\nNotes\n-----\n- Users MUST provide a valid client certificate (PEM format) and private key.\n- Users MUST provide (or trust) the correct certificate authority (CA) for the server they're connecting to.\n- The client certificate/key and CA certificate paths can be managed via secrets, config files, or secure\n environment variables in any production system.\n- Executors should ensure that these files are rotated and managed securely.", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "session_parameters": { + "$ref": "#/$defs/SessionParameters" + }, + "url": { + "title": "Url", + "type": "string" + }, + "auth": { + "anyOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/AuthConfig" + } + ], + "default": null + }, + "headers": { + "anyOf": [ + { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Headers" + }, + "sensitive_headers": { + "anyOf": [ + { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Sensitive Headers" + }, + "retry_policy": { + "anyOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/RetryPolicy" + } + ], + "default": null + }, + "key_file": { + "title": "Key File", + "type": "string" + }, + "cert_file": { + "title": "Cert File", + "type": "string" + }, + "ca_file": { + "title": "Ca File", + "type": "string" + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "StreamableHTTPmTLSTransport" + } + }, + "required": [ + "ca_file", + "cert_file", + "key_file", + "name", + "url" + ], + "title": "StreamableHTTPmTLSTransport", + "type": "object", + "x-abstract-component": false + }, + "BaseSwarm": { + "additionalProperties": false, + "description": "Defines a ``Swarm`` conversational component.\n\nA ``Swarm`` is a multi-agent conversational component in which each agent determines\nthe next agent to be executed, based on a list of pre-defined relationships.\nAgents in Swarm can be any ``AgenticComponent``.\n\nExamples\n--------\n>>> from pyagentspec.agent import Agent\n>>> from pyagentspec.swarm import Swarm\n>>> addition_agent = Agent(name=\"addition_agent\", description=\"Agent that can do additions\", llm_config=llm_config, system_prompt=\"You can do additions.\")\n>>> multiplication_agent = Agent(name=\"multiplication_agent\", description=\"Agent that can do multiplication\", llm_config=llm_config, system_prompt=\"You can do multiplication.\")\n>>> division_agent = Agent(name=\"division_agent\", description=\"Agent that can do division\", llm_config=llm_config, system_prompt=\"You can do division.\")\n>>>\n>>> swarm = Swarm(\n... name=\"swarm\",\n... first_agent=addition_agent,\n... relationships=[\n... (addition_agent, multiplication_agent),\n... (addition_agent, division_agent),\n... (multiplication_agent, division_agent),\n... ]\n... )", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "inputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Inputs" + }, + "outputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Outputs" + }, + "first_agent": { + "$ref": "#/$defs/AgenticComponent" + }, + "relationships": { + "items": { + "maxItems": 2, + "minItems": 2, + "prefixItems": [ + { + "$ref": "#/$defs/AgenticComponent" + }, + { + "$ref": "#/$defs/AgenticComponent" + } + ], + "type": "array" + }, + "title": "Relationships", + "type": "array" + }, + "handoff": { + "anyOf": [ + { + "type": "boolean" + }, + { + "$ref": "#/$defs/HandoffMode" + } + ], + "default": "optional", + "title": "Handoff" + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "Swarm" + } + }, + "required": [ + "first_agent", + "name", + "relationships" + ], + "title": "Swarm", + "type": "object", + "x-abstract-component": false + }, + "BaseTlsOracleDatabaseConnectionConfig": { + "anyOf": [ + { + "$ref": "#/$defs/MTlsOracleDatabaseConnectionConfig" + }, + { + "additionalProperties": false, + "description": "TLS Connection Configuration to Oracle Database.", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "user": { + "title": "User", + "type": "string" + }, + "password": { + "title": "Password", + "type": "string" + }, + "dsn": { + "title": "Dsn", + "type": "string" + }, + "config_dir": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Config Dir" + }, + "protocol": { + "default": "tcps", + "enum": [ + "tcp", + "tcps" + ], + "title": "Protocol", + "type": "string" + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "TlsOracleDatabaseConnectionConfig" + } + }, + "required": [ + "dsn", + "name", + "password", + "user" + ], + "title": "TlsOracleDatabaseConnectionConfig", + "type": "object" + } + ], + "x-abstract-component": false + }, + "BaseTlsPostgresDatabaseConnectionConfig": { + "additionalProperties": false, + "description": "Configuration for a PostgreSQL connection with TLS/SSL support.", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "user": { + "title": "User", + "type": "string" + }, + "password": { + "title": "Password", + "type": "string" + }, + "url": { + "title": "Url", + "type": "string" + }, + "sslmode": { + "default": "require", + "enum": [ + "allow", + "disable", + "prefer", + "require", + "verify-ca", + "verify-full" + ], + "title": "Sslmode", + "type": "string" + }, + "sslcert": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Sslcert" + }, + "sslkey": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Sslkey" + }, + "sslrootcert": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Sslrootcert" + }, + "sslcrl": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Sslcrl" + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "TlsPostgresDatabaseConnectionConfig" + } + }, + "required": [ + "name", + "password", + "url", + "user" + ], + "title": "TlsPostgresDatabaseConnectionConfig", + "type": "object", + "x-abstract-component": false + }, + "BaseTool": { + "anyOf": [ + { + "$ref": "#/$defs/BuiltinTool" + }, + { + "$ref": "#/$defs/ClientTool" + }, + { + "$ref": "#/$defs/MCPTool" + }, + { + "$ref": "#/$defs/RemoteTool" + }, + { + "$ref": "#/$defs/ServerTool" + } + ], + "x-abstract-component": true + }, + "BaseToolBox": { + "anyOf": [ + { + "$ref": "#/$defs/MCPToolBox" + } + ], + "x-abstract-component": true + }, + "BaseToolNode": { + "additionalProperties": false, + "description": "The tool execution node is a node that will execute a tool as part of a flow.\n\n- **Inputs**\n Inferred from the definition of the tool to execute.\n- **Outputs**\n Inferred from the definition of the tool to execute.\n- **Branches**\n One, the default next.\n\nExamples\n--------\n>>> from pyagentspec.flows.edges import ControlFlowEdge, DataFlowEdge\n>>> from pyagentspec.flows.flow import Flow\n>>> from pyagentspec.flows.nodes import ToolNode, StartNode, EndNode\n>>> from pyagentspec.tools import ServerTool\n>>> from pyagentspec.property import Property\n>>>\n>>> x_property = Property(json_schema={\"title\": \"x\", \"type\": \"number\"})\n>>> x_square_root_property = Property(\n... json_schema={\"title\": \"x_square_root\", \"type\": \"number\"}\n... )\n>>> square_root_tool = ServerTool(\n... name=\"compute_square_root\",\n... description=\"Computes the square root of a number\",\n... inputs=[x_property],\n... outputs=[x_square_root_property],\n... )\n>>> start_node = StartNode(name=\"start\", inputs=[x_property])\n>>> end_node = EndNode(name=\"end\", outputs=[x_square_root_property])\n>>> tool_node = ToolNode(name=\"\", tool=square_root_tool)\n>>> flow = Flow(\n... name=\"Compute square root flow\",\n... start_node=start_node,\n... nodes=[start_node, tool_node, end_node],\n... control_flow_connections=[\n... ControlFlowEdge(name=\"start_to_tool\", from_node=start_node, to_node=tool_node),\n... ControlFlowEdge(name=\"tool_to_end\", from_node=tool_node, to_node=end_node),\n... ],\n... data_flow_connections=[\n... DataFlowEdge(\n... name=\"x_edge\",\n... source_node=start_node,\n... source_output=\"x\",\n... destination_node=tool_node,\n... destination_input=\"x\",\n... ),\n... DataFlowEdge(\n... name=\"x_square_root_edge\",\n... source_node=tool_node,\n... source_output=\"x_square_root\",\n... destination_node=end_node,\n... destination_input=\"x_square_root\"\n... ),\n... ],\n... )", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "inputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Inputs" + }, + "outputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Outputs" + }, + "branches": { + "items": { + "type": "string" + }, + "title": "Branches", + "type": "array" + }, + "tool": { + "$ref": "#/$defs/Tool" + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "ToolNode" + } + }, + "required": [ + "name", + "tool" + ], + "title": "ToolNode", + "type": "object", + "x-abstract-component": false + }, + "BaseVllmConfig": { + "additionalProperties": false, + "description": "Class to configure a connection to a vLLM-hosted LLM.\n\nRequires to specify the url at which the instance is running.", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "model_id": { + "title": "Model Id", + "type": "string" + }, + "provider": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Provider" + }, + "api_provider": { + "const": "vllm", + "default": "vllm", + "title": "Api Provider", + "type": "string" + }, + "api_type": { + "$ref": "#/$defs/OpenAIAPIType", + "default": "chat_completions" + }, + "url": { + "title": "Url", + "type": "string" + }, + "api_key": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Api Key" + }, + "default_generation_parameters": { + "anyOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/LlmGenerationConfig" + } + ], + "default": null + }, + "retry_policy": { + "anyOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/RetryPolicy" + } + ], + "default": null + }, + "key_file": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Key File" + }, + "cert_file": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Cert File" + }, + "ca_file": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Ca File" + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "VllmConfig" + } + }, + "required": [ + "model_id", + "name", + "url" + ], + "title": "VllmConfig", + "type": "object", + "x-abstract-component": false + }, + "ReferencedComponents": { + "type": "object", + "additionalProperties": { + "anyOf": [ + { + "$ref": "#/$defs/BaseA2AAgent" + }, + { + "$ref": "#/$defs/BaseA2AConnectionConfig" + }, + { + "$ref": "#/$defs/BaseAgent" + }, + { + "$ref": "#/$defs/BaseAgentNode" + }, + { + "$ref": "#/$defs/BaseAgentSpecializationParameters" + }, + { + "$ref": "#/$defs/BaseAgenticComponent" + }, + { + "$ref": "#/$defs/BaseApiNode" + }, + { + "$ref": "#/$defs/BaseAuthConfig" + }, + { + "$ref": "#/$defs/BaseBranchingNode" + }, + { + "$ref": "#/$defs/BaseBuiltinTool" + }, + { + "$ref": "#/$defs/BaseCatchExceptionNode" + }, + { + "$ref": "#/$defs/BaseClientTool" + }, + { + "$ref": "#/$defs/BaseClientTransport" + }, + { + "$ref": "#/$defs/BaseComponentWithIO" + }, + { + "$ref": "#/$defs/BaseControlFlowEdge" + }, + { + "$ref": "#/$defs/BaseConversationSummarizationTransform" + }, + { + "$ref": "#/$defs/BaseDataFlowEdge" + }, + { + "$ref": "#/$defs/BaseDatastore" + }, + { + "$ref": "#/$defs/BaseDbmsVectorChainLlmConfig" + }, + { + "$ref": "#/$defs/BaseEndNode" + }, + { + "$ref": "#/$defs/BaseFlow" + }, + { + "$ref": "#/$defs/BaseFlowNode" + }, + { + "$ref": "#/$defs/BaseGeminiAIStudioAuthConfig" + }, + { + "$ref": "#/$defs/BaseGeminiAuthConfig" + }, + { + "$ref": "#/$defs/BaseGeminiConfig" + }, + { + "$ref": "#/$defs/BaseGeminiVertexAIAuthConfig" + }, + { + "$ref": "#/$defs/BaseInMemoryCollectionDatastore" + }, + { + "$ref": "#/$defs/BaseInputMessageNode" + }, + { + "$ref": "#/$defs/BaseLlmConfig" + }, + { + "$ref": "#/$defs/BaseLlmNode" + }, + { + "$ref": "#/$defs/BaseMCPTool" + }, + { + "$ref": "#/$defs/BaseMCPToolBox" + }, + { + "$ref": "#/$defs/BaseMCPToolSpec" + }, + { + "$ref": "#/$defs/BaseMTlsOracleDatabaseConnectionConfig" + }, + { + "$ref": "#/$defs/BaseManagerWorkers" + }, + { + "$ref": "#/$defs/BaseMapNode" + }, + { + "$ref": "#/$defs/BaseMessageSummarizationTransform" + }, + { + "$ref": "#/$defs/BaseMessageTransform" + }, + { + "$ref": "#/$defs/BaseNode" + }, + { + "$ref": "#/$defs/BaseOAuthClientConfig" + }, + { + "$ref": "#/$defs/BaseOAuthConfig" + }, + { + "$ref": "#/$defs/BaseOciAgent" + }, + { + "$ref": "#/$defs/BaseOciClientConfig" + }, + { + "$ref": "#/$defs/BaseOciClientConfigWithApiKey" + }, + { + "$ref": "#/$defs/BaseOciClientConfigWithInstancePrincipal" + }, + { + "$ref": "#/$defs/BaseOciClientConfigWithResourcePrincipal" + }, + { + "$ref": "#/$defs/BaseOciClientConfigWithSecurityToken" + }, + { + "$ref": "#/$defs/BaseOciGenAiConfig" + }, + { + "$ref": "#/$defs/BaseOllamaConfig" + }, + { + "$ref": "#/$defs/BaseOpenAiCompatibleConfig" + }, + { + "$ref": "#/$defs/BaseOpenAiConfig" + }, + { + "$ref": "#/$defs/BaseOracleDatabaseConnectionConfig" + }, + { + "$ref": "#/$defs/BaseOracleDatabaseDatastore" + }, + { + "$ref": "#/$defs/BaseOutputMessageNode" + }, + { + "$ref": "#/$defs/BaseParallelFlowNode" + }, + { + "$ref": "#/$defs/BaseParallelMapNode" + }, + { + "$ref": "#/$defs/BasePostgresDatabaseConnectionConfig" + }, + { + "$ref": "#/$defs/BasePostgresDatabaseDatastore" + }, + { + "$ref": "#/$defs/BaseRemoteAgent" + }, + { + "$ref": "#/$defs/BaseRemoteTool" + }, + { + "$ref": "#/$defs/BaseRemoteTransport" + }, + { + "$ref": "#/$defs/BaseSSETransport" + }, + { + "$ref": "#/$defs/BaseSSEmTLSTransport" + }, + { + "$ref": "#/$defs/BaseServerTool" + }, + { + "$ref": "#/$defs/BaseSpecializedAgent" + }, + { + "$ref": "#/$defs/BaseStartNode" + }, + { + "$ref": "#/$defs/BaseStdioTransport" + }, + { + "$ref": "#/$defs/BaseStreamableHTTPTransport" + }, + { + "$ref": "#/$defs/BaseStreamableHTTPmTLSTransport" + }, + { + "$ref": "#/$defs/BaseSwarm" + }, + { + "$ref": "#/$defs/BaseTlsOracleDatabaseConnectionConfig" + }, + { + "$ref": "#/$defs/BaseTlsPostgresDatabaseConnectionConfig" + }, + { + "$ref": "#/$defs/BaseTool" + }, + { + "$ref": "#/$defs/BaseToolBox" + }, + { + "$ref": "#/$defs/BaseToolNode" + }, + { + "$ref": "#/$defs/BaseVllmConfig" + }, + { + "$ref": "#/$defs/ComponentReferenceWithNestedReferences" + } + ] + } + }, + "ComponentReference": { + "type": "object", + "properties": { + "$component_ref": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "$component_ref" + ] + }, + "ComponentReferenceWithNestedReferences": { + "type": "object", + "properties": { + "$component_ref": { + "type": "string" + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + } + }, + "additionalProperties": false, + "required": [ + "$component_ref", + "$referenced_components" + ] + }, + "VersionedA2AAgent": { + "anyOf": [ + { + "$ref": "#/$defs/BaseA2AAgent" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedA2AConnectionConfig": { + "anyOf": [ + { + "$ref": "#/$defs/BaseA2AConnectionConfig" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedAgent": { + "anyOf": [ + { + "$ref": "#/$defs/BaseAgent" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedAgentNode": { + "anyOf": [ + { + "$ref": "#/$defs/BaseAgentNode" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedAgentSpecializationParameters": { + "anyOf": [ + { + "$ref": "#/$defs/BaseAgentSpecializationParameters" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedAgenticComponent": { + "anyOf": [ + { + "$ref": "#/$defs/BaseAgenticComponent" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedApiNode": { + "anyOf": [ + { + "$ref": "#/$defs/BaseApiNode" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedBranchingNode": { + "anyOf": [ + { + "$ref": "#/$defs/BaseBranchingNode" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedBuiltinTool": { + "anyOf": [ + { + "$ref": "#/$defs/BaseBuiltinTool" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedCatchExceptionNode": { + "anyOf": [ + { + "$ref": "#/$defs/BaseCatchExceptionNode" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedClientTool": { + "anyOf": [ + { + "$ref": "#/$defs/BaseClientTool" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedClientTransport": { + "anyOf": [ + { + "$ref": "#/$defs/BaseClientTransport" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedComponentWithIO": { + "anyOf": [ + { + "$ref": "#/$defs/BaseComponentWithIO" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedControlFlowEdge": { + "anyOf": [ + { + "$ref": "#/$defs/BaseControlFlowEdge" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedConversationSummarizationTransform": { + "anyOf": [ + { + "$ref": "#/$defs/BaseConversationSummarizationTransform" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedDataFlowEdge": { + "anyOf": [ + { + "$ref": "#/$defs/BaseDataFlowEdge" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedDatastore": { + "anyOf": [ + { + "$ref": "#/$defs/BaseDatastore" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedDbmsVectorChainLlmConfig": { + "anyOf": [ + { + "$ref": "#/$defs/BaseDbmsVectorChainLlmConfig" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedEndNode": { + "anyOf": [ + { + "$ref": "#/$defs/BaseEndNode" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedFlow": { + "anyOf": [ + { + "$ref": "#/$defs/BaseFlow" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedFlowNode": { + "anyOf": [ + { + "$ref": "#/$defs/BaseFlowNode" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedGeminiAIStudioAuthConfig": { + "anyOf": [ + { + "$ref": "#/$defs/BaseGeminiAIStudioAuthConfig" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedGeminiAuthConfig": { + "anyOf": [ + { + "$ref": "#/$defs/BaseGeminiAuthConfig" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedGeminiConfig": { + "anyOf": [ + { + "$ref": "#/$defs/BaseGeminiConfig" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedGeminiVertexAIAuthConfig": { + "anyOf": [ + { + "$ref": "#/$defs/BaseGeminiVertexAIAuthConfig" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedInMemoryCollectionDatastore": { + "anyOf": [ + { + "$ref": "#/$defs/BaseInMemoryCollectionDatastore" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedInputMessageNode": { + "anyOf": [ + { + "$ref": "#/$defs/BaseInputMessageNode" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedLlmConfig": { + "anyOf": [ + { + "$ref": "#/$defs/BaseLlmConfig" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedLlmNode": { + "anyOf": [ + { + "$ref": "#/$defs/BaseLlmNode" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedMCPTool": { + "anyOf": [ + { + "$ref": "#/$defs/BaseMCPTool" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedMCPToolBox": { + "anyOf": [ + { + "$ref": "#/$defs/BaseMCPToolBox" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedMCPToolSpec": { + "anyOf": [ + { + "$ref": "#/$defs/BaseMCPToolSpec" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedMTlsOracleDatabaseConnectionConfig": { + "anyOf": [ + { + "$ref": "#/$defs/BaseMTlsOracleDatabaseConnectionConfig" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedManagerWorkers": { + "anyOf": [ + { + "$ref": "#/$defs/BaseManagerWorkers" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedMapNode": { + "anyOf": [ + { + "$ref": "#/$defs/BaseMapNode" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedMessageSummarizationTransform": { + "anyOf": [ + { + "$ref": "#/$defs/BaseMessageSummarizationTransform" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedNode": { + "anyOf": [ + { + "$ref": "#/$defs/BaseNode" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedOAuthClientConfig": { + "anyOf": [ + { + "$ref": "#/$defs/BaseOAuthClientConfig" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedOAuthConfig": { + "anyOf": [ + { + "$ref": "#/$defs/BaseOAuthConfig" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedOciAgent": { + "anyOf": [ + { + "$ref": "#/$defs/BaseOciAgent" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedOciClientConfig": { + "anyOf": [ + { + "$ref": "#/$defs/BaseOciClientConfig" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedOciClientConfigWithApiKey": { + "anyOf": [ + { + "$ref": "#/$defs/BaseOciClientConfigWithApiKey" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedOciClientConfigWithInstancePrincipal": { + "anyOf": [ + { + "$ref": "#/$defs/BaseOciClientConfigWithInstancePrincipal" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedOciClientConfigWithResourcePrincipal": { + "anyOf": [ + { + "$ref": "#/$defs/BaseOciClientConfigWithResourcePrincipal" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedOciClientConfigWithSecurityToken": { + "anyOf": [ + { + "$ref": "#/$defs/BaseOciClientConfigWithSecurityToken" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedOciGenAiConfig": { + "anyOf": [ + { + "$ref": "#/$defs/BaseOciGenAiConfig" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedOllamaConfig": { + "anyOf": [ + { + "$ref": "#/$defs/BaseOllamaConfig" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedOpenAiCompatibleConfig": { + "anyOf": [ + { + "$ref": "#/$defs/BaseOpenAiCompatibleConfig" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedOpenAiConfig": { + "anyOf": [ + { + "$ref": "#/$defs/BaseOpenAiConfig" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedOracleDatabaseDatastore": { + "anyOf": [ + { + "$ref": "#/$defs/BaseOracleDatabaseDatastore" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedOutputMessageNode": { + "anyOf": [ + { + "$ref": "#/$defs/BaseOutputMessageNode" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedParallelFlowNode": { + "anyOf": [ + { + "$ref": "#/$defs/BaseParallelFlowNode" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedParallelMapNode": { + "anyOf": [ + { + "$ref": "#/$defs/BaseParallelMapNode" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedPostgresDatabaseDatastore": { + "anyOf": [ + { + "$ref": "#/$defs/BasePostgresDatabaseDatastore" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedRemoteAgent": { + "anyOf": [ + { + "$ref": "#/$defs/BaseRemoteAgent" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedRemoteTool": { + "anyOf": [ + { + "$ref": "#/$defs/BaseRemoteTool" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedRemoteTransport": { + "anyOf": [ + { + "$ref": "#/$defs/BaseRemoteTransport" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedSSETransport": { + "anyOf": [ + { + "$ref": "#/$defs/BaseSSETransport" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedSSEmTLSTransport": { + "anyOf": [ + { + "$ref": "#/$defs/BaseSSEmTLSTransport" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedServerTool": { + "anyOf": [ + { + "$ref": "#/$defs/BaseServerTool" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedSpecializedAgent": { + "anyOf": [ + { + "$ref": "#/$defs/BaseSpecializedAgent" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedStartNode": { + "anyOf": [ + { + "$ref": "#/$defs/BaseStartNode" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedStdioTransport": { + "anyOf": [ + { + "$ref": "#/$defs/BaseStdioTransport" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedStreamableHTTPTransport": { + "anyOf": [ + { + "$ref": "#/$defs/BaseStreamableHTTPTransport" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedStreamableHTTPmTLSTransport": { + "anyOf": [ + { + "$ref": "#/$defs/BaseStreamableHTTPmTLSTransport" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedSwarm": { + "anyOf": [ + { + "$ref": "#/$defs/BaseSwarm" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedTlsOracleDatabaseConnectionConfig": { + "anyOf": [ + { + "$ref": "#/$defs/BaseTlsOracleDatabaseConnectionConfig" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedTlsPostgresDatabaseConnectionConfig": { + "anyOf": [ + { + "$ref": "#/$defs/BaseTlsPostgresDatabaseConnectionConfig" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedTool": { + "anyOf": [ + { + "$ref": "#/$defs/BaseTool" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedToolBox": { + "anyOf": [ + { + "$ref": "#/$defs/BaseToolBox" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedToolNode": { + "anyOf": [ + { + "$ref": "#/$defs/BaseToolNode" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedVllmConfig": { + "anyOf": [ + { + "$ref": "#/$defs/BaseVllmConfig" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedComponentReferenceWithNestedReferences": { + "type": "object", + "properties": { + "$component_ref": { + "type": "string" + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + }, + "additionalProperties": false, + "required": [ + "$component_ref", + "$referenced_components" + ] + } + }, + "anyOf": [ + { + "$ref": "#/$defs/VersionedA2AAgent" + }, + { + "$ref": "#/$defs/VersionedA2AConnectionConfig" + }, + { + "$ref": "#/$defs/VersionedAgent" + }, + { + "$ref": "#/$defs/VersionedAgentNode" + }, + { + "$ref": "#/$defs/VersionedAgentSpecializationParameters" + }, + { + "$ref": "#/$defs/VersionedAgenticComponent" + }, + { + "$ref": "#/$defs/VersionedApiNode" + }, + { + "$ref": "#/$defs/VersionedBranchingNode" + }, + { + "$ref": "#/$defs/VersionedBuiltinTool" + }, + { + "$ref": "#/$defs/VersionedCatchExceptionNode" + }, + { + "$ref": "#/$defs/VersionedClientTool" + }, + { + "$ref": "#/$defs/VersionedClientTransport" + }, + { + "$ref": "#/$defs/VersionedComponentReferenceWithNestedReferences" + }, + { + "$ref": "#/$defs/VersionedComponentWithIO" + }, + { + "$ref": "#/$defs/VersionedControlFlowEdge" + }, + { + "$ref": "#/$defs/VersionedConversationSummarizationTransform" + }, + { + "$ref": "#/$defs/VersionedDataFlowEdge" + }, + { + "$ref": "#/$defs/VersionedDatastore" + }, + { + "$ref": "#/$defs/VersionedDbmsVectorChainLlmConfig" + }, + { + "$ref": "#/$defs/VersionedEndNode" + }, + { + "$ref": "#/$defs/VersionedFlow" + }, + { + "$ref": "#/$defs/VersionedFlowNode" + }, + { + "$ref": "#/$defs/VersionedGeminiAIStudioAuthConfig" + }, + { + "$ref": "#/$defs/VersionedGeminiAuthConfig" + }, + { + "$ref": "#/$defs/VersionedGeminiConfig" + }, + { + "$ref": "#/$defs/VersionedGeminiVertexAIAuthConfig" + }, + { + "$ref": "#/$defs/VersionedInMemoryCollectionDatastore" + }, + { + "$ref": "#/$defs/VersionedInputMessageNode" + }, + { + "$ref": "#/$defs/VersionedLlmConfig" + }, + { + "$ref": "#/$defs/VersionedLlmNode" + }, + { + "$ref": "#/$defs/VersionedMCPTool" + }, + { + "$ref": "#/$defs/VersionedMCPToolBox" + }, + { + "$ref": "#/$defs/VersionedMCPToolSpec" + }, + { + "$ref": "#/$defs/VersionedMTlsOracleDatabaseConnectionConfig" + }, + { + "$ref": "#/$defs/VersionedManagerWorkers" + }, + { + "$ref": "#/$defs/VersionedMapNode" + }, + { + "$ref": "#/$defs/VersionedMessageSummarizationTransform" + }, + { + "$ref": "#/$defs/VersionedNode" + }, + { + "$ref": "#/$defs/VersionedOAuthClientConfig" + }, + { + "$ref": "#/$defs/VersionedOAuthConfig" + }, + { + "$ref": "#/$defs/VersionedOciAgent" + }, + { + "$ref": "#/$defs/VersionedOciClientConfig" + }, + { + "$ref": "#/$defs/VersionedOciClientConfigWithApiKey" + }, + { + "$ref": "#/$defs/VersionedOciClientConfigWithInstancePrincipal" + }, + { + "$ref": "#/$defs/VersionedOciClientConfigWithResourcePrincipal" + }, + { + "$ref": "#/$defs/VersionedOciClientConfigWithSecurityToken" + }, + { + "$ref": "#/$defs/VersionedOciGenAiConfig" + }, + { + "$ref": "#/$defs/VersionedOllamaConfig" + }, + { + "$ref": "#/$defs/VersionedOpenAiCompatibleConfig" + }, + { + "$ref": "#/$defs/VersionedOpenAiConfig" + }, + { + "$ref": "#/$defs/VersionedOracleDatabaseDatastore" + }, + { + "$ref": "#/$defs/VersionedOutputMessageNode" + }, + { + "$ref": "#/$defs/VersionedParallelFlowNode" + }, + { + "$ref": "#/$defs/VersionedParallelMapNode" + }, + { + "$ref": "#/$defs/VersionedPostgresDatabaseDatastore" + }, + { + "$ref": "#/$defs/VersionedRemoteAgent" + }, + { + "$ref": "#/$defs/VersionedRemoteTool" + }, + { + "$ref": "#/$defs/VersionedRemoteTransport" + }, + { + "$ref": "#/$defs/VersionedSSETransport" + }, + { + "$ref": "#/$defs/VersionedSSEmTLSTransport" + }, + { + "$ref": "#/$defs/VersionedServerTool" + }, + { + "$ref": "#/$defs/VersionedSpecializedAgent" + }, + { + "$ref": "#/$defs/VersionedStartNode" + }, + { + "$ref": "#/$defs/VersionedStdioTransport" + }, + { + "$ref": "#/$defs/VersionedStreamableHTTPTransport" + }, + { + "$ref": "#/$defs/VersionedStreamableHTTPmTLSTransport" + }, + { + "$ref": "#/$defs/VersionedSwarm" + }, + { + "$ref": "#/$defs/VersionedTlsOracleDatabaseConnectionConfig" + }, + { + "$ref": "#/$defs/VersionedTlsPostgresDatabaseConnectionConfig" + }, + { + "$ref": "#/$defs/VersionedTool" + }, + { + "$ref": "#/$defs/VersionedToolBox" + }, + { + "$ref": "#/$defs/VersionedToolNode" + }, + { + "$ref": "#/$defs/VersionedVllmConfig" + } + ] +} diff --git a/docs/pyagentspec/source/agentspec/json_spec/agentspec_json_spec_26_4_0.json b/docs/pyagentspec/source/agentspec/json_spec/agentspec_json_spec_26_4_0.json new file mode 100644 index 00000000..b8b2ff74 --- /dev/null +++ b/docs/pyagentspec/source/agentspec/json_spec/agentspec_json_spec_26_4_0.json @@ -0,0 +1,9418 @@ +{ + "$defs": { + "A2AAgent": { + "anyOf": [ + { + "$ref": "#/$defs/BaseA2AAgent" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "A2AConnectionConfig": { + "anyOf": [ + { + "$ref": "#/$defs/BaseA2AConnectionConfig" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "A2ASessionParameters": { + "description": "Class to specify parameters of the A2A session.", + "properties": { + "timeout": { + "default": 60.0, + "title": "Timeout", + "type": "number" + }, + "poll_interval": { + "default": 2.0, + "title": "Poll Interval", + "type": "number" + }, + "max_retries": { + "default": 5, + "title": "Max Retries", + "type": "integer" + } + }, + "title": "A2ASessionParameters", + "type": "object" + }, + "Agent": { + "anyOf": [ + { + "$ref": "#/$defs/BaseAgent" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "AgentNode": { + "anyOf": [ + { + "$ref": "#/$defs/BaseAgentNode" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "AgentSpecVersionEnum": { + "description": "An Enumeration for different versions of Agent Spec.", + "enum": [ + "25.4.1", + "25.4.2", + "26.1.0", + "26.1.2", + "26.3.0", + "26.4.0" + ], + "title": "AgentSpecVersionEnum", + "type": "string" + }, + "AgentSpecializationParameters": { + "anyOf": [ + { + "$ref": "#/$defs/BaseAgentSpecializationParameters" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "AgenticComponent": { + "anyOf": [ + { + "$ref": "#/$defs/BaseAgenticComponent" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "ApiNode": { + "anyOf": [ + { + "$ref": "#/$defs/BaseApiNode" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "AuthConfig": { + "anyOf": [ + { + "$ref": "#/$defs/BaseAuthConfig" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "BranchingNode": { + "anyOf": [ + { + "$ref": "#/$defs/BaseBranchingNode" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "BuiltinTool": { + "anyOf": [ + { + "$ref": "#/$defs/BaseBuiltinTool" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "CatchExceptionNode": { + "anyOf": [ + { + "$ref": "#/$defs/BaseCatchExceptionNode" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "ClientTool": { + "anyOf": [ + { + "$ref": "#/$defs/BaseClientTool" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "ClientTransport": { + "anyOf": [ + { + "$ref": "#/$defs/BaseClientTransport" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "ComponentWithIO": { + "anyOf": [ + { + "$ref": "#/$defs/BaseComponentWithIO" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "ControlFlowEdge": { + "anyOf": [ + { + "$ref": "#/$defs/BaseControlFlowEdge" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "ConversationSummarizationTransform": { + "anyOf": [ + { + "$ref": "#/$defs/BaseConversationSummarizationTransform" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "DataFlowEdge": { + "anyOf": [ + { + "$ref": "#/$defs/BaseDataFlowEdge" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "Datastore": { + "anyOf": [ + { + "$ref": "#/$defs/BaseDatastore" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "DbmsVectorChainLlmConfig": { + "anyOf": [ + { + "$ref": "#/$defs/BaseDbmsVectorChainLlmConfig" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "EndNode": { + "anyOf": [ + { + "$ref": "#/$defs/BaseEndNode" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "Flow": { + "anyOf": [ + { + "$ref": "#/$defs/BaseFlow" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "FlowNode": { + "anyOf": [ + { + "$ref": "#/$defs/BaseFlowNode" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "GeminiAIStudioAuthConfig": { + "anyOf": [ + { + "$ref": "#/$defs/BaseGeminiAIStudioAuthConfig" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "GeminiAuthConfig": { + "anyOf": [ + { + "$ref": "#/$defs/BaseGeminiAuthConfig" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "GeminiConfig": { + "anyOf": [ + { + "$ref": "#/$defs/BaseGeminiConfig" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "GeminiVertexAIAuthConfig": { + "anyOf": [ + { + "$ref": "#/$defs/BaseGeminiVertexAIAuthConfig" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "HandoffMode": { + "description": "Controls how agents in a Swarm may delegate work to one another.\n\nThis setting determines whether an agent is equipped with:\n\n * *send_message* \u2014 a tool for asking another agent to perform a sub-task and reply back.\n\n * *handoff_conversation* \u2014 a tool for transferring the full user\u2013agent conversation to another agent.\n\nDepending on the selected mode, agents have different capabilities for delegation and collaboration.", + "enum": [ + "always", + "never", + "optional" + ], + "title": "HandoffMode", + "type": "string" + }, + "InMemoryCollectionDatastore": { + "anyOf": [ + { + "$ref": "#/$defs/BaseInMemoryCollectionDatastore" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "InputMessageNode": { + "anyOf": [ + { + "$ref": "#/$defs/BaseInputMessageNode" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "LlmConfig": { + "anyOf": [ + { + "$ref": "#/$defs/BaseLlmConfig" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "LlmGenerationConfig": { + "additionalProperties": true, + "description": "A configuration object defining LLM generation parameters.\n\nParameters include number of tokens, sampling parameters, etc.", + "properties": { + "max_tokens": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Max Tokens" + }, + "temperature": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Temperature" + }, + "top_p": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Top P" + } + }, + "title": "LlmGenerationConfig", + "type": "object" + }, + "LlmNode": { + "anyOf": [ + { + "$ref": "#/$defs/BaseLlmNode" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "MCPTool": { + "anyOf": [ + { + "$ref": "#/$defs/BaseMCPTool" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "MCPToolBox": { + "anyOf": [ + { + "$ref": "#/$defs/BaseMCPToolBox" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "MCPToolSpec": { + "anyOf": [ + { + "$ref": "#/$defs/BaseMCPToolSpec" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "MTlsOracleDatabaseConnectionConfig": { + "anyOf": [ + { + "$ref": "#/$defs/BaseMTlsOracleDatabaseConnectionConfig" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "ManagerWorkers": { + "anyOf": [ + { + "$ref": "#/$defs/BaseManagerWorkers" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "MapNode": { + "anyOf": [ + { + "$ref": "#/$defs/BaseMapNode" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "MessageSummarizationTransform": { + "anyOf": [ + { + "$ref": "#/$defs/BaseMessageSummarizationTransform" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "MessageTransform": { + "anyOf": [ + { + "$ref": "#/$defs/BaseMessageTransform" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "ModelProvider": { + "description": "Provider of the model. It is used to ensure the requests to this model respect\nthe format expected by the provider.", + "enum": [ + "COHERE", + "GROK", + "META", + "OTHER", + "XAI" + ], + "title": "ModelProvider", + "type": "string" + }, + "Node": { + "anyOf": [ + { + "$ref": "#/$defs/BaseNode" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "OAuthClientConfig": { + "anyOf": [ + { + "$ref": "#/$defs/BaseOAuthClientConfig" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "OAuthConfig": { + "anyOf": [ + { + "$ref": "#/$defs/BaseOAuthConfig" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "OAuthEndpoints": { + "description": "Explicit OAuth endpoint configuration.\n\nUse this component when endpoint discovery is not available or not desired.\nThis groups the relevant endpoints required to execute OAuth authorization\ncode flows and token refresh.", + "properties": { + "authorization_endpoint": { + "title": "Authorization Endpoint", + "type": "string" + }, + "token_endpoint": { + "title": "Token Endpoint", + "type": "string" + }, + "refresh_endpoint": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Refresh Endpoint" + }, + "revocation_endpoint": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Revocation Endpoint" + }, + "userinfo_endpoint": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Userinfo Endpoint" + } + }, + "required": [ + "authorization_endpoint", + "token_endpoint" + ], + "title": "OAuthEndpoints", + "type": "object" + }, + "OciAPIType": { + "description": "Enumeration of API Types.", + "enum": [ + "oci", + "openai_chat_completions", + "openai_responses" + ], + "title": "OciAPIType", + "type": "string" + }, + "OciAgent": { + "anyOf": [ + { + "$ref": "#/$defs/BaseOciAgent" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "OciClientConfig": { + "anyOf": [ + { + "$ref": "#/$defs/BaseOciClientConfig" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "OciClientConfigWithApiKey": { + "anyOf": [ + { + "$ref": "#/$defs/BaseOciClientConfigWithApiKey" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "OciClientConfigWithInstancePrincipal": { + "anyOf": [ + { + "$ref": "#/$defs/BaseOciClientConfigWithInstancePrincipal" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "OciClientConfigWithResourcePrincipal": { + "anyOf": [ + { + "$ref": "#/$defs/BaseOciClientConfigWithResourcePrincipal" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "OciClientConfigWithSecurityToken": { + "anyOf": [ + { + "$ref": "#/$defs/BaseOciClientConfigWithSecurityToken" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "OciGenAiConfig": { + "anyOf": [ + { + "$ref": "#/$defs/BaseOciGenAiConfig" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "OllamaConfig": { + "anyOf": [ + { + "$ref": "#/$defs/BaseOllamaConfig" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "OpenAIAPIType": { + "description": "Enumeration of OpenAI API Types.\n\nchat completions: Chat Completions API\nresponses: Responses API", + "enum": [ + "chat_completions", + "responses" + ], + "title": "OpenAIAPIType", + "type": "string" + }, + "OpenAiCompatibleConfig": { + "anyOf": [ + { + "$ref": "#/$defs/BaseOpenAiCompatibleConfig" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "OpenAiConfig": { + "anyOf": [ + { + "$ref": "#/$defs/BaseOpenAiConfig" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "OracleDatabaseConnectionConfig": { + "anyOf": [ + { + "$ref": "#/$defs/BaseOracleDatabaseConnectionConfig" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "OracleDatabaseDatastore": { + "anyOf": [ + { + "$ref": "#/$defs/BaseOracleDatabaseDatastore" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "OutputMessageNode": { + "anyOf": [ + { + "$ref": "#/$defs/BaseOutputMessageNode" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "PKCEMethod": { + "enum": [ + "S256", + "plain" + ], + "title": "PKCEMethod", + "type": "string" + }, + "PKCEPolicy": { + "description": "Policy configuration for Proof Key for Code Exchange (PKCE).\n\nPKCE mitigates authorization code interception and injection attacks in\nauthorization code flows. Some protocols (such as MCP OAuth) require PKCE.", + "properties": { + "required": { + "default": true, + "title": "Required", + "type": "boolean" + }, + "method": { + "$ref": "#/$defs/PKCEMethod", + "default": "S256" + } + }, + "title": "PKCEPolicy", + "type": "object" + }, + "ParallelFlowNode": { + "anyOf": [ + { + "$ref": "#/$defs/BaseParallelFlowNode" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "ParallelMapNode": { + "anyOf": [ + { + "$ref": "#/$defs/BaseParallelMapNode" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "PostgresDatabaseConnectionConfig": { + "anyOf": [ + { + "$ref": "#/$defs/BasePostgresDatabaseConnectionConfig" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "PostgresDatabaseDatastore": { + "anyOf": [ + { + "$ref": "#/$defs/BasePostgresDatabaseDatastore" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "Property": { + "description": "This object must be a valid JSON Schema", + "type": "object" + }, + "ReductionMethod": { + "description": "Enumerator for the types of reduction available in the MapNode.", + "enum": [ + "append", + "average", + "max", + "min", + "sum" + ], + "title": "ReductionMethod", + "type": "string" + }, + "RemoteAgent": { + "anyOf": [ + { + "$ref": "#/$defs/BaseRemoteAgent" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "RemoteTool": { + "anyOf": [ + { + "$ref": "#/$defs/BaseRemoteTool" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "RemoteTransport": { + "anyOf": [ + { + "$ref": "#/$defs/BaseRemoteTransport" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "RetryPolicy": { + "additionalProperties": false, + "properties": { + "max_attempts": { + "default": 2, + "minimum": 0, + "title": "Max Attempts", + "type": "integer" + }, + "request_timeout": { + "anyOf": [ + { + "exclusiveMinimum": 0, + "type": "number" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Request Timeout" + }, + "initial_retry_delay": { + "default": 1.0, + "minimum": 0, + "title": "Initial Retry Delay", + "type": "number" + }, + "max_retry_delay": { + "default": 8.0, + "minimum": 0, + "title": "Max Retry Delay", + "type": "number" + }, + "backoff_factor": { + "default": 2.0, + "exclusiveMinimum": 0, + "title": "Backoff Factor", + "type": "number" + }, + "jitter": { + "anyOf": [ + { + "enum": [ + "decorrelated", + "equal", + "full", + "full_and_equal_for_throttle" + ], + "type": "string" + }, + { + "type": "null" + } + ], + "default": "full_and_equal_for_throttle", + "title": "Jitter" + }, + "service_error_retry_on_any_5xx": { + "default": true, + "title": "Service Error Retry On Any 5Xx", + "type": "boolean" + }, + "recoverable_statuses": { + "additionalProperties": { + "items": { + "type": "string" + }, + "type": "array" + }, + "title": "Recoverable Statuses", + "type": "object" + } + }, + "title": "RetryPolicy", + "type": "object" + }, + "SSETransport": { + "anyOf": [ + { + "$ref": "#/$defs/BaseSSETransport" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "SSEmTLSTransport": { + "anyOf": [ + { + "$ref": "#/$defs/BaseSSEmTLSTransport" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "ScopePolicy": { + "enum": [ + "fixed", + "use_challenge_or_supported" + ], + "title": "ScopePolicy", + "type": "string" + }, + "ServerTool": { + "anyOf": [ + { + "$ref": "#/$defs/BaseServerTool" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "ServingMode": { + "description": "Serving mode to use for the GenAI service", + "enum": [ + "DEDICATED", + "ON_DEMAND" + ], + "title": "ServingMode", + "type": "string" + }, + "SessionParameters": { + "description": "Class to specify parameters of the MCP client session.", + "properties": { + "read_timeout_seconds": { + "default": 60.0, + "title": "Read Timeout Seconds", + "type": "number" + } + }, + "title": "SessionParameters", + "type": "object" + }, + "SpecializedAgent": { + "anyOf": [ + { + "$ref": "#/$defs/BaseSpecializedAgent" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "StartNode": { + "anyOf": [ + { + "$ref": "#/$defs/BaseStartNode" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "StdioTransport": { + "anyOf": [ + { + "$ref": "#/$defs/BaseStdioTransport" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "StreamableHTTPTransport": { + "anyOf": [ + { + "$ref": "#/$defs/BaseStreamableHTTPTransport" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "StreamableHTTPmTLSTransport": { + "anyOf": [ + { + "$ref": "#/$defs/BaseStreamableHTTPmTLSTransport" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "Swarm": { + "anyOf": [ + { + "$ref": "#/$defs/BaseSwarm" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "TlsOracleDatabaseConnectionConfig": { + "anyOf": [ + { + "$ref": "#/$defs/BaseTlsOracleDatabaseConnectionConfig" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "TlsPostgresDatabaseConnectionConfig": { + "anyOf": [ + { + "$ref": "#/$defs/BaseTlsPostgresDatabaseConnectionConfig" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "Tool": { + "anyOf": [ + { + "$ref": "#/$defs/BaseTool" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "ToolBox": { + "anyOf": [ + { + "$ref": "#/$defs/BaseToolBox" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "ToolNode": { + "anyOf": [ + { + "$ref": "#/$defs/BaseToolNode" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "VllmConfig": { + "anyOf": [ + { + "$ref": "#/$defs/BaseVllmConfig" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ] + }, + "BaseA2AAgent": { + "additionalProperties": false, + "description": "Component which communicates with a remote server agent using the A2A Protocol.", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "inputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Inputs" + }, + "outputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Outputs" + }, + "agent_url": { + "title": "Agent Url", + "type": "string" + }, + "connection_config": { + "$ref": "#/$defs/A2AConnectionConfig" + }, + "session_parameters": { + "$ref": "#/$defs/A2ASessionParameters", + "default": { + "timeout": 60.0, + "poll_interval": 2.0, + "max_retries": 5 + } + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "A2AAgent" + } + }, + "required": [ + "agent_url", + "connection_config", + "name" + ], + "title": "A2AAgent", + "type": "object", + "x-abstract-component": false + }, + "BaseA2AConnectionConfig": { + "additionalProperties": false, + "description": "Class to specify configuration settings for establishing a connection in A2A communication.", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "timeout": { + "default": 600.0, + "title": "Timeout", + "type": "number" + }, + "headers": { + "anyOf": [ + { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Headers" + }, + "verify": { + "default": true, + "title": "Verify", + "type": "boolean" + }, + "key_file": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Key File" + }, + "cert_file": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Cert File" + }, + "ssl_ca_cert": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Ssl Ca Cert" + }, + "retry_policy": { + "anyOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/RetryPolicy" + } + ], + "default": null + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "A2AConnectionConfig" + } + }, + "required": [ + "name" + ], + "title": "A2AConnectionConfig", + "type": "object", + "x-abstract-component": false + }, + "BaseAgent": { + "additionalProperties": false, + "description": "An agent is a component that can do several rounds of conversation to solve a task.\n\nIt can be executed by itself, or be executed in a flow using an AgentNode.\n\n\nExamples\n--------\n>>> from pyagentspec.agent import Agent\n>>> from pyagentspec.property import Property\n>>> expertise_property=Property(\n... json_schema={\"title\": \"domain_of_expertise\", \"type\": \"string\"}\n... )\n>>> system_prompt = '''You are an expert in {{domain_of_expertise}}.\n... Please help the users with their requests.'''\n>>> agent = Agent(\n... name=\"Adaptive expert agent\",\n... system_prompt=system_prompt,\n... llm_config=llm_config,\n... inputs=[expertise_property],\n... )", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "inputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Inputs" + }, + "outputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Outputs" + }, + "llm_config": { + "$ref": "#/$defs/LlmConfig" + }, + "system_prompt": { + "title": "System Prompt", + "type": "string" + }, + "tools": { + "items": { + "$ref": "#/$defs/Tool" + }, + "title": "Tools", + "type": "array" + }, + "toolboxes": { + "items": { + "$ref": "#/$defs/ToolBox" + }, + "title": "Toolboxes", + "type": "array" + }, + "human_in_the_loop": { + "default": true, + "title": "Human In The Loop", + "type": "boolean" + }, + "transforms": { + "items": { + "$ref": "#/$defs/MessageTransform" + }, + "title": "Transforms", + "type": "array" + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "Agent" + } + }, + "required": [ + "llm_config", + "name", + "system_prompt" + ], + "title": "Agent", + "type": "object", + "x-abstract-component": false + }, + "BaseAgentNode": { + "additionalProperties": false, + "description": "The agent execution node is a node that will execute an agent as part of a flow.\n\nIf branches are configured, the agent will be prompted to select a branch before the agent node\ncompletes to transition to another node of the Flow.\n\n- **Inputs**\n Inferred from the definition of the agent to execute.\n- **Outputs**\n Inferred from the definition of the agent to execute.\n- **Branches**\n One, the default next.\n\nExamples\n--------\n>>> from pyagentspec.flows.flow import Flow\n>>> from pyagentspec.flows.edges import ControlFlowEdge, DataFlowEdge\n>>> from pyagentspec.agent import Agent\n>>> from pyagentspec.property import Property\n>>> from pyagentspec.flows.nodes import AgentNode, StartNode, EndNode\n>>> from pyagentspec.tools import ServerTool\n>>> query_property = Property(json_schema={\"title\": \"query\", \"type\": \"string\"})\n>>> search_results_property = Property(\n... json_schema={\"title\": \"search_results\", \"type\": \"array\", \"items\": {\"type\": \"string\"}}\n... )\n>>> search_tool = ServerTool(\n... name=\"search_tool\",\n... description=(\n... \"This tool runs a web search with the given query \"\n... \"and returns the most relevant results\"\n... ),\n... inputs=[query_property],\n... outputs=[search_results_property],\n... )\n>>> agent = Agent(\n... name=\"Search agent\",\n... llm_config=llm_config,\n... system_prompt=(\n... \"Your task is to gather the required information for the user: {{query}}\"\n... ),\n... tools=[search_tool],\n... outputs=[search_results_property],\n... )\n>>> start_node = StartNode(name=\"start\", inputs=[query_property])\n>>> end_node = EndNode(name=\"end\", outputs=[search_results_property])\n>>> agent_node = AgentNode(\n... name=\"Search agent node\",\n... agent=agent,\n... )\n>>> flow = Flow(\n... name=\"Search agent flow\",\n... start_node=start_node,\n... nodes=[start_node, agent_node, end_node],\n... control_flow_connections=[\n... ControlFlowEdge(name=\"start_to_agent\", from_node=start_node, to_node=agent_node),\n... ControlFlowEdge(name=\"agent_to_end\", from_node=agent_node, to_node=end_node),\n... ],\n... data_flow_connections=[\n... DataFlowEdge(\n... name=\"query_edge\",\n... source_node=start_node,\n... source_output=\"query\",\n... destination_node=agent_node,\n... destination_input=\"query\",\n... ),\n... DataFlowEdge(\n... name=\"search_results_edge\",\n... source_node=agent_node,\n... source_output=\"search_results\",\n... destination_node=end_node,\n... destination_input=\"search_results\"\n... ),\n... ],\n... )", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "inputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Inputs" + }, + "outputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Outputs" + }, + "branches": { + "items": { + "type": "string" + }, + "title": "Branches", + "type": "array" + }, + "agent": { + "$ref": "#/$defs/AgenticComponent" + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "AgentNode" + } + }, + "required": [ + "agent", + "name" + ], + "title": "AgentNode", + "type": "object", + "x-abstract-component": false + }, + "BaseAgentSpecializationParameters": { + "additionalProperties": false, + "description": "Parameters used to specialize an agent for a certain goal or task.", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "inputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Inputs" + }, + "outputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Outputs" + }, + "additional_instructions": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Additional Instructions" + }, + "additional_tools": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Tool" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Additional Tools" + }, + "human_in_the_loop": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Human In The Loop" + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "AgentSpecializationParameters" + } + }, + "required": [ + "name" + ], + "title": "AgentSpecializationParameters", + "type": "object", + "x-abstract-component": false + }, + "BaseAgenticComponent": { + "anyOf": [ + { + "$ref": "#/$defs/A2AAgent" + }, + { + "$ref": "#/$defs/Agent" + }, + { + "$ref": "#/$defs/Flow" + }, + { + "$ref": "#/$defs/ManagerWorkers" + }, + { + "$ref": "#/$defs/OciAgent" + }, + { + "$ref": "#/$defs/RemoteAgent" + }, + { + "$ref": "#/$defs/SpecializedAgent" + }, + { + "$ref": "#/$defs/Swarm" + } + ], + "x-abstract-component": true + }, + "BaseApiNode": { + "additionalProperties": false, + "description": "Make an API call.\n\nThis node is intended to be a part of a Flow.\n\n- **Inputs**\n Inferred from the json spec retrieved from API Spec URI, if available and reachable.\n Otherwise, users have to manually specify them.\n- **Outputs**\n Inferred from the json spec retrieved from API Spec URI, if available and reachable.\n Otherwise, users should manually specify them.\n\n If None is given, ``pyagentspec`` infers a generic property of any type named ``response``.\n- **Branches**\n One, the default next.\n\n\nExamples\n--------\n>>> from pyagentspec.flows.nodes import ApiNode\n>>> from pyagentspec.property import Property\n>>> weather_result_property = Property(\n... json_schema={\n... \"title\": \"zurich_weather\",\n... \"type\": \"object\",\n... \"properties\": {\n... \"temperature\": {\n... \"type\": \"number\",\n... \"description\": \"Temperature in celsius degrees\",\n... },\n... \"weather\": {\"type\": \"string\"}\n... },\n... }\n... )\n>>> call_current_weather_step = ApiNode(\n... name=\"Weather API call node\",\n... url=\"https://example.com/weather\",\n... http_method = \"GET\",\n... query_params={\n... \"location\": \"zurich\",\n... },\n... outputs=[weather_result_property]\n... )\n>>>\n>>> item_id_property = Property(\n... json_schema={\"title\": \"item_id\", \"type\": \"string\"}\n... )\n>>> order_id_property = Property(\n... json_schema={\"title\": \"order_id\", \"type\": \"string\"}\n... )\n>>> store_id_property = Property(\n... json_schema={\"title\": \"store_id\", \"type\": \"string\"}\n... )\n>>> session_id_property = Property(\n... json_schema={\"title\": \"session_id\", \"type\": \"string\"}\n... )\n>>> create_order_step = ApiNode(\n... name=\"Orders api call node\",\n... url=\"https://example.com/orders/{{ order_id }}\",\n... http_method=\"POST\",\n... # sending an object which will automatically be transformed into JSON\n... data={\n... # define a static body parameter\n... \"topic_id\": 12345,\n... # define a templated body parameter.\n... # The value for {{ item_id }} will be taken from the IO system at runtime\n... \"item_id\": \"{{ item_id }}\",\n... },\n... query_params={\n... # provide one templated query parameter called \"store_id\"\n... # which will take its value from the IO system from key \"store_id\"\n... \"store_id\": \"{{ store_id }}\",\n... },\n... headers={\n... # set header session_id. the value is coming from the IO system\n... \"session_id\": \"{{ session_id }}\",\n... },\n... inputs=[item_id_property, order_id_property, store_id_property, session_id_property],\n... )", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "inputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Inputs" + }, + "outputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Outputs" + }, + "branches": { + "items": { + "type": "string" + }, + "title": "Branches", + "type": "array" + }, + "url": { + "title": "Url", + "type": "string" + }, + "http_method": { + "title": "Http Method", + "type": "string" + }, + "api_spec_uri": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Api Spec Uri" + }, + "data": { + "anyOf": [ + { + "type": "string" + }, + { + "format": "binary", + "type": "string" + }, + {} + ], + "title": "Data" + }, + "query_params": { + "additionalProperties": true, + "title": "Query Params", + "type": "object" + }, + "headers": { + "additionalProperties": true, + "title": "Headers", + "type": "object" + }, + "sensitive_headers": { + "additionalProperties": true, + "title": "Sensitive Headers", + "type": "object" + }, + "url_allow_list": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Url Allow List" + }, + "retry_policy": { + "anyOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/RetryPolicy" + } + ], + "default": null + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "ApiNode" + } + }, + "required": [ + "http_method", + "name", + "url" + ], + "title": "ApiNode", + "type": "object", + "x-abstract-component": false + }, + "BaseAuthConfig": { + "anyOf": [ + { + "$ref": "#/$defs/OAuthConfig" + } + ], + "x-abstract-component": true + }, + "BaseBranchingNode": { + "additionalProperties": false, + "description": "Select the next node to transition to based on a mapping.\n\nThe input is used as key for the mapping. If the input does not correspond to any of the keys\nof the mapping the branch 'default' will be selected. This node is intended to be a part of a\nFlow.\n\n- **Inputs**\n The input value that should be used as key for the mapping.\n\n If None is given, ``pyagentspec`` infers a string property named ``branching_mapping_key``.\n- **Outputs**\n None.\n- **Branches**\n One for each value in the mapping, plus a branch called ``default``,\n which is the branch taken by the flow when mapping fails\n (i.e., the input does not match any key in the mapping).\n\nExamples\n--------\n>>> from pyagentspec.agent import Agent\n>>> from pyagentspec.flows.edges import ControlFlowEdge, DataFlowEdge\n>>> from pyagentspec.flows.flow import Flow\n>>> from pyagentspec.flows.nodes import AgentNode, BranchingNode, StartNode, EndNode\n>>> from pyagentspec.property import Property\n>>> CORRECT_PASSWORD_BRANCH = \"PASSWORD_OK\"\n>>> password_property = Property(\n... json_schema={\"title\": \"password\", \"type\": \"string\"}\n... )\n>>> agent = Agent(\n... name=\"User input agent\",\n... llm_config=llm_config,\n... system_prompt=(\n... \"Your task is to ask the password to the user. \"\n... \"Once you get it, submit it and end.\"\n... ),\n... outputs=[password_property],\n... )\n>>> start_node = StartNode(name=\"start\")\n>>> access_granted_end_node = EndNode(\n... name=\"access granted end\", branch_name=\"ACCESS_GRANTED\"\n... )\n>>> access_denied_end_node = EndNode(\n... name=\"access denied end\", branch_name=\"ACCESS_DENIED\"\n... )\n>>> branching_node = BranchingNode(\n... name=\"password check\",\n... mapping={\"123456\": CORRECT_PASSWORD_BRANCH},\n... inputs=[password_property]\n... )\n>>> agent_node = AgentNode(\n... name=\"User input agent node\",\n... agent=agent,\n... )\n>>> assistant = Flow(\n... name=\"Check access flow\",\n... start_node=start_node,\n... nodes=[\n... start_node,\n... agent_node,\n... branching_node,\n... access_granted_end_node,\n... access_denied_end_node\n... ],\n... control_flow_connections=[\n... ControlFlowEdge(\n... name=\"start_to_agent\",\n... from_node=start_node,\n... to_node=agent_node\n... ),\n... ControlFlowEdge(\n... name=\"agent_to_branching\",\n... from_node=agent_node,\n... to_node=branching_node\n... ),\n... ControlFlowEdge(\n... name=\"branching_to_access_granted\",\n... from_node=branching_node,\n... from_branch=CORRECT_PASSWORD_BRANCH,\n... to_node=access_granted_end_node,\n... ),\n... ControlFlowEdge(\n... name=\"branching_to_access_denied\",\n... from_node=branching_node,\n... from_branch=BranchingNode.DEFAULT_BRANCH,\n... to_node=access_denied_end_node,\n... ),\n... ],\n... data_flow_connections=[\n... DataFlowEdge(\n... name=\"password_edge\",\n... source_node=agent_node,\n... source_output=\"password\",\n... destination_node=branching_node,\n... destination_input=\"password\",\n... ),\n... ],\n... )", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "inputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Inputs" + }, + "outputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Outputs" + }, + "branches": { + "items": { + "type": "string" + }, + "title": "Branches", + "type": "array" + }, + "mapping": { + "additionalProperties": { + "type": "string" + }, + "title": "Mapping", + "type": "object" + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "BranchingNode" + } + }, + "required": [ + "mapping", + "name" + ], + "title": "BranchingNode", + "type": "object", + "x-abstract-component": false + }, + "BaseBuiltinTool": { + "additionalProperties": false, + "description": "A tool that is built into and executed by the orchestrator", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "inputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Inputs" + }, + "outputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Outputs" + }, + "requires_confirmation": { + "default": false, + "title": "Requires Confirmation", + "type": "boolean" + }, + "tool_type": { + "title": "Tool Type", + "type": "string" + }, + "configuration": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Configuration" + }, + "executor_name": { + "anyOf": [ + { + "type": "string" + }, + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Executor Name" + }, + "tool_version": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Tool Version" + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "BuiltinTool" + } + }, + "required": [ + "name", + "tool_type" + ], + "title": "BuiltinTool", + "type": "object", + "x-abstract-component": false + }, + "BaseCatchExceptionNode": { + "additionalProperties": false, + "description": "Node to execute a Flow and catch exceptions.\n\n- If no exception is caught, the node will transition to the branches of its subflow.\n- If an exception is caught, it will transition to an exception branch.\n\nInputs\n------\nSame as the inputs from the ``subflow``.\n\nOutputs\n-------\n\n- The outputs of the ``subflow``.\n If an exception is raised, the default values of each output property are used.\n- An additional output named ``caught_exception_info`` with type ``string | null``\n and default value ``null``. Executors may populate it with a non-sensitive\n error description when an exception is caught.\n\nBranches\n--------\n\n- The branches of the ``subflow``\n- One additional branch named ``caught_exception_branch``\n\nSecurity Considerations\n-----------------------\n\nSee security considerations regarding exception catching in\nthe :ref:`Security Considerations `", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "inputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Inputs" + }, + "outputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Outputs" + }, + "branches": { + "items": { + "type": "string" + }, + "title": "Branches", + "type": "array" + }, + "subflow": { + "$ref": "#/$defs/Flow" + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "CatchExceptionNode" + } + }, + "required": [ + "name", + "subflow" + ], + "title": "CatchExceptionNode", + "type": "object", + "x-abstract-component": false + }, + "BaseClientTool": { + "additionalProperties": false, + "description": "A tool that needs to be run by the client application.", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "inputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Inputs" + }, + "outputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Outputs" + }, + "requires_confirmation": { + "default": false, + "title": "Requires Confirmation", + "type": "boolean" + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "ClientTool" + } + }, + "required": [ + "name" + ], + "title": "ClientTool", + "type": "object", + "x-abstract-component": false + }, + "BaseClientTransport": { + "anyOf": [ + { + "$ref": "#/$defs/RemoteTransport" + }, + { + "$ref": "#/$defs/SSETransport" + }, + { + "$ref": "#/$defs/SSEmTLSTransport" + }, + { + "$ref": "#/$defs/StdioTransport" + }, + { + "$ref": "#/$defs/StreamableHTTPTransport" + }, + { + "$ref": "#/$defs/StreamableHTTPmTLSTransport" + } + ], + "x-abstract-component": true + }, + "BaseComponentWithIO": { + "anyOf": [ + { + "$ref": "#/$defs/A2AAgent" + }, + { + "$ref": "#/$defs/Agent" + }, + { + "$ref": "#/$defs/AgentNode" + }, + { + "$ref": "#/$defs/AgentSpecializationParameters" + }, + { + "$ref": "#/$defs/AgenticComponent" + }, + { + "$ref": "#/$defs/ApiNode" + }, + { + "$ref": "#/$defs/BranchingNode" + }, + { + "$ref": "#/$defs/BuiltinTool" + }, + { + "$ref": "#/$defs/CatchExceptionNode" + }, + { + "$ref": "#/$defs/ClientTool" + }, + { + "$ref": "#/$defs/EndNode" + }, + { + "$ref": "#/$defs/Flow" + }, + { + "$ref": "#/$defs/FlowNode" + }, + { + "$ref": "#/$defs/InputMessageNode" + }, + { + "$ref": "#/$defs/LlmNode" + }, + { + "$ref": "#/$defs/MCPTool" + }, + { + "$ref": "#/$defs/MCPToolSpec" + }, + { + "$ref": "#/$defs/ManagerWorkers" + }, + { + "$ref": "#/$defs/MapNode" + }, + { + "$ref": "#/$defs/Node" + }, + { + "$ref": "#/$defs/OciAgent" + }, + { + "$ref": "#/$defs/OutputMessageNode" + }, + { + "$ref": "#/$defs/ParallelFlowNode" + }, + { + "$ref": "#/$defs/ParallelMapNode" + }, + { + "$ref": "#/$defs/RemoteAgent" + }, + { + "$ref": "#/$defs/RemoteTool" + }, + { + "$ref": "#/$defs/ServerTool" + }, + { + "$ref": "#/$defs/SpecializedAgent" + }, + { + "$ref": "#/$defs/StartNode" + }, + { + "$ref": "#/$defs/Swarm" + }, + { + "$ref": "#/$defs/Tool" + }, + { + "$ref": "#/$defs/ToolNode" + } + ], + "x-abstract-component": true + }, + "BaseControlFlowEdge": { + "additionalProperties": false, + "description": "A control flow edge specifies a possible transition from a node to another in a flow.\n\nA single node can have several potential next nodes, in which case several control flow edges\nshould be present in the control flow connections of that flow.", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "from_node": { + "$ref": "#/$defs/Node" + }, + "from_branch": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "From Branch" + }, + "to_node": { + "$ref": "#/$defs/Node" + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "ControlFlowEdge" + } + }, + "required": [ + "from_node", + "name", + "to_node" + ], + "title": "ControlFlowEdge", + "type": "object", + "x-abstract-component": false + }, + "BaseConversationSummarizationTransform": { + "additionalProperties": false, + "description": "Summarizes conversations exceeding a configured size threshold using an LLM and caches\nconversation summaries in a ``Datastore``.\n\nThis is useful to reduce long conversation history into a concise context for downstream LLM calls.\n\nExamples\n--------\n>>> from pyagentspec.transforms import ConversationSummarizationTransform\n>>> summarization_transform = ConversationSummarizationTransform(\n... name=\"conversation-summarizer\",\n... llm=llm_config,\n... max_num_messages=30,\n... min_num_messages=10\n... )", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "llm": { + "$ref": "#/$defs/LlmConfig" + }, + "max_num_messages": { + "anyOf": [ + { + "exclusiveMinimum": 0, + "type": "integer" + }, + { + "type": "null" + } + ], + "default": 50, + "title": "Max Num Messages" + }, + "max_num_characters": { + "anyOf": [ + { + "exclusiveMinimum": 0, + "type": "integer" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Max Num Characters" + }, + "min_num_messages": { + "default": 10, + "exclusiveMinimum": 0, + "title": "Min Num Messages", + "type": "integer" + }, + "summarization_instructions": { + "default": "Please make a summary of this conversation. Include relevant information and keep it short. Your response will replace the messages, so just output the summary directly, no introduction needed.", + "title": "Summarization Instructions", + "type": "string" + }, + "summarized_conversation_template": { + "default": "Summarized conversation: {{summary}}", + "title": "Summarized Conversation Template", + "type": "string" + }, + "max_cache_size": { + "anyOf": [ + { + "exclusiveMinimum": 0, + "type": "integer" + }, + { + "type": "null" + } + ], + "default": 10000, + "title": "Max Cache Size" + }, + "max_cache_lifetime": { + "anyOf": [ + { + "exclusiveMinimum": 0, + "type": "integer" + }, + { + "type": "null" + } + ], + "default": 14400, + "title": "Max Cache Lifetime" + }, + "cache_collection_name": { + "default": "summarized_conversations_cache", + "title": "Cache Collection Name", + "type": "string" + }, + "datastore": { + "anyOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/InMemoryCollectionDatastore" + }, + { + "$ref": "#/$defs/OracleDatabaseDatastore" + }, + { + "$ref": "#/$defs/PostgresDatabaseDatastore" + } + ], + "title": "Datastore" + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "ConversationSummarizationTransform" + } + }, + "required": [ + "llm", + "name" + ], + "title": "ConversationSummarizationTransform", + "type": "object", + "x-abstract-component": false + }, + "BaseDataFlowEdge": { + "additionalProperties": false, + "description": "A data flow edge specifies how the output of a node propagates as input of another node.\n\nAn outputs can be propagated as input of several nodes.", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "source_node": { + "$ref": "#/$defs/Node" + }, + "source_output": { + "title": "Source Output", + "type": "string" + }, + "destination_node": { + "$ref": "#/$defs/Node" + }, + "destination_input": { + "title": "Destination Input", + "type": "string" + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "DataFlowEdge" + } + }, + "required": [ + "destination_input", + "destination_node", + "name", + "source_node", + "source_output" + ], + "title": "DataFlowEdge", + "type": "object", + "x-abstract-component": false + }, + "BaseDatastore": { + "anyOf": [ + { + "$ref": "#/$defs/InMemoryCollectionDatastore" + }, + { + "$ref": "#/$defs/OracleDatabaseDatastore" + }, + { + "$ref": "#/$defs/PostgresDatabaseDatastore" + } + ], + "x-abstract-component": true + }, + "BaseDbmsVectorChainLlmConfig": { + "additionalProperties": false, + "description": "Configure an LLM executed by Oracle Database through DBMS_VECTOR_CHAIN.", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "model_id": { + "minLength": 1, + "title": "Model Id", + "type": "string" + }, + "provider": { + "minLength": 1, + "title": "Provider", + "type": "string" + }, + "url": { + "minLength": 1, + "title": "Url", + "type": "string" + }, + "default_generation_parameters": { + "anyOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/LlmGenerationConfig" + } + ], + "default": null + }, + "retry_policy": { + "anyOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/RetryPolicy" + } + ], + "default": null + }, + "host": { + "anyOf": [ + { + "const": "local", + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Host" + }, + "credential_name": { + "anyOf": [ + { + "minLength": 1, + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Credential Name" + }, + "transfer_timeout": { + "anyOf": [ + { + "minimum": 0, + "type": "integer" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Transfer Timeout" + }, + "connection_config": { + "anyOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/OracleDatabaseConnectionConfig" + } + ], + "default": null + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "DbmsVectorChainLlmConfig" + } + }, + "required": [ + "model_id", + "name", + "provider", + "url" + ], + "title": "DbmsVectorChainLlmConfig", + "type": "object", + "x-abstract-component": false + }, + "BaseEndNode": { + "additionalProperties": false, + "description": "End nodes denote the end of the execution of a flow.\n\nThere might be several end nodes in a flow, in which case the executor of the flow\nshould be able to track which one was reached and pass that back to the caller.\n\n- **Inputs**\n The list of inputs of the step. If both input and output properties are specified they\n must be an exact match\n\n If None is given, ``pyagentspec`` copies the outputs provided, if any. Otherwise, no input is exposed.\n- **Outputs**\n The list of outputs that should be exposed by the flow. If both input and output properties\n are specified they must be an exact match\n\n If None is given, ``pyagentspec`` copies the inputs provided, if any. Otherwise, no output is exposed.\n- **Branches**\n None.\n\nExamples\n--------\n>>> from pyagentspec.agent import Agent\n>>> from pyagentspec.flows.edges import ControlFlowEdge, DataFlowEdge\n>>> from pyagentspec.flows.flow import Flow\n>>> from pyagentspec.flows.nodes import AgentNode, BranchingNode, StartNode, EndNode\n>>> from pyagentspec.property import Property\n>>> languages_to_branch_name = {\n... \"english\": \"ENGLISH\",\n... \"spanish\": \"SPANISH\",\n... \"italian\": \"ITALIAN\",\n... }\n>>> language_property = Property(\n... json_schema={\"title\": \"language\", \"type\": \"string\"}\n... )\n>>> agent = Agent(\n... name=\"Language detector agent\",\n... llm_config=llm_config,\n... system_prompt=(\n... \"Your task is to understand the language spoken by the user.\"\n... \"Please output only the language in lowercase and submit.\"\n... ),\n... outputs=[language_property],\n... )\n>>> start_node = StartNode(name=\"start\")\n>>> english_end_node = EndNode(\n... name=\"english end\", branch_name=languages_to_branch_name[\"english\"]\n... )\n>>> spanish_end_node = EndNode(\n... name=\"spanish end\", branch_name=languages_to_branch_name[\"spanish\"]\n... )\n>>> italian_end_node = EndNode(\n... name=\"italian end\", branch_name=languages_to_branch_name[\"italian\"]\n... )\n>>> unknown_end_node = EndNode(name=\"unknown language end\", branch_name=\"unknown\")\n>>> branching_node = BranchingNode(\n... name=\"language check\",\n... mapping=languages_to_branch_name,\n... inputs=[language_property]\n... )\n>>> agent_node = AgentNode(\n... name=\"User input agent node\",\n... agent=agent,\n... )\n>>> assistant = Flow(\n... name=\"Check access flow\",\n... start_node=start_node,\n... nodes=[\n... start_node,\n... agent_node,\n... branching_node,\n... english_end_node,\n... spanish_end_node,\n... italian_end_node,\n... unknown_end_node,\n... ],\n... control_flow_connections=[\n... ControlFlowEdge(\n... name=\"start_to_agent\", from_node=start_node, to_node=agent_node\n... ),\n... ControlFlowEdge(\n... name=\"agent_to_branching\", from_node=agent_node, to_node=branching_node\n... ),\n... ControlFlowEdge(\n... name=\"branching_to_english_end\",\n... from_node=branching_node,\n... from_branch=languages_to_branch_name[\"english\"],\n... to_node=english_end_node,\n... ),\n... ControlFlowEdge(\n... name=\"branching_to_spanish_end\",\n... from_node=branching_node,\n... from_branch=languages_to_branch_name[\"spanish\"],\n... to_node=spanish_end_node,\n... ),\n... ControlFlowEdge(\n... name=\"branching_to_italian_end\",\n... from_node=branching_node,\n... from_branch=languages_to_branch_name[\"italian\"],\n... to_node=italian_end_node,\n... ),\n... ControlFlowEdge(\n... name=\"branching_to_unknown_end\",\n... from_node=branching_node,\n... from_branch=BranchingNode.DEFAULT_BRANCH,\n... to_node=unknown_end_node,\n... ),\n... ],\n... data_flow_connections=[\n... DataFlowEdge(\n... name=\"language_edge\",\n... source_node=agent_node,\n... source_output=\"language\",\n... destination_node=branching_node,\n... destination_input=\"language\",\n... ),\n... ],\n... )", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "inputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Inputs" + }, + "outputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Outputs" + }, + "branches": { + "items": { + "type": "string" + }, + "title": "Branches", + "type": "array" + }, + "branch_name": { + "default": "next", + "title": "Branch Name", + "type": "string" + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "EndNode" + } + }, + "required": [ + "name" + ], + "title": "EndNode", + "type": "object", + "x-abstract-component": false + }, + "BaseFlow": { + "additionalProperties": false, + "description": "A flow is a component to model sequences of operations to do in a precised order.\n\nThe operations and sequence is defined by the nodes and transitions associated to the flow.\nSteps can be deterministic, or for some use LLMs.\n\nExample\n-------\n>>> from pyagentspec.property import Property\n>>> from pyagentspec.flows.flow import Flow\n>>> from pyagentspec.flows.edges import ControlFlowEdge, DataFlowEdge\n>>> from pyagentspec.flows.nodes import LlmNode, StartNode, EndNode\n>>> prompt_property = Property(\n... json_schema={\"title\": \"prompt\", \"type\": \"string\"}\n... )\n>>> llm_output_property = Property(\n... json_schema={\"title\": \"llm_output\", \"type\": \"string\"}\n... )\n>>> start_node = StartNode(name=\"start\", inputs=[prompt_property])\n>>> end_node = EndNode(name=\"end\", outputs=[llm_output_property])\n>>> llm_node = LlmNode(\n... name=\"simple llm node\",\n... llm_config=llm_config,\n... prompt_template=\"{{prompt}}\",\n... inputs=[prompt_property],\n... outputs=[llm_output_property],\n... )\n>>> flow = Flow(\n... name=\"Simple prompting flow\",\n... start_node=start_node,\n... nodes=[start_node, llm_node, end_node],\n... control_flow_connections=[\n... ControlFlowEdge(name=\"start_to_llm\", from_node=start_node, to_node=llm_node),\n... ControlFlowEdge(name=\"llm_to_end\", from_node=llm_node, to_node=end_node),\n... ],\n... data_flow_connections=[\n... DataFlowEdge(\n... name=\"prompt_edge\",\n... source_node=start_node,\n... source_output=\"prompt\",\n... destination_node=llm_node,\n... destination_input=\"prompt\",\n... ),\n... DataFlowEdge(\n... name=\"llm_output_edge\",\n... source_node=llm_node,\n... source_output=\"llm_output\",\n... destination_node=end_node,\n... destination_input=\"llm_output\"\n... ),\n... ],\n... )", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "inputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Inputs" + }, + "outputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Outputs" + }, + "start_node": { + "$ref": "#/$defs/Node" + }, + "nodes": { + "items": { + "$ref": "#/$defs/Node" + }, + "title": "Nodes", + "type": "array" + }, + "control_flow_connections": { + "items": { + "$ref": "#/$defs/ControlFlowEdge" + }, + "title": "Control Flow Connections", + "type": "array" + }, + "data_flow_connections": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/DataFlowEdge" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Data Flow Connections" + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "Flow" + } + }, + "required": [ + "control_flow_connections", + "name", + "nodes", + "start_node" + ], + "title": "Flow", + "type": "object", + "x-abstract-component": false + }, + "BaseFlowNode": { + "additionalProperties": false, + "description": "The flow node executes a subflow as part of a flow.\n\n- **Inputs**\n Inferred from the inner structure. It's the sets of inputs\n required by the StartNode of the inner flow.\n- **Outputs**\n Inferred from the inner structure. It's the union of the\n sets of outputs exposed by the EndNodes of the inner flow.\n- **Branches**\n Inferred from the inner flow, one per each different value of the attribute\n ``branch_name`` of the nodes of type EndNode in the inner flow.\n\nExample\n-------\nThe ``FlowNode`` is particularly suitable when subflows can be reused inside a project.\nLet's see an example with a flow that estimates numerical value\nusing the \"wisdowm of the crowd\" effect:\n\n>>> from pyagentspec.property import Property\n>>> from pyagentspec.flows.flow import Flow\n>>> from pyagentspec.flows.edges import ControlFlowEdge, DataFlowEdge\n>>> from pyagentspec.flows.nodes import MapNode, LlmNode, ToolNode, StartNode, EndNode\n>>> from pyagentspec.tools import ServerTool\n>>> duplication_tool = ServerTool(\n... name=\"duplication_tool\",\n... description=\"\",\n... inputs=[\n... Property(\n... json_schema={\"title\": \"element\", \"description\": \"\", \"type\": \"string\"}\n... ),\n... Property(\n... json_schema={\"title\": \"n\", \"description\": \"\", \"type\": \"integer\"}\n... ),\n... ],\n... outputs=[\n... Property(\n... json_schema={\n... \"title\": \"flow_iterable_queries\",\n... \"type\": \"array\",\n... \"items\": {\"type\": \"string\"}\n... },\n... )\n... ],\n... )\n>>> reduce_tool = ServerTool(\n... name=\"reduce_tool\",\n... description=\"\",\n... inputs=[\n... Property(\n... json_schema={\"title\": \"elements\", \"type\": \"array\", \"items\": {\"type\": \"string\"}}\n... ),\n... ],\n... outputs=[Property(json_schema={\"title\": \"flow_processed_query\", \"type\": \"string\"})],\n... )\n>>> # Defining a simple prompt\n>>> REASONING_PROMPT_TEMPLATE = '''Provide your best numerical estimate for: {{user_input}}\n... Your answer should be a single number.\n... Do not include any units, reasoning, or extra text.'''\n>>> # Defining the subflow for the map step\n>>> user_input_property = Property(\n... json_schema={\"title\": \"user_input\", \"type\": \"string\"}\n... )\n>>> flow_processed_query_property = Property(\n... json_schema={\"title\": \"flow_processed_query\", \"type\": \"string\"}\n... )\n>>> start_node = StartNode(name=\"start\", inputs=[user_input_property])\n>>> end_node = EndNode(name=\"end\", outputs=[flow_processed_query_property])\n>>> llm_node = LlmNode(\n... name=\"reasoning llm node\",\n... llm_config=llm_config,\n... prompt_template=REASONING_PROMPT_TEMPLATE,\n... inputs=[user_input_property],\n... outputs=[flow_processed_query_property],\n... )\n>>> inner_map_flow = Flow(\n... name=\"Map flow\",\n... start_node=start_node,\n... nodes=[start_node, llm_node, end_node],\n... control_flow_connections=[\n... ControlFlowEdge(name=\"start_to_llm\", from_node=start_node, to_node=llm_node),\n... ControlFlowEdge(name=\"llm_to_end\", from_node=llm_node, to_node=end_node),\n... ],\n... data_flow_connections=[\n... DataFlowEdge(\n... name=\"query_edge\",\n... source_node=start_node,\n... source_output=\"user_input\",\n... destination_node=llm_node,\n... destination_input=\"user_input\",\n... ),\n... DataFlowEdge(\n... name=\"search_results_edge\",\n... source_node=llm_node,\n... source_output=\"flow_processed_query\",\n... destination_node=end_node,\n... destination_input=\"flow_processed_query\"\n... ),\n... ],\n... )\n>>> user_query_property = Property(\n... json_schema={\"title\": \"user_query\", \"type\": \"string\"}\n... )\n>>> n_repeat_property = Property(\n... json_schema={\"title\": \"n_repeat\", \"type\": \"integer\"}\n... )\n>>> flow_iterable_queries_property = Property(\n... json_schema={\n... \"title\": \"iterated_user_input\",\n... \"type\": \"array\",\n... \"items\": {\"type\": \"string\"},\n... }\n... )\n>>> flow_processed_queries_property = Property(\n... json_schema={\n... \"title\": \"collected_flow_processed_query\",\n... \"type\": \"array\",\n... \"items\": {\"type\": \"string\"},\n... }\n... )\n>>> start_node = StartNode(name=\"start\", inputs=[user_query_property, n_repeat_property])\n>>> end_node = EndNode(name=\"end\", outputs=[flow_processed_query_property])\n>>> duplication_node = ToolNode(\n... name=\"duplication_tool node\",\n... tool=duplication_tool,\n... )\n>>> reduce_node = ToolNode(\n... name=\"reduce_tool node\",\n... tool=reduce_tool,\n... )\n>>> map_node = MapNode(\n... name=\"map node\",\n... subflow=inner_map_flow,\n... inputs=[flow_iterable_queries_property],\n... outputs=[flow_processed_queries_property],\n... )\n>>> mapreduce_flow = Flow(\n... name=\"Map-reduce flow\",\n... start_node=start_node,\n... nodes=[start_node, duplication_node, map_node, reduce_node, end_node],\n... control_flow_connections=[\n... ControlFlowEdge(\n... name=\"start_to_duplication\", from_node=start_node, to_node=duplication_node\n... ),\n... ControlFlowEdge(\n... name=\"duplication_to_map\", from_node=duplication_node, to_node=map_node\n... ),\n... ControlFlowEdge(name=\"map_to_reduce\", from_node=map_node, to_node=reduce_node),\n... ControlFlowEdge(name=\"reduce_to_end\", from_node=reduce_node, to_node=end_node),\n... ],\n... data_flow_connections=[\n... DataFlowEdge(\n... name=\"query_edge\",\n... source_node=start_node,\n... source_output=\"user_query\",\n... destination_node=duplication_node,\n... destination_input=\"element\",\n... ),\n... DataFlowEdge(\n... name=\"n_repeat_edge\",\n... source_node=start_node,\n... source_output=\"n_repeat\",\n... destination_node=duplication_node,\n... destination_input=\"n\",\n... ),\n... DataFlowEdge(\n... name=\"flow_iterables_edge\",\n... source_node=duplication_node,\n... source_output=\"flow_iterable_queries\",\n... destination_node=map_node,\n... destination_input=\"iterated_user_input\",\n... ),\n... DataFlowEdge(\n... name=\"flow_processed_queries_edge\",\n... source_node=map_node,\n... source_output=\"collected_flow_processed_query\",\n... destination_node=reduce_node,\n... destination_input=\"elements\",\n... ),\n... DataFlowEdge(\n... name=\"flow_processed_query_edge\",\n... source_node=reduce_node,\n... source_output=\"flow_processed_query\",\n... destination_node=end_node,\n... destination_input=\"flow_processed_query\",\n... ),\n... ],\n... )\n\nOnce the subflow is created we can simply integrate it with the ``FlowNode``:\n\n>>> from pyagentspec.flows.nodes import FlowNode, AgentNode\n>>> from pyagentspec.agent import Agent\n>>> start_node = StartNode(name=\"start\")\n>>> end_node = EndNode(name=\"end\", outputs=[flow_processed_query_property])\n>>> flow_node = FlowNode(name=\"flow node\", subflow=mapreduce_flow)\n>>> agent = Agent(\n... name=\"User interaction agent\",\n... llm_config=llm_config,\n... system_prompt=(\n... \"Your task is to gather from the user the query and the number of times \"\n... \"it should be asked to an LLM. Once you have this information, submit and exit.\"\n... ),\n... outputs=[user_query_property, n_repeat_property],\n... )\n>>> agent_node = AgentNode(name=\"flow node\", agent=agent)\n>>> flow = Flow(\n... name=\"Map flow\",\n... start_node=start_node,\n... nodes=[start_node, agent_node, flow_node, end_node],\n... control_flow_connections=[\n... ControlFlowEdge(name=\"start_to_agent\", from_node=start_node, to_node=agent_node),\n... ControlFlowEdge(name=\"agent_to_flow\", from_node=agent_node, to_node=flow_node),\n... ControlFlowEdge(name=\"flow_to_end\", from_node=flow_node, to_node=end_node),\n... ],\n... data_flow_connections=[\n... DataFlowEdge(\n... name=\"query_edge\",\n... source_node=agent_node,\n... source_output=\"user_query\",\n... destination_node=flow_node,\n... destination_input=\"user_query\",\n... ),\n... DataFlowEdge(\n... name=\"n_rep_edge\",\n... source_node=agent_node,\n... source_output=\"n_repeat\",\n... destination_node=flow_node,\n... destination_input=\"n_repeat\"\n... ),\n... DataFlowEdge(\n... name=\"n_rep_edge\",\n... source_node=flow_node,\n... source_output=\"flow_processed_query\",\n... destination_node=end_node,\n... destination_input=\"flow_processed_query\"\n... ),\n... ],\n... )", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "inputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Inputs" + }, + "outputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Outputs" + }, + "branches": { + "items": { + "type": "string" + }, + "title": "Branches", + "type": "array" + }, + "subflow": { + "$ref": "#/$defs/Flow" + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "FlowNode" + } + }, + "required": [ + "name", + "subflow" + ], + "title": "FlowNode", + "type": "object", + "x-abstract-component": false + }, + "BaseGeminiAIStudioAuthConfig": { + "additionalProperties": false, + "description": "Authentication settings for Gemini via Google AI Studio.", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "api_key": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Api Key" + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "GeminiAIStudioAuthConfig" + } + }, + "required": [ + "name" + ], + "title": "GeminiAIStudioAuthConfig", + "type": "object", + "x-abstract-component": false + }, + "BaseGeminiAuthConfig": { + "anyOf": [ + { + "$ref": "#/$defs/GeminiAIStudioAuthConfig" + }, + { + "$ref": "#/$defs/GeminiVertexAIAuthConfig" + } + ], + "x-abstract-component": true + }, + "BaseGeminiConfig": { + "additionalProperties": false, + "description": "Configure a connection to a Gemini LLM (AI Studio or Vertex AI).", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "model_id": { + "title": "Model Id", + "type": "string" + }, + "provider": { + "const": "google", + "default": "google", + "title": "Provider", + "type": "string" + }, + "api_provider": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Api Provider" + }, + "api_type": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Api Type" + }, + "url": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Url" + }, + "api_key": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Api Key" + }, + "default_generation_parameters": { + "anyOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/LlmGenerationConfig" + } + ], + "default": null + }, + "retry_policy": { + "anyOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/RetryPolicy" + } + ], + "default": null + }, + "auth": { + "$ref": "#/$defs/GeminiAuthConfig" + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "GeminiConfig" + } + }, + "required": [ + "auth", + "model_id", + "name" + ], + "title": "GeminiConfig", + "type": "object", + "x-abstract-component": false + }, + "BaseGeminiVertexAIAuthConfig": { + "additionalProperties": false, + "description": "Authentication settings for Gemini via Google Vertex AI.", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "project_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Project Id" + }, + "location": { + "default": "global", + "title": "Location", + "type": "string" + }, + "credentials": { + "anyOf": [ + { + "type": "string" + }, + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Credentials" + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "GeminiVertexAIAuthConfig" + } + }, + "required": [ + "name" + ], + "title": "GeminiVertexAIAuthConfig", + "type": "object", + "x-abstract-component": false + }, + "BaseInMemoryCollectionDatastore": { + "additionalProperties": false, + "description": "In-memory datastore for testing and development purposes.", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "datastore_schema": { + "additionalProperties": { + "$ref": "#/$defs/Property" + }, + "title": "Datastore Schema", + "type": "object" + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "InMemoryCollectionDatastore" + } + }, + "required": [ + "datastore_schema", + "name" + ], + "title": "InMemoryCollectionDatastore", + "type": "object", + "x-abstract-component": false + }, + "BaseInputMessageNode": { + "additionalProperties": false, + "description": "This node interrupts the execution of the flow in order to wait for a user input, and restarts after receiving it.\nAn agent message, if given, is appended to the conversation before waiting for input.\nUser input is appended to the conversation as a user message, and it is returned as a string property from the node.\n\n- **Inputs**\n One per variable in the message\n- **Outputs**\n One string property that represents the content of the input user message.\n\n If None is given, ``pyagentspec`` infers a string property named ``user_input``.\n- **Branches**\n One, the default next.\n\nExamples\n--------\n>>> from pyagentspec.flows.edges import ControlFlowEdge, DataFlowEdge\n>>> from pyagentspec.flows.flow import Flow\n>>> from pyagentspec.flows.nodes import StartNode, EndNode, InputMessageNode, OutputMessageNode, LlmNode\n>>> from pyagentspec.property import StringProperty\n>>> start_node = StartNode(name=\"start\")\n>>> prompt_node = OutputMessageNode(name=\"ask_input\", message=\"What is the paragraph you want to rephrase?\")\n>>> input_node = InputMessageNode(name=\"user_input\", outputs=[StringProperty(title=\"user_input\")])\n>>> llm_node = LlmNode(\n... name=\"rephrase\",\n... llm_config=llm_config,\n... prompt_template=\"Rephrase {{user_input}}\",\n... outputs=[StringProperty(title=\"rephrased_user_input\")],\n... )\n>>> output_node = OutputMessageNode(name=\"ask_input\", message=\"{{rephrased_user_input}}\")\n>>> end_node = EndNode(name=\"end\")\n>>> flow = Flow(\n... name=\"rephrase_paragraph_flow\",\n... start_node=start_node,\n... nodes=[start_node, prompt_node, input_node, llm_node, output_node, end_node],\n... control_flow_connections=[\n... ControlFlowEdge(name=\"ce1\", from_node=start_node, to_node=prompt_node),\n... ControlFlowEdge(name=\"ce2\", from_node=prompt_node, to_node=input_node),\n... ControlFlowEdge(name=\"ce3\", from_node=input_node, to_node=llm_node),\n... ControlFlowEdge(name=\"ce4\", from_node=llm_node, to_node=output_node),\n... ControlFlowEdge(name=\"ce5\", from_node=output_node, to_node=end_node),\n... ],\n... data_flow_connections=[\n... DataFlowEdge(\n... name=\"de1\",\n... source_node=input_node,\n... source_output=\"user_input\",\n... destination_node=llm_node,\n... destination_input=\"user_input\",\n... ),\n... DataFlowEdge(\n... name=\"de2\",\n... source_node=llm_node,\n... source_output=\"rephrased_user_input\",\n... destination_node=output_node,\n... destination_input=\"rephrased_user_input\",\n... ),\n... ]\n... )", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "inputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Inputs" + }, + "outputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Outputs" + }, + "branches": { + "items": { + "type": "string" + }, + "title": "Branches", + "type": "array" + }, + "message": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Message" + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "InputMessageNode" + } + }, + "required": [ + "name" + ], + "title": "InputMessageNode", + "type": "object", + "x-abstract-component": false + }, + "BaseLlmConfig": { + "anyOf": [ + { + "$ref": "#/$defs/DbmsVectorChainLlmConfig" + }, + { + "$ref": "#/$defs/GeminiConfig" + }, + { + "$ref": "#/$defs/OciGenAiConfig" + }, + { + "$ref": "#/$defs/OllamaConfig" + }, + { + "$ref": "#/$defs/OpenAiCompatibleConfig" + }, + { + "$ref": "#/$defs/OpenAiConfig" + }, + { + "$ref": "#/$defs/VllmConfig" + }, + { + "additionalProperties": false, + "description": "A LLM configuration defines how to connect to a LLM to do generation requests.\n\nThis class can be used directly with the ``provider``, ``api_provider``, and ``api_type``\nfields to describe any LLM without a dedicated subclass. Concrete subclasses provide\nadditional configuration for specific LLM providers.", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "model_id": { + "title": "Model Id", + "type": "string" + }, + "provider": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Provider" + }, + "api_provider": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Api Provider" + }, + "api_type": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Api Type" + }, + "url": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Url" + }, + "api_key": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Api Key" + }, + "default_generation_parameters": { + "anyOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/LlmGenerationConfig" + } + ], + "default": null + }, + "retry_policy": { + "anyOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/RetryPolicy" + } + ], + "default": null + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "LlmConfig" + } + }, + "required": [ + "model_id", + "name" + ], + "title": "LlmConfig", + "type": "object" + } + ], + "x-abstract-component": false + }, + "BaseLlmNode": { + "additionalProperties": false, + "description": "Execute a prompt template with a given LLM.\n\nThis node is intended to be a part of a Flow.\n\n- **Inputs**\n One per placeholder in the prompt template.\n- **Outputs**\n The output text generated by the LLM.\n\n If None is given, ``pyagentspec`` infers a string property named ``generated_text``.\n- **Branches**\n One, the default next.\n\nExample\n-------\n>>> from pyagentspec.property import Property\n>>> from pyagentspec.flows.flow import Flow\n>>> from pyagentspec.flows.edges import ControlFlowEdge, DataFlowEdge\n>>> from pyagentspec.flows.nodes import LlmNode, StartNode, EndNode\n>>> country_property = Property(\n... json_schema={\"title\": \"country\", \"type\": \"string\"}\n... )\n>>> capital_property = Property(\n... json_schema={\"title\": \"capital\", \"type\": \"string\"}\n... )\n>>> start_node = StartNode(name=\"start\", inputs=[country_property])\n>>> end_node = EndNode(name=\"end\", outputs=[capital_property])\n>>> llm_node = LlmNode(\n... name=\"simple llm node\",\n... llm_config=llm_config,\n... prompt_template=\"What is the capital of {{ country }}?\",\n... inputs=[country_property],\n... outputs=[capital_property],\n... )\n>>> flow = Flow(\n... name=\"Get the country's capital flow\",\n... start_node=start_node,\n... nodes=[start_node, llm_node, end_node],\n... control_flow_connections=[\n... ControlFlowEdge(name=\"start_to_llm\", from_node=start_node, to_node=llm_node),\n... ControlFlowEdge(name=\"llm_to_end\", from_node=llm_node, to_node=end_node),\n... ],\n... data_flow_connections=[\n... DataFlowEdge(\n... name=\"country_edge\",\n... source_node=start_node,\n... source_output=\"country\",\n... destination_node=llm_node,\n... destination_input=\"country\",\n... ),\n... DataFlowEdge(\n... name=\"capital_edge\",\n... source_node=llm_node,\n... source_output=\"capital\",\n... destination_node=end_node,\n... destination_input=\"capital\"\n... ),\n... ],\n... )", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "inputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Inputs" + }, + "outputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Outputs" + }, + "branches": { + "items": { + "type": "string" + }, + "title": "Branches", + "type": "array" + }, + "llm_config": { + "$ref": "#/$defs/LlmConfig" + }, + "prompt_template": { + "title": "Prompt Template", + "type": "string" + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "LlmNode" + } + }, + "required": [ + "llm_config", + "name", + "prompt_template" + ], + "title": "LlmNode", + "type": "object", + "x-abstract-component": false + }, + "BaseMCPTool": { + "additionalProperties": false, + "description": "Class for tools exposed by MCP servers", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "inputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Inputs" + }, + "outputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Outputs" + }, + "requires_confirmation": { + "default": false, + "title": "Requires Confirmation", + "type": "boolean" + }, + "client_transport": { + "$ref": "#/$defs/ClientTransport" + }, + "retry_policy": { + "anyOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/RetryPolicy" + } + ], + "default": null + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "MCPTool" + } + }, + "required": [ + "client_transport", + "name" + ], + "title": "MCPTool", + "type": "object", + "x-abstract-component": false + }, + "BaseMCPToolBox": { + "additionalProperties": false, + "description": "Class to dynamically expose a list of tools from a MCP Server.", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "requires_confirmation": { + "default": false, + "title": "Requires Confirmation", + "type": "boolean" + }, + "client_transport": { + "$ref": "#/$defs/ClientTransport" + }, + "retry_policy": { + "anyOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/RetryPolicy" + } + ], + "default": null + }, + "tool_filter": { + "anyOf": [ + { + "items": { + "anyOf": [ + { + "type": "string" + }, + { + "$ref": "#/$defs/MCPToolSpec" + } + ] + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Tool Filter" + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "MCPToolBox" + } + }, + "required": [ + "client_transport", + "name" + ], + "title": "MCPToolBox", + "type": "object", + "x-abstract-component": false + }, + "BaseMCPToolSpec": { + "additionalProperties": false, + "description": "Specification of MCP tool", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "inputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Inputs" + }, + "outputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Outputs" + }, + "requires_confirmation": { + "default": false, + "title": "Requires Confirmation", + "type": "boolean" + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "MCPToolSpec" + } + }, + "required": [ + "name" + ], + "title": "MCPToolSpec", + "type": "object", + "x-abstract-component": false + }, + "BaseMTlsOracleDatabaseConnectionConfig": { + "additionalProperties": false, + "description": "Mutual-TLS Connection Configuration to Oracle Database.", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "user": { + "title": "User", + "type": "string" + }, + "password": { + "title": "Password", + "type": "string" + }, + "dsn": { + "title": "Dsn", + "type": "string" + }, + "config_dir": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Config Dir" + }, + "protocol": { + "default": "tcps", + "enum": [ + "tcp", + "tcps" + ], + "title": "Protocol", + "type": "string" + }, + "wallet_location": { + "title": "Wallet Location", + "type": "string" + }, + "wallet_password": { + "title": "Wallet Password", + "type": "string" + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "MTlsOracleDatabaseConnectionConfig" + } + }, + "required": [ + "dsn", + "name", + "password", + "user", + "wallet_location", + "wallet_password" + ], + "title": "MTlsOracleDatabaseConnectionConfig", + "type": "object", + "x-abstract-component": false + }, + "BaseManagerWorkers": { + "additionalProperties": false, + "description": "Defines a ``ManagerWorkers`` conversational component.\n\nA ``ManagerWorkers`` is a multi-agent conversational component in which a group manager\nassigns tasks to the workers. The group manager and workers can be instantiated from\nany ``AgenticComponent`` type.\n\nExamples\n--------\n>>> from pyagentspec.agent import Agent\n>>> from pyagentspec.managerworkers import ManagerWorkers\n>>> manager_agent = Agent(\n... name=\"manager_agent\",\n... description=\"Agent that manages a group of math agents\",\n... llm_config=llm_config,\n... system_prompt=\"You are the manager of a group of math agents\"\n... )\n>>> multiplication_agent = Agent(\n... name=\"multiplication_agent\",\n... description=\"Agent that can do multiplication\",\n... llm_config=llm_config,\n... system_prompt=\"You can do multiplication.\"\n... )\n>>> division_agent = Agent(\n... name=\"division_agent\",\n... description=\"Agent that can do division\",\n... llm_config=llm_config,\n... system_prompt=\"You can do division.\"\n... )\n>>> group = ManagerWorkers(\n... name=\"managerworkers\",\n... group_manager=manager_agent,\n... workers=[multiplication_agent, division_agent],\n... )", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "inputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Inputs" + }, + "outputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Outputs" + }, + "group_manager": { + "$ref": "#/$defs/AgenticComponent" + }, + "workers": { + "items": { + "$ref": "#/$defs/AgenticComponent" + }, + "title": "Workers", + "type": "array" + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "ManagerWorkers" + } + }, + "required": [ + "group_manager", + "name", + "workers" + ], + "title": "ManagerWorkers", + "type": "object", + "x-abstract-component": false + }, + "BaseMapNode": { + "additionalProperties": false, + "description": "The map node executes a subflow on each element of a given input as part of a flow.\n\n- **Inputs**\n Inferred from the inner structure. It's the sets of inputs\n required by the StartNode of the inner flow.\n The names of the inputs will be the ones of the inner flow,\n complemented with the ``iterated_`` prefix. Their type is\n ``Union[inner_type, List[inner_type]]``, where ``inner_type``\n is the type of the respective input in the inner flow.\n\n If None is given, ``pyagentspec`` infers input properties directly from the inner flow,\n specifying title and type according to the rules defined above.\n\n- **Outputs**\n Inferred from the inner structure. It's the union of the\n sets of outputs exposed by the EndNodes of the inner flow,\n combined with the reducer method of each output.\n The names of the outputs will be the ones of the inner flow,\n complemented with the ``collected_`` prefix. Their type depends\n on the ``reduce`` method specified for that output:\n\n - ``List`` of the respective output type in case of ``append``\n - same type of the respective output type in case of ``sum``, ``avg``\n\n If None is given, ``pyagentspec`` infers outputs by exposing\n an output property for each entry in the ``reducers`` dictionary, specifying title\n and type according to the rules defined above.\n\n- **Branches**\n One, the default next.\n\nExamples\n--------\nIn this example we will create a flow that returns\nan L2-normalized version of a given list of numbers.\n\n>>> from pyagentspec.property import Property\n>>> from pyagentspec.flows.edges import ControlFlowEdge, DataFlowEdge\n>>> from pyagentspec.flows.flow import Flow\n>>> from pyagentspec.flows.nodes import EndNode, StartNode, MapNode, ToolNode\n>>> from pyagentspec.tools import ServerTool\n\nFirst, we define a MapNode that returns the square of all the elements in a list.\nIt will be used to compute the L2-norm.\n\n>>> x_property = Property(json_schema={\"title\": \"x\", \"type\": \"number\"})\n>>> x_square_property = Property(\n... json_schema={\"title\": \"x_square\", \"type\": \"number\"}\n... )\n>>> square_tool = ServerTool(\n... name=\"compute_square_tool\",\n... description=\"Computes the square of a number\",\n... inputs=[x_property],\n... outputs=[x_square_property],\n... )\n>>> list_of_x_property = Property(\n... json_schema={\"title\": \"x_list\", \"type\": \"array\", \"items\": {\"type\": \"number\"}}\n... )\n>>> start_node = StartNode(name=\"start\", inputs=[x_property])\n>>> end_node = EndNode(name=\"end\", outputs=[x_square_property])\n>>> square_tool_node = ToolNode(name=\"square tool node\", tool=square_tool)\n>>> square_number_flow = Flow(\n... name=\"Square number flow\",\n... start_node=start_node,\n... nodes=[start_node, square_tool_node, end_node],\n... control_flow_connections=[\n... ControlFlowEdge(\n... name=\"start_to_tool\", from_node=start_node, to_node=square_tool_node\n... ),\n... ControlFlowEdge(\n... name=\"tool_to_end\", from_node=square_tool_node, to_node=end_node\n... ),\n... ],\n... data_flow_connections=[\n... DataFlowEdge(\n... name=\"x_edge\",\n... source_node=start_node,\n... source_output=\"x\",\n... destination_node=square_tool_node,\n... destination_input=\"x\",\n... ),\n... DataFlowEdge(\n... name=\"x_square_edge\",\n... source_node=square_tool_node,\n... source_output=\"x_square\",\n... destination_node=end_node,\n... destination_input=\"x_square\",\n... ),\n... ],\n... )\n>>> list_of_x_square_property = Property(\n... json_schema={\"title\": \"x_square_list\", \"type\": \"array\", \"items\": {\"type\": \"number\"}}\n... )\n>>> square_numbers_map_node = MapNode(\n... name=\"square number map node\",\n... subflow=square_number_flow,\n... )\n\nNow we define the MapNode responsible for normalizing the given list of input numbers.\nThe denominator is the same for all of the numbers,\nwe are going to map only the numerators (i.e., the input numbers).\n\n>>> numerator_property = Property(\n... json_schema={\"title\": \"numerator\", \"type\": \"number\"}\n... )\n>>> denominator_property = Property(\n... json_schema={\"title\": \"denominator\", \"type\": \"number\"}\n... )\n>>> result_property = Property(\n... json_schema={\"title\": \"result\", \"type\": \"number\"}\n... )\n>>> division_tool = ServerTool(\n... name=\"division_tool\",\n... description=\"Computes the ratio between two numbers\",\n... inputs=[numerator_property, denominator_property],\n... outputs=[result_property],\n... )\n>>> start_node = StartNode(name=\"start\", inputs=[numerator_property, denominator_property])\n>>> end_node = EndNode(name=\"end\", outputs=[result_property])\n>>> divide_node = ToolNode(name=\"divide node\", tool=division_tool)\n>>> normalize_flow = Flow(\n... name=\"Normalize flow\",\n... start_node=start_node,\n... nodes=[start_node, divide_node, end_node],\n... control_flow_connections=[\n... ControlFlowEdge(name=\"start_to_tool\", from_node=start_node, to_node=divide_node),\n... ControlFlowEdge(name=\"tool_to_end\", from_node=divide_node, to_node=end_node),\n... ],\n... data_flow_connections=[\n... DataFlowEdge(\n... name=\"numerator_edge\",\n... source_node=start_node,\n... source_output=\"numerator\",\n... destination_node=divide_node,\n... destination_input=\"numerator\",\n... ),\n... DataFlowEdge(\n... name=\"denominator_edge\",\n... source_node=start_node,\n... source_output=\"denominator\",\n... destination_node=divide_node,\n... destination_input=\"denominator\",\n... ),\n... DataFlowEdge(\n... name=\"result_edge\",\n... source_node=divide_node,\n... source_output=\"result\",\n... destination_node=end_node,\n... destination_input=\"result\",\n... ),\n... ],\n... )\n\nFinally, we define the overall flow:\n\n- The list of inputs is squared\n- The squared list is summed and root squared\n- The list of inputs is normalized based on the outcomes of the previous 2 steps\n\n>>> squared_sum_property = Property(\n... json_schema={\"title\": \"squared_sum\", \"type\": \"number\"}\n... )\n>>> normalized_list_of_x_property = Property(\n... json_schema={\n... \"title\": \"x_list_normalized\",\n... \"type\": \"array\",\n... \"items\": {\"type\": \"number\"},\n... }\n... )\n>>> normalize_map_node = MapNode(\n... name=\"normalize map node\",\n... subflow=normalize_flow,\n... )\n>>> squared_sum_tool = ServerTool(\n... name=\"squared_sum_tool\",\n... description=\"Computes the squared sum of a list of numbers\",\n... inputs=[list_of_x_property],\n... outputs=[squared_sum_property],\n... )\n>>> start_node = StartNode(name=\"start\", inputs=[list_of_x_property])\n>>> end_node = EndNode(name=\"end\", outputs=[normalized_list_of_x_property])\n>>> squared_sum_tool_node = ToolNode(name=\"squared sum tool node\", tool=squared_sum_tool)\n>>> flow = Flow(\n... name=\"L2 normalize flow\",\n... start_node=start_node,\n... nodes=[\n... start_node,\n... square_numbers_map_node,\n... squared_sum_tool_node,\n... normalize_map_node,\n... end_node,\n... ],\n... control_flow_connections=[\n... ControlFlowEdge(\n... name=\"start_to_square_numbers\",\n... from_node=start_node,\n... to_node=square_numbers_map_node\n... ),\n... ControlFlowEdge(\n... name=\"square_numbers_to_squared_sum_tool\",\n... from_node=square_numbers_map_node,\n... to_node=squared_sum_tool_node\n... ),\n... ControlFlowEdge(\n... name=\"squared_sum_tool_to_normalize\",\n... from_node=squared_sum_tool_node,\n... to_node=normalize_map_node\n... ),\n... ControlFlowEdge(\n... name=\"normalize_to_end\",\n... from_node=normalize_map_node,\n... to_node=end_node\n... ),\n... ],\n... data_flow_connections=[\n... DataFlowEdge(\n... name=\"list_of_x_edge\",\n... source_node=start_node,\n... source_output=\"x_list\",\n... destination_node=square_numbers_map_node,\n... destination_input=\"iterated_x\",\n... ),\n... DataFlowEdge(\n... name=\"x_square_list_edge\",\n... source_node=square_numbers_map_node,\n... source_output=\"collected_x_square\",\n... destination_node=squared_sum_tool_node,\n... destination_input=\"x_list\",\n... ),\n... DataFlowEdge(\n... name=\"numerator_edge\",\n... source_node=start_node,\n... source_output=\"x_list\",\n... destination_node=normalize_map_node,\n... destination_input=\"iterated_numerator\",\n... ),\n... DataFlowEdge(\n... name=\"denominator_edge\",\n... source_node=squared_sum_tool_node,\n... source_output=\"squared_sum\",\n... destination_node=normalize_map_node,\n... destination_input=\"iterated_denominator\",\n... ),\n... DataFlowEdge(\n... name=\"x_list_normalized_edge\",\n... source_node=normalize_map_node,\n... source_output=\"collected_result\",\n... destination_node=end_node,\n... destination_input=\"x_list_normalized\",\n... ),\n... ],\n... )", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "inputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Inputs" + }, + "outputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Outputs" + }, + "branches": { + "items": { + "type": "string" + }, + "title": "Branches", + "type": "array" + }, + "subflow": { + "$ref": "#/$defs/Flow" + }, + "reducers": { + "anyOf": [ + { + "additionalProperties": { + "$ref": "#/$defs/ReductionMethod" + }, + "type": "object" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Reducers" + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "MapNode" + } + }, + "required": [ + "name", + "subflow" + ], + "title": "MapNode", + "type": "object", + "x-abstract-component": false + }, + "BaseMessageSummarizationTransform": { + "additionalProperties": false, + "description": "Summarizes oversized messages using an LLM and optionally caches summaries.\n\nThis is useful for long conversations where the context can become too large for the LLM to handle.\n\nExamples\n--------\n>>> from pyagentspec.transforms import MessageSummarizationTransform\n>>> summarization_transform = MessageSummarizationTransform(\n... name=\"message-summarizer\",\n... llm=llm_config,\n... max_message_size=30_000\n... )", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "llm": { + "$ref": "#/$defs/LlmConfig" + }, + "max_message_size": { + "default": 20000, + "title": "Max Message Size", + "type": "integer" + }, + "summarization_instructions": { + "default": "Please make a summary of this message. Include relevant information and keep it short. Your response will replace the message, so just output the summary directly, no introduction needed.", + "title": "Summarization Instructions", + "type": "string" + }, + "summarized_message_template": { + "default": "Summarized message: {{summary}}", + "title": "Summarized Message Template", + "type": "string" + }, + "max_cache_size": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "default": 10000, + "title": "Max Cache Size" + }, + "max_cache_lifetime": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "default": 14400, + "title": "Max Cache Lifetime" + }, + "cache_collection_name": { + "default": "summarized_messages_cache", + "title": "Cache Collection Name", + "type": "string" + }, + "datastore": { + "anyOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/InMemoryCollectionDatastore" + }, + { + "$ref": "#/$defs/OracleDatabaseDatastore" + }, + { + "$ref": "#/$defs/PostgresDatabaseDatastore" + } + ], + "title": "Datastore" + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "MessageSummarizationTransform" + } + }, + "required": [ + "llm", + "name" + ], + "title": "MessageSummarizationTransform", + "type": "object", + "x-abstract-component": false + }, + "BaseMessageTransform": { + "anyOf": [ + { + "$ref": "#/$defs/ConversationSummarizationTransform" + }, + { + "$ref": "#/$defs/MessageSummarizationTransform" + } + ], + "x-abstract-component": true + }, + "BaseNode": { + "anyOf": [ + { + "$ref": "#/$defs/AgentNode" + }, + { + "$ref": "#/$defs/ApiNode" + }, + { + "$ref": "#/$defs/BranchingNode" + }, + { + "$ref": "#/$defs/CatchExceptionNode" + }, + { + "$ref": "#/$defs/EndNode" + }, + { + "$ref": "#/$defs/FlowNode" + }, + { + "$ref": "#/$defs/InputMessageNode" + }, + { + "$ref": "#/$defs/LlmNode" + }, + { + "$ref": "#/$defs/MapNode" + }, + { + "$ref": "#/$defs/OutputMessageNode" + }, + { + "$ref": "#/$defs/ParallelFlowNode" + }, + { + "$ref": "#/$defs/ParallelMapNode" + }, + { + "$ref": "#/$defs/StartNode" + }, + { + "$ref": "#/$defs/ToolNode" + } + ], + "x-abstract-component": true + }, + "BaseOAuthClientConfig": { + "additionalProperties": false, + "description": "OAuth client identity / registration configuration.\n\nThis configuration describes how the runtime establishes the OAuth client\nidentity to use with the authorization server. It supports:\n- Pre-registered clients (static client_id/client_secret)\n- Client ID Metadata Documents (URL-formatted client_id)\n- Dynamic client registration (RFC 7591)", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "min_agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum", + "default": "26.1.2" + }, + "type": { + "enum": [ + "client_id_metadata_document", + "dynamic_registration", + "pre_registered" + ], + "title": "Type", + "type": "string" + }, + "client_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Client Id" + }, + "client_secret": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Client Secret" + }, + "token_endpoint_auth_method": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Token Endpoint Auth Method" + }, + "client_id_metadata_url": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Client Id Metadata Url" + }, + "registration_endpoint": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Registration Endpoint" + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "OAuthClientConfig" + } + }, + "required": [ + "name", + "type" + ], + "title": "OAuthClientConfig", + "type": "object", + "x-abstract-component": false + }, + "BaseOAuthConfig": { + "additionalProperties": false, + "description": "Configure OAuth-based authentication for a tool or transport.\n\nOAuthConfig is a generic configuration that can be used for both MCP servers\nand non-MCP remote API tools. It supports discovery-based configuration (via\n``issuer``) and explicit endpoints (via ``endpoints``).", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "min_agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum", + "default": "26.1.2" + }, + "issuer": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Issuer" + }, + "endpoints": { + "anyOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/OAuthEndpoints" + } + ], + "default": null + }, + "client": { + "$ref": "#/$defs/OAuthClientConfig" + }, + "redirect_uri": { + "title": "Redirect Uri", + "type": "string" + }, + "scopes": { + "anyOf": [ + { + "type": "string" + }, + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Scopes" + }, + "scope_policy": { + "anyOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/ScopePolicy" + } + ], + "default": null + }, + "pkce": { + "anyOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/PKCEPolicy" + } + ], + "default": null + }, + "resource": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Resource" + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "OAuthConfig" + } + }, + "required": [ + "client", + "name", + "redirect_uri" + ], + "title": "OAuthConfig", + "type": "object", + "x-abstract-component": false + }, + "BaseOciAgent": { + "additionalProperties": false, + "description": "An agent is a component that can do several rounds of conversation to solve a task.\n\nThe agent is defined on the OCI console and this is only a wrapper to connect to it.\nIt can be executed by itself, or be executed in a flow using an AgentNode.", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "inputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Inputs" + }, + "outputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Outputs" + }, + "agent_endpoint_id": { + "title": "Agent Endpoint Id", + "type": "string" + }, + "client_config": { + "$ref": "#/$defs/OciClientConfig" + }, + "retry_policy": { + "anyOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/RetryPolicy" + } + ], + "default": null + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "OciAgent" + } + }, + "required": [ + "agent_endpoint_id", + "client_config", + "name" + ], + "title": "OciAgent", + "type": "object", + "x-abstract-component": false + }, + "BaseOciClientConfig": { + "anyOf": [ + { + "$ref": "#/$defs/OciClientConfigWithApiKey" + }, + { + "$ref": "#/$defs/OciClientConfigWithInstancePrincipal" + }, + { + "$ref": "#/$defs/OciClientConfigWithResourcePrincipal" + }, + { + "$ref": "#/$defs/OciClientConfigWithSecurityToken" + } + ], + "x-abstract-component": true + }, + "BaseOciClientConfigWithApiKey": { + "additionalProperties": false, + "description": "OCI client config class for authentication using API_KEY and a config file.", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "service_endpoint": { + "title": "Service Endpoint", + "type": "string" + }, + "auth_type": { + "const": "API_KEY", + "default": "API_KEY", + "title": "Auth Type", + "type": "string" + }, + "auth_profile": { + "title": "Auth Profile", + "type": "string" + }, + "auth_file_location": { + "title": "Auth File Location", + "type": "string" + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "OciClientConfigWithApiKey" + } + }, + "required": [ + "auth_file_location", + "auth_profile", + "name", + "service_endpoint" + ], + "title": "OciClientConfigWithApiKey", + "type": "object", + "x-abstract-component": false + }, + "BaseOciClientConfigWithInstancePrincipal": { + "additionalProperties": false, + "description": "OCI client config class for authentication using INSTANCE_PRINCIPAL.", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "service_endpoint": { + "title": "Service Endpoint", + "type": "string" + }, + "auth_type": { + "const": "INSTANCE_PRINCIPAL", + "default": "INSTANCE_PRINCIPAL", + "title": "Auth Type", + "type": "string" + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "OciClientConfigWithInstancePrincipal" + } + }, + "required": [ + "name", + "service_endpoint" + ], + "title": "OciClientConfigWithInstancePrincipal", + "type": "object", + "x-abstract-component": false + }, + "BaseOciClientConfigWithResourcePrincipal": { + "additionalProperties": false, + "description": "OCI client config class for authentication using RESOURCE_PRINCIPAL.", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "service_endpoint": { + "title": "Service Endpoint", + "type": "string" + }, + "auth_type": { + "const": "RESOURCE_PRINCIPAL", + "default": "RESOURCE_PRINCIPAL", + "title": "Auth Type", + "type": "string" + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "OciClientConfigWithResourcePrincipal" + } + }, + "required": [ + "name", + "service_endpoint" + ], + "title": "OciClientConfigWithResourcePrincipal", + "type": "object", + "x-abstract-component": false + }, + "BaseOciClientConfigWithSecurityToken": { + "additionalProperties": false, + "description": "OCI client config class for authentication using SECURITY_TOKEN.", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "service_endpoint": { + "title": "Service Endpoint", + "type": "string" + }, + "auth_type": { + "const": "SECURITY_TOKEN", + "default": "SECURITY_TOKEN", + "title": "Auth Type", + "type": "string" + }, + "auth_profile": { + "title": "Auth Profile", + "type": "string" + }, + "auth_file_location": { + "title": "Auth File Location", + "type": "string" + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "OciClientConfigWithSecurityToken" + } + }, + "required": [ + "auth_file_location", + "auth_profile", + "name", + "service_endpoint" + ], + "title": "OciClientConfigWithSecurityToken", + "type": "object", + "x-abstract-component": false + }, + "BaseOciGenAiConfig": { + "additionalProperties": false, + "description": "Class to configure a connection to a OCI GenAI hosted model.\n\nRequires to specify the model id and the client configuration to the OCI GenAI service.", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "model_id": { + "title": "Model Id", + "type": "string" + }, + "provider": { + "anyOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/ModelProvider" + } + ], + "default": null + }, + "api_provider": { + "const": "oci", + "default": "oci", + "title": "Api Provider", + "type": "string" + }, + "api_type": { + "$ref": "#/$defs/OciAPIType", + "default": "oci" + }, + "url": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Url" + }, + "api_key": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Api Key" + }, + "default_generation_parameters": { + "anyOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/LlmGenerationConfig" + } + ], + "default": null + }, + "retry_policy": { + "anyOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/RetryPolicy" + } + ], + "default": null + }, + "compartment_id": { + "title": "Compartment Id", + "type": "string" + }, + "serving_mode": { + "$ref": "#/$defs/ServingMode", + "default": "ON_DEMAND" + }, + "client_config": { + "$ref": "#/$defs/OciClientConfig" + }, + "conversation_store_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Conversation Store Id" + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "OciGenAiConfig" + } + }, + "required": [ + "client_config", + "compartment_id", + "model_id", + "name" + ], + "title": "OciGenAiConfig", + "type": "object", + "x-abstract-component": false + }, + "BaseOllamaConfig": { + "additionalProperties": false, + "description": "Class to configure a connection to a local model ran with Ollama.\n\nRequires to specify the url and port at which the model is running.", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "model_id": { + "title": "Model Id", + "type": "string" + }, + "provider": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Provider" + }, + "api_provider": { + "const": "ollama", + "default": "ollama", + "title": "Api Provider", + "type": "string" + }, + "api_type": { + "$ref": "#/$defs/OpenAIAPIType", + "default": "chat_completions" + }, + "url": { + "title": "Url", + "type": "string" + }, + "api_key": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Api Key" + }, + "default_generation_parameters": { + "anyOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/LlmGenerationConfig" + } + ], + "default": null + }, + "retry_policy": { + "anyOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/RetryPolicy" + } + ], + "default": null + }, + "key_file": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Key File" + }, + "cert_file": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Cert File" + }, + "ca_file": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Ca File" + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "OllamaConfig" + } + }, + "required": [ + "model_id", + "name", + "url" + ], + "title": "OllamaConfig", + "type": "object", + "x-abstract-component": false + }, + "BaseOpenAiCompatibleConfig": { + "anyOf": [ + { + "$ref": "#/$defs/OllamaConfig" + }, + { + "$ref": "#/$defs/VllmConfig" + }, + { + "additionalProperties": false, + "description": "Class to configure a connection to an LLM that is compatible with OpenAI completions APIs.\n\nRequires to specify the url of the APIs to contact.", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "model_id": { + "title": "Model Id", + "type": "string" + }, + "provider": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Provider" + }, + "api_provider": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Api Provider" + }, + "api_type": { + "$ref": "#/$defs/OpenAIAPIType", + "default": "chat_completions" + }, + "url": { + "title": "Url", + "type": "string" + }, + "api_key": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Api Key" + }, + "default_generation_parameters": { + "anyOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/LlmGenerationConfig" + } + ], + "default": null + }, + "retry_policy": { + "anyOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/RetryPolicy" + } + ], + "default": null + }, + "key_file": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Key File" + }, + "cert_file": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Cert File" + }, + "ca_file": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Ca File" + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "OpenAiCompatibleConfig" + } + }, + "required": [ + "model_id", + "name", + "url" + ], + "title": "OpenAiCompatibleConfig", + "type": "object" + } + ], + "x-abstract-component": false + }, + "BaseOpenAiConfig": { + "additionalProperties": false, + "description": "Class to configure a connection to a OpenAI LLM.\n\nRequires to specify the identity of the model to use.", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "model_id": { + "title": "Model Id", + "type": "string" + }, + "provider": { + "const": "openai", + "default": "openai", + "title": "Provider", + "type": "string" + }, + "api_provider": { + "const": "openai", + "default": "openai", + "title": "Api Provider", + "type": "string" + }, + "api_type": { + "$ref": "#/$defs/OpenAIAPIType", + "default": "chat_completions" + }, + "url": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Url" + }, + "api_key": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Api Key" + }, + "default_generation_parameters": { + "anyOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/LlmGenerationConfig" + } + ], + "default": null + }, + "retry_policy": { + "anyOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/RetryPolicy" + } + ], + "default": null + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "OpenAiConfig" + } + }, + "required": [ + "model_id", + "name" + ], + "title": "OpenAiConfig", + "type": "object", + "x-abstract-component": false + }, + "BaseOracleDatabaseConnectionConfig": { + "anyOf": [ + { + "$ref": "#/$defs/MTlsOracleDatabaseConnectionConfig" + }, + { + "$ref": "#/$defs/TlsOracleDatabaseConnectionConfig" + } + ], + "x-abstract-component": true + }, + "BaseOracleDatabaseDatastore": { + "additionalProperties": false, + "description": "Datastore that uses Oracle Database as the storage mechanism.", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "datastore_schema": { + "additionalProperties": { + "$ref": "#/$defs/Property" + }, + "title": "Datastore Schema", + "type": "object" + }, + "connection_config": { + "$ref": "#/$defs/OracleDatabaseConnectionConfig" + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "OracleDatabaseDatastore" + } + }, + "required": [ + "connection_config", + "datastore_schema", + "name" + ], + "title": "OracleDatabaseDatastore", + "type": "object", + "x-abstract-component": false + }, + "BaseOutputMessageNode": { + "additionalProperties": false, + "description": "This node appends an agent message to the ongoing flow conversation.\n\n- **Inputs**\n One per variable in the message.\n- **Outputs**\n No outputs.\n- **Branches**\n One, the default next.\n\nExamples\n--------\n>>> from pyagentspec.flows.edges import ControlFlowEdge, DataFlowEdge\n>>> from pyagentspec.flows.flow import Flow\n>>> from pyagentspec.flows.nodes import StartNode, EndNode, InputMessageNode, OutputMessageNode, LlmNode\n>>> from pyagentspec.property import StringProperty\n>>> start_node = StartNode(name=\"start\")\n>>> prompt_node = OutputMessageNode(name=\"ask_input\", message=\"What is the paragraph you want to rephrase?\")\n>>> input_node = InputMessageNode(name=\"user_input\", outputs=[StringProperty(title=\"user_input\")])\n>>> llm_node = LlmNode(\n... name=\"rephrase\",\n... llm_config=llm_config,\n... prompt_template=\"Rephrase {{user_input}}\",\n... outputs=[StringProperty(title=\"rephrased_user_input\")],\n... )\n>>> output_node = OutputMessageNode(name=\"ask_input\", message=\"{{rephrased_user_input}}\")\n>>> end_node = EndNode(name=\"end\")\n>>> flow = Flow(\n... name=\"rephrase_paragraph_flow\",\n... start_node=start_node,\n... nodes=[start_node, prompt_node, input_node, llm_node, output_node, end_node],\n... control_flow_connections=[\n... ControlFlowEdge(name=\"ce1\", from_node=start_node, to_node=prompt_node),\n... ControlFlowEdge(name=\"ce2\", from_node=prompt_node, to_node=input_node),\n... ControlFlowEdge(name=\"ce3\", from_node=input_node, to_node=llm_node),\n... ControlFlowEdge(name=\"ce4\", from_node=llm_node, to_node=output_node),\n... ControlFlowEdge(name=\"ce5\", from_node=output_node, to_node=end_node),\n... ],\n... data_flow_connections=[\n... DataFlowEdge(\n... name=\"de1\",\n... source_node=input_node,\n... source_output=\"user_input\",\n... destination_node=llm_node,\n... destination_input=\"user_input\",\n... ),\n... DataFlowEdge(\n... name=\"de2\",\n... source_node=llm_node,\n... source_output=\"rephrased_user_input\",\n... destination_node=output_node,\n... destination_input=\"rephrased_user_input\",\n... ),\n... ]\n... )", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "inputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Inputs" + }, + "outputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Outputs" + }, + "branches": { + "items": { + "type": "string" + }, + "title": "Branches", + "type": "array" + }, + "message": { + "title": "Message", + "type": "string" + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "OutputMessageNode" + } + }, + "required": [ + "message", + "name" + ], + "title": "OutputMessageNode", + "type": "object", + "x-abstract-component": false + }, + "BaseParallelFlowNode": { + "additionalProperties": false, + "description": "The parallel flow node executes multiple subflows in parallel.\n\n- **Inputs**\n Inferred from the inner structure. It's the union of the sets of inputs of the inner flows.\n Inputs of different inner flows that have the same name are merged if they have the same type.\n\n- **Outputs**\n Inferred from the inner structure. It's the union of the outputs of the inner flows.\n Outputs of different inner flows that have the same name are not allowed.\n\n- **Branches**\n One, the default next.\n\nExamples\n--------", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "inputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Inputs" + }, + "outputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Outputs" + }, + "branches": { + "items": { + "type": "string" + }, + "title": "Branches", + "type": "array" + }, + "subflows": { + "items": { + "$ref": "#/$defs/Flow" + }, + "title": "Subflows", + "type": "array" + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "ParallelFlowNode" + } + }, + "required": [ + "name" + ], + "title": "ParallelFlowNode", + "type": "object", + "x-abstract-component": false + }, + "BaseParallelMapNode": { + "additionalProperties": false, + "description": "The parallel map node executes a subflow on each element of a given input in a parallel manner.\n\n- **Inputs**\n Inferred from the inner structure. It's the sets of inputs\n required by the StartNode of the inner flow.\n The names of the inputs will be the ones of the inner flow,\n complemented with the ``iterated_`` prefix. Their type is\n ``Union[inner_type, List[inner_type]]``, where ``inner_type``\n is the type of the respective input in the inner flow.\n\n If None is given, ``pyagentspec`` infers input properties directly from the inner flow,\n specifying title and type according to the rules defined above.\n\n- **Outputs**\n Inferred from the inner structure. It's the union of the\n sets of outputs exposed by the EndNodes of the inner flow,\n combined with the reducer method of each output.\n The names of the outputs will be the ones of the inner flow,\n complemented with the ``collected_`` prefix. Their type depends\n on the ``reduce`` method specified for that output:\n\n - ``List`` of the respective output type in case of ``append``\n - same type of the respective output type in case of ``sum``, ``avg``\n\n If None is given, ``pyagentspec`` infers outputs by exposing\n an output property for each entry in the ``reducers`` dictionary, specifying title\n and type according to the rules defined above.\n\n- **Branches**\n One, the default next.\n\nExamples\n--------\nIn this example we create a flow that generates a summary of the given articles that talk about LLMs.\n\n>>> from pyagentspec.property import BooleanProperty, StringProperty, ListProperty\n>>> from pyagentspec.flows.edges import ControlFlowEdge, DataFlowEdge\n>>> from pyagentspec.flows.flow import Flow\n>>> from pyagentspec.flows.nodes import EndNode, StartNode, ParallelMapNode, LlmNode, BranchingNode\n>>> from pyagentspec.flows.node import Node\n\nFirst we define the flow that determines if an article talks about LLMs or not:\n\n- if it does, we return it, so that we will use it for our summary\n- if it does not, we don't return its text\n\n>>> def create_data_flow_edge(source_node: Node, destination_node: Node, property_name: str) -> DataFlowEdge:\n... return DataFlowEdge(\n... name=f\"{source_node.name}_{destination_node.name}_{property_name}_edge\",\n... source_node=source_node,\n... source_output=property_name,\n... destination_node=destination_node,\n... destination_input=property_name,\n... )\n>>>\n>>> article_property = StringProperty(title=\"article\")\n>>> is_llm_article_property = StringProperty(title=\"is_article\")\n>>> llm_node = LlmNode(\n... name=\"check_if_article_talks_about_llms_node\",\n... prompt_template=\"Look at this article: {{article}}. Does it talk about LLMs? Answer `yes` or `no`.\",\n... llm_config=llm_config,\n... inputs=[article_property],\n... outputs=[is_llm_article_property],\n... )\n>>>\n>>> branching_node = BranchingNode(\n... name=\"decide_if_we_should_return_the_article\",\n... mapping={\"yes\": \"yes\"},\n... inputs=[is_llm_article_property],\n... )\n>>>\n>>> start_node = StartNode(name=\"start\", inputs=[article_property])\n>>> end_node_with_output = EndNode(name=\"end_with_output\", outputs=[article_property])\n>>> end_node_without_output = EndNode(name=\"end_without_output\", outputs=[])\n>>>\n>>> check_if_article_is_about_llm_flow = Flow(\n... name=\"is_article_about_llm_flow\",\n... start_node=start_node,\n... nodes=[start_node, llm_node, end_node_with_output, end_node_without_output, branching_node],\n... control_flow_connections=[\n... ControlFlowEdge(name=\"start_to_llm\", from_node=start_node, to_node=llm_node),\n... ControlFlowEdge(name=\"llm_to_branching\", from_node=llm_node, to_node=branching_node),\n... ControlFlowEdge(name=\"branching_to_end_with\", from_node=branching_node, to_node=end_node_with_output),\n... ControlFlowEdge(name=\"branching_to_end_without\", from_node=branching_node, to_node=end_node_without_output),\n... ],\n... data_flow_connections=[\n... create_data_flow_edge(start_node, llm_node, article_property.title),\n... create_data_flow_edge(llm_node, branching_node, is_llm_article_property.title),\n... create_data_flow_edge(start_node, end_node_with_output, article_property.title),\n... ],\n... outputs=[StringProperty(title=article_property.title, default=\"\")],\n... )\n\nWe put this flow into a ``ParallelMapNode``, so that we can perform the check\nin parallel on multiple articles at the same time.\n\n>>> list_of_articles_property = ListProperty(title=\"iterated_article\", item_type=article_property)\n>>> list_of_articles_about_llm_property = ListProperty(title=\"collected_article\", item_type=article_property)\n>>> parallel_article_check_node = ParallelMapNode(\n... name=\"parallel_check_if_articles_talk_about_llms_node\",\n... subflow=check_if_article_is_about_llm_flow,\n... inputs=[list_of_articles_property],\n... outputs=[list_of_articles_about_llm_property],\n... )\n\nFinally, we create the flow that takes the list of articles as input, filters them through the\n``ParallelMapNode`` we have just created, and we generate the summary with the remaining articles.\n\n>>> summary_property = StringProperty(title=\"summary\")\n>>> summary_llm_node = LlmNode(\n... name=\"generate_summary_of_llm_articles_node\",\n... prompt_template=\"Summarize the following articles that talk about LLMs: {{collected_article}}\",\n... llm_config=llm_config,\n... inputs=[list_of_articles_about_llm_property],\n... outputs=[summary_property],\n... )\n>>> start_node = StartNode(name=\"start\", inputs=[list_of_articles_property])\n>>> end_node = EndNode(name=\"end\", outputs=[summary_property])\n>>> generate_summary_of_llm_articles_flow = Flow(\n... name=\"Generate summary of articles about LLMs flow\",\n... start_node=start_node,\n... nodes=[start_node, parallel_article_check_node, summary_llm_node, end_node],\n... control_flow_connections=[\n... ControlFlowEdge(name=\"start_to_parallelmap\", from_node=start_node, to_node=parallel_article_check_node),\n... ControlFlowEdge(name=\"parallelmap_to_llm\", from_node=parallel_article_check_node, to_node=summary_llm_node),\n... ControlFlowEdge(name=\"llm_to_end\", from_node=summary_llm_node, to_node=end_node),\n... ],\n... data_flow_connections=[\n... create_data_flow_edge(start_node, parallel_article_check_node, list_of_articles_property.title),\n... create_data_flow_edge(parallel_article_check_node, summary_llm_node, list_of_articles_about_llm_property.title),\n... create_data_flow_edge(summary_llm_node, end_node, summary_property.title),\n... ],\n... )", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "inputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Inputs" + }, + "outputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Outputs" + }, + "branches": { + "items": { + "type": "string" + }, + "title": "Branches", + "type": "array" + }, + "subflow": { + "$ref": "#/$defs/Flow" + }, + "reducers": { + "anyOf": [ + { + "additionalProperties": { + "$ref": "#/$defs/ReductionMethod" + }, + "type": "object" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Reducers" + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "ParallelMapNode" + } + }, + "required": [ + "name", + "subflow" + ], + "title": "ParallelMapNode", + "type": "object", + "x-abstract-component": false + }, + "BasePostgresDatabaseConnectionConfig": { + "anyOf": [ + { + "$ref": "#/$defs/TlsPostgresDatabaseConnectionConfig" + } + ], + "x-abstract-component": true + }, + "BasePostgresDatabaseDatastore": { + "additionalProperties": false, + "description": "Datastore that uses PostgreSQL Database as the storage mechanism.", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "datastore_schema": { + "additionalProperties": { + "$ref": "#/$defs/Property" + }, + "title": "Datastore Schema", + "type": "object" + }, + "connection_config": { + "$ref": "#/$defs/PostgresDatabaseConnectionConfig" + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "PostgresDatabaseDatastore" + } + }, + "required": [ + "connection_config", + "datastore_schema", + "name" + ], + "title": "PostgresDatabaseDatastore", + "type": "object", + "x-abstract-component": false + }, + "BaseRemoteAgent": { + "anyOf": [ + { + "$ref": "#/$defs/A2AAgent" + }, + { + "$ref": "#/$defs/OciAgent" + } + ], + "x-abstract-component": true + }, + "BaseRemoteTool": { + "additionalProperties": false, + "description": "A tool that is run remotely and called through REST.", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "inputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Inputs" + }, + "outputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Outputs" + }, + "requires_confirmation": { + "default": false, + "title": "Requires Confirmation", + "type": "boolean" + }, + "url": { + "title": "Url", + "type": "string" + }, + "http_method": { + "title": "Http Method", + "type": "string" + }, + "api_spec_uri": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Api Spec Uri" + }, + "data": { + "anyOf": [ + { + "type": "string" + }, + { + "format": "binary", + "type": "string" + }, + {} + ], + "title": "Data" + }, + "query_params": { + "additionalProperties": true, + "title": "Query Params", + "type": "object" + }, + "headers": { + "additionalProperties": true, + "title": "Headers", + "type": "object" + }, + "sensitive_headers": { + "additionalProperties": true, + "title": "Sensitive Headers", + "type": "object" + }, + "url_allow_list": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Url Allow List" + }, + "retry_policy": { + "anyOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/RetryPolicy" + } + ], + "default": null + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "RemoteTool" + } + }, + "required": [ + "http_method", + "name", + "url" + ], + "title": "RemoteTool", + "type": "object", + "x-abstract-component": false + }, + "BaseRemoteTransport": { + "anyOf": [ + { + "$ref": "#/$defs/SSETransport" + }, + { + "$ref": "#/$defs/SSEmTLSTransport" + }, + { + "$ref": "#/$defs/StreamableHTTPTransport" + }, + { + "$ref": "#/$defs/StreamableHTTPmTLSTransport" + } + ], + "x-abstract-component": true + }, + "BaseSSETransport": { + "anyOf": [ + { + "$ref": "#/$defs/SSEmTLSTransport" + }, + { + "additionalProperties": false, + "description": "Transport implementation that connects to an MCP server via Server-Sent Events.", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "session_parameters": { + "$ref": "#/$defs/SessionParameters" + }, + "url": { + "title": "Url", + "type": "string" + }, + "auth": { + "anyOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/AuthConfig" + } + ], + "default": null + }, + "headers": { + "anyOf": [ + { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Headers" + }, + "sensitive_headers": { + "anyOf": [ + { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Sensitive Headers" + }, + "retry_policy": { + "anyOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/RetryPolicy" + } + ], + "default": null + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "SSETransport" + } + }, + "required": [ + "name", + "url" + ], + "title": "SSETransport", + "type": "object" + } + ], + "x-abstract-component": false + }, + "BaseSSEmTLSTransport": { + "additionalProperties": false, + "description": "Transport layer for SSE with mTLS (mutual Transport Layer Security).\n\nThis transport establishes a secure, mutually authenticated TLS connection to the MCP server using client\ncertificates. Production deployments MUST use this transport to ensure both client and server identities\nare verified.\n\nNotes\n-----\n- Users MUST provide a valid client certificate (PEM format) and private key.\n- Users MUST provide (or trust) the correct certificate authority (CA) for the server they're connecting to.\n- The client certificate/key and CA certificate paths can be managed via secrets, config files, or secure\n environment variables in any production system.\n- Executors should ensure that these files are rotated and managed securely.", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "session_parameters": { + "$ref": "#/$defs/SessionParameters" + }, + "url": { + "title": "Url", + "type": "string" + }, + "auth": { + "anyOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/AuthConfig" + } + ], + "default": null + }, + "headers": { + "anyOf": [ + { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Headers" + }, + "sensitive_headers": { + "anyOf": [ + { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Sensitive Headers" + }, + "retry_policy": { + "anyOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/RetryPolicy" + } + ], + "default": null + }, + "key_file": { + "title": "Key File", + "type": "string" + }, + "cert_file": { + "title": "Cert File", + "type": "string" + }, + "ca_file": { + "title": "Ca File", + "type": "string" + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "SSEmTLSTransport" + } + }, + "required": [ + "ca_file", + "cert_file", + "key_file", + "name", + "url" + ], + "title": "SSEmTLSTransport", + "type": "object", + "x-abstract-component": false + }, + "BaseServerTool": { + "additionalProperties": false, + "description": "A tool that is registered to and executed by the orchestrator.", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "inputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Inputs" + }, + "outputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Outputs" + }, + "requires_confirmation": { + "default": false, + "title": "Requires Confirmation", + "type": "boolean" + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "ServerTool" + } + }, + "required": [ + "name" + ], + "title": "ServerTool", + "type": "object", + "x-abstract-component": false + }, + "BaseSpecializedAgent": { + "additionalProperties": false, + "description": "A specialized agent is an agent that uses an existing generic agent and\nspecializes it to solve a given task.\n\nIt can be executed anywhere an Agent can be executed.\n\nExamples\n--------\n>>> from pyagentspec.agent import Agent\n>>> from pyagentspec.property import StringProperty\n>>> from pyagentspec.specialized_agent import AgentSpecializationParameters, SpecializedAgent\n>>> from pyagentspec.tools import ServerTool\n>>> expertise_property = StringProperty(title=\"domain_of_expertise\")\n>>> system_prompt = '''You are an expert in {{domain_of_expertise}}.\n... Please help the users with their requests.'''\n>>> agent = Agent(\n... name=\"Adaptive expert agent\",\n... system_prompt=system_prompt,\n... llm_config=llm_config,\n... inputs=[expertise_property],\n... )\n>>> websearch_tool = ServerTool(\n... name=\"websearch_tool\",\n... description=\"Search the web for information\",\n... inputs=[StringProperty(title=\"query\")],\n... outputs=[StringProperty(title=\"search_result\")],\n... )\n>>> agent_specialization_parameters = AgentSpecializationParameters(\n... name=\"essay_agent\",\n... additional_instructions=\"Your goal is to help the user write an essay around the domain of expertise.\",\n... additional_tools=[websearch_tool]\n... )", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "inputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Inputs" + }, + "outputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Outputs" + }, + "agent": { + "$ref": "#/$defs/Agent" + }, + "agent_specialization_parameters": { + "$ref": "#/$defs/AgentSpecializationParameters" + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "SpecializedAgent" + } + }, + "required": [ + "agent", + "agent_specialization_parameters", + "name" + ], + "title": "SpecializedAgent", + "type": "object", + "x-abstract-component": false + }, + "BaseStartNode": { + "additionalProperties": false, + "description": "Start nodes denote the start of the execution of a flow.\n\n- **Inputs**\n The list of inputs that should be the inputs of the flow. If both input and output\n properties are specified they must be an exact match\n\n If None is given, ``pyagentspec`` copies the outputs provided, if any. Otherwise, no input is exposed.\n- **Outputs**\n The list of outputs of the step. If both input and output properties are specified they\n must be an exact match\n\n If None is given, ``pyagentspec`` copies the inputs provided, if any. Otherwise, no output is exposed.\n- **Branches**\n One, the default next.\n\nExamples\n--------\n>>> from pyagentspec.property import Property\n>>> from pyagentspec.flows.edges import ControlFlowEdge, DataFlowEdge\n>>> from pyagentspec.flows.flow import Flow\n>>> from pyagentspec.flows.nodes import EndNode, LlmNode, StartNode\n>>> user_question_property = Property(\n... json_schema=dict(\n... title=\"user_question\",\n... description=\"The user question.\",\n... type=\"string\",\n... )\n... )\n>>> answer_property = Property(json_schema=dict(title=\"answer\", type=\"string\"))\n>>> start_node = StartNode(name=\"start\", inputs=[user_question_property])\n>>> end_node = EndNode(name=\"end\", outputs=[answer_property])\n>>> llm_node = LlmNode(\n... name=\"llm node\",\n... prompt_template=\"Answer the user question: {{user_question}}\",\n... llm_config=llm_config,\n... )\n>>> flow = Flow(\n... name=\"flow\",\n... start_node=start_node,\n... nodes=[start_node, llm_node, end_node],\n... control_flow_connections=[\n... ControlFlowEdge(name=\"start_to_llm\", from_node=start_node, to_node=llm_node),\n... ControlFlowEdge(name=\"llm_to_end\", from_node=llm_node, to_node=end_node),\n... ],\n... data_flow_connections=[\n... DataFlowEdge(\n... name=\"query_edge\",\n... source_node=start_node,\n... source_output=\"user_question\",\n... destination_node=llm_node,\n... destination_input=\"user_question\",\n... ),\n... DataFlowEdge(\n... name=\"answer_edge\",\n... source_node=llm_node,\n... source_output=\"generated_text\",\n... destination_node=end_node,\n... destination_input=\"answer\"\n... ),\n... ],\n... )", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "inputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Inputs" + }, + "outputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Outputs" + }, + "branches": { + "items": { + "type": "string" + }, + "title": "Branches", + "type": "array" + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "StartNode" + } + }, + "required": [ + "name" + ], + "title": "StartNode", + "type": "object", + "x-abstract-component": false + }, + "BaseStdioTransport": { + "additionalProperties": false, + "description": "Base transport for connecting to an MCP server via subprocess with stdio.\n\nThis is a base class that can be subclassed for specific command-based\ntransports like Python, Node, Uvx, etc.\n\n.. warning::\n Stdio should be used for local prototyping only.", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "session_parameters": { + "$ref": "#/$defs/SessionParameters" + }, + "command": { + "title": "Command", + "type": "string" + }, + "args": { + "items": { + "type": "string" + }, + "title": "Args", + "type": "array" + }, + "env": { + "anyOf": [ + { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Env" + }, + "cwd": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Cwd" + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "StdioTransport" + } + }, + "required": [ + "command", + "name" + ], + "title": "StdioTransport", + "type": "object", + "x-abstract-component": false + }, + "BaseStreamableHTTPTransport": { + "anyOf": [ + { + "$ref": "#/$defs/StreamableHTTPmTLSTransport" + }, + { + "additionalProperties": false, + "description": "Transport implementation that connects to an MCP server via Streamable HTTP.", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "session_parameters": { + "$ref": "#/$defs/SessionParameters" + }, + "url": { + "title": "Url", + "type": "string" + }, + "auth": { + "anyOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/AuthConfig" + } + ], + "default": null + }, + "headers": { + "anyOf": [ + { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Headers" + }, + "sensitive_headers": { + "anyOf": [ + { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Sensitive Headers" + }, + "retry_policy": { + "anyOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/RetryPolicy" + } + ], + "default": null + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "StreamableHTTPTransport" + } + }, + "required": [ + "name", + "url" + ], + "title": "StreamableHTTPTransport", + "type": "object" + } + ], + "x-abstract-component": false + }, + "BaseStreamableHTTPmTLSTransport": { + "additionalProperties": false, + "description": "Transport layer for streamable HTTP with mTLS (mutual Transport Layer Security).\n\nThis transport establishes a secure, mutually authenticated TLS connection to the MCP server using client\ncertificates. Production deployments MUST use this transport to ensure both client and server identities\nare verified.\n\nNotes\n-----\n- Users MUST provide a valid client certificate (PEM format) and private key.\n- Users MUST provide (or trust) the correct certificate authority (CA) for the server they're connecting to.\n- The client certificate/key and CA certificate paths can be managed via secrets, config files, or secure\n environment variables in any production system.\n- Executors should ensure that these files are rotated and managed securely.", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "session_parameters": { + "$ref": "#/$defs/SessionParameters" + }, + "url": { + "title": "Url", + "type": "string" + }, + "auth": { + "anyOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/AuthConfig" + } + ], + "default": null + }, + "headers": { + "anyOf": [ + { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Headers" + }, + "sensitive_headers": { + "anyOf": [ + { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Sensitive Headers" + }, + "retry_policy": { + "anyOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/RetryPolicy" + } + ], + "default": null + }, + "key_file": { + "title": "Key File", + "type": "string" + }, + "cert_file": { + "title": "Cert File", + "type": "string" + }, + "ca_file": { + "title": "Ca File", + "type": "string" + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "StreamableHTTPmTLSTransport" + } + }, + "required": [ + "ca_file", + "cert_file", + "key_file", + "name", + "url" + ], + "title": "StreamableHTTPmTLSTransport", + "type": "object", + "x-abstract-component": false + }, + "BaseSwarm": { + "additionalProperties": false, + "description": "Defines a ``Swarm`` conversational component.\n\nA ``Swarm`` is a multi-agent conversational component in which each agent determines\nthe next agent to be executed, based on a list of pre-defined relationships.\nAgents in Swarm can be any ``AgenticComponent``.\n\nExamples\n--------\n>>> from pyagentspec.agent import Agent\n>>> from pyagentspec.swarm import Swarm\n>>> addition_agent = Agent(name=\"addition_agent\", description=\"Agent that can do additions\", llm_config=llm_config, system_prompt=\"You can do additions.\")\n>>> multiplication_agent = Agent(name=\"multiplication_agent\", description=\"Agent that can do multiplication\", llm_config=llm_config, system_prompt=\"You can do multiplication.\")\n>>> division_agent = Agent(name=\"division_agent\", description=\"Agent that can do division\", llm_config=llm_config, system_prompt=\"You can do division.\")\n>>>\n>>> swarm = Swarm(\n... name=\"swarm\",\n... first_agent=addition_agent,\n... relationships=[\n... (addition_agent, multiplication_agent),\n... (addition_agent, division_agent),\n... (multiplication_agent, division_agent),\n... ]\n... )", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "inputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Inputs" + }, + "outputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Outputs" + }, + "first_agent": { + "$ref": "#/$defs/AgenticComponent" + }, + "relationships": { + "items": { + "maxItems": 2, + "minItems": 2, + "prefixItems": [ + { + "$ref": "#/$defs/AgenticComponent" + }, + { + "$ref": "#/$defs/AgenticComponent" + } + ], + "type": "array" + }, + "title": "Relationships", + "type": "array" + }, + "handoff": { + "anyOf": [ + { + "type": "boolean" + }, + { + "$ref": "#/$defs/HandoffMode" + } + ], + "default": "optional", + "title": "Handoff" + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "Swarm" + } + }, + "required": [ + "first_agent", + "name", + "relationships" + ], + "title": "Swarm", + "type": "object", + "x-abstract-component": false + }, + "BaseTlsOracleDatabaseConnectionConfig": { + "anyOf": [ + { + "$ref": "#/$defs/MTlsOracleDatabaseConnectionConfig" + }, + { + "additionalProperties": false, + "description": "TLS Connection Configuration to Oracle Database.", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "user": { + "title": "User", + "type": "string" + }, + "password": { + "title": "Password", + "type": "string" + }, + "dsn": { + "title": "Dsn", + "type": "string" + }, + "config_dir": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Config Dir" + }, + "protocol": { + "default": "tcps", + "enum": [ + "tcp", + "tcps" + ], + "title": "Protocol", + "type": "string" + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "TlsOracleDatabaseConnectionConfig" + } + }, + "required": [ + "dsn", + "name", + "password", + "user" + ], + "title": "TlsOracleDatabaseConnectionConfig", + "type": "object" + } + ], + "x-abstract-component": false + }, + "BaseTlsPostgresDatabaseConnectionConfig": { + "additionalProperties": false, + "description": "Configuration for a PostgreSQL connection with TLS/SSL support.", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "user": { + "title": "User", + "type": "string" + }, + "password": { + "title": "Password", + "type": "string" + }, + "url": { + "title": "Url", + "type": "string" + }, + "sslmode": { + "default": "require", + "enum": [ + "allow", + "disable", + "prefer", + "require", + "verify-ca", + "verify-full" + ], + "title": "Sslmode", + "type": "string" + }, + "sslcert": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Sslcert" + }, + "sslkey": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Sslkey" + }, + "sslrootcert": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Sslrootcert" + }, + "sslcrl": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Sslcrl" + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "TlsPostgresDatabaseConnectionConfig" + } + }, + "required": [ + "name", + "password", + "url", + "user" + ], + "title": "TlsPostgresDatabaseConnectionConfig", + "type": "object", + "x-abstract-component": false + }, + "BaseTool": { + "anyOf": [ + { + "$ref": "#/$defs/BuiltinTool" + }, + { + "$ref": "#/$defs/ClientTool" + }, + { + "$ref": "#/$defs/MCPTool" + }, + { + "$ref": "#/$defs/RemoteTool" + }, + { + "$ref": "#/$defs/ServerTool" + } + ], + "x-abstract-component": true + }, + "BaseToolBox": { + "anyOf": [ + { + "$ref": "#/$defs/MCPToolBox" + } + ], + "x-abstract-component": true + }, + "BaseToolNode": { + "additionalProperties": false, + "description": "The tool execution node is a node that will execute a tool as part of a flow.\n\n- **Inputs**\n Inferred from the definition of the tool to execute.\n- **Outputs**\n Inferred from the definition of the tool to execute.\n- **Branches**\n One, the default next.\n\nExamples\n--------\n>>> from pyagentspec.flows.edges import ControlFlowEdge, DataFlowEdge\n>>> from pyagentspec.flows.flow import Flow\n>>> from pyagentspec.flows.nodes import ToolNode, StartNode, EndNode\n>>> from pyagentspec.tools import ServerTool\n>>> from pyagentspec.property import Property\n>>>\n>>> x_property = Property(json_schema={\"title\": \"x\", \"type\": \"number\"})\n>>> x_square_root_property = Property(\n... json_schema={\"title\": \"x_square_root\", \"type\": \"number\"}\n... )\n>>> square_root_tool = ServerTool(\n... name=\"compute_square_root\",\n... description=\"Computes the square root of a number\",\n... inputs=[x_property],\n... outputs=[x_square_root_property],\n... )\n>>> start_node = StartNode(name=\"start\", inputs=[x_property])\n>>> end_node = EndNode(name=\"end\", outputs=[x_square_root_property])\n>>> tool_node = ToolNode(name=\"\", tool=square_root_tool)\n>>> flow = Flow(\n... name=\"Compute square root flow\",\n... start_node=start_node,\n... nodes=[start_node, tool_node, end_node],\n... control_flow_connections=[\n... ControlFlowEdge(name=\"start_to_tool\", from_node=start_node, to_node=tool_node),\n... ControlFlowEdge(name=\"tool_to_end\", from_node=tool_node, to_node=end_node),\n... ],\n... data_flow_connections=[\n... DataFlowEdge(\n... name=\"x_edge\",\n... source_node=start_node,\n... source_output=\"x\",\n... destination_node=tool_node,\n... destination_input=\"x\",\n... ),\n... DataFlowEdge(\n... name=\"x_square_root_edge\",\n... source_node=tool_node,\n... source_output=\"x_square_root\",\n... destination_node=end_node,\n... destination_input=\"x_square_root\"\n... ),\n... ],\n... )", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "inputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Inputs" + }, + "outputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/Property" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Outputs" + }, + "branches": { + "items": { + "type": "string" + }, + "title": "Branches", + "type": "array" + }, + "tool": { + "$ref": "#/$defs/Tool" + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "ToolNode" + } + }, + "required": [ + "name", + "tool" + ], + "title": "ToolNode", + "type": "object", + "x-abstract-component": false + }, + "BaseVllmConfig": { + "additionalProperties": false, + "description": "Class to configure a connection to a vLLM-hosted LLM.\n\nRequires to specify the url at which the instance is running.", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata" + }, + "model_id": { + "title": "Model Id", + "type": "string" + }, + "provider": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Provider" + }, + "api_provider": { + "const": "vllm", + "default": "vllm", + "title": "Api Provider", + "type": "string" + }, + "api_type": { + "$ref": "#/$defs/OpenAIAPIType", + "default": "chat_completions" + }, + "url": { + "title": "Url", + "type": "string" + }, + "api_key": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Api Key" + }, + "default_generation_parameters": { + "anyOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/LlmGenerationConfig" + } + ], + "default": null + }, + "retry_policy": { + "anyOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/RetryPolicy" + } + ], + "default": null + }, + "key_file": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Key File" + }, + "cert_file": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Cert File" + }, + "ca_file": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Ca File" + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "component_type": { + "const": "VllmConfig" + } + }, + "required": [ + "model_id", + "name", + "url" + ], + "title": "VllmConfig", + "type": "object", + "x-abstract-component": false + }, + "ReferencedComponents": { + "type": "object", + "additionalProperties": { + "anyOf": [ + { + "$ref": "#/$defs/BaseA2AAgent" + }, + { + "$ref": "#/$defs/BaseA2AConnectionConfig" + }, + { + "$ref": "#/$defs/BaseAgent" + }, + { + "$ref": "#/$defs/BaseAgentNode" + }, + { + "$ref": "#/$defs/BaseAgentSpecializationParameters" + }, + { + "$ref": "#/$defs/BaseAgenticComponent" + }, + { + "$ref": "#/$defs/BaseApiNode" + }, + { + "$ref": "#/$defs/BaseAuthConfig" + }, + { + "$ref": "#/$defs/BaseBranchingNode" + }, + { + "$ref": "#/$defs/BaseBuiltinTool" + }, + { + "$ref": "#/$defs/BaseCatchExceptionNode" + }, + { + "$ref": "#/$defs/BaseClientTool" + }, + { + "$ref": "#/$defs/BaseClientTransport" + }, + { + "$ref": "#/$defs/BaseComponentWithIO" + }, + { + "$ref": "#/$defs/BaseControlFlowEdge" + }, + { + "$ref": "#/$defs/BaseConversationSummarizationTransform" + }, + { + "$ref": "#/$defs/BaseDataFlowEdge" + }, + { + "$ref": "#/$defs/BaseDatastore" + }, + { + "$ref": "#/$defs/BaseDbmsVectorChainLlmConfig" + }, + { + "$ref": "#/$defs/BaseEndNode" + }, + { + "$ref": "#/$defs/BaseFlow" + }, + { + "$ref": "#/$defs/BaseFlowNode" + }, + { + "$ref": "#/$defs/BaseGeminiAIStudioAuthConfig" + }, + { + "$ref": "#/$defs/BaseGeminiAuthConfig" + }, + { + "$ref": "#/$defs/BaseGeminiConfig" + }, + { + "$ref": "#/$defs/BaseGeminiVertexAIAuthConfig" + }, + { + "$ref": "#/$defs/BaseInMemoryCollectionDatastore" + }, + { + "$ref": "#/$defs/BaseInputMessageNode" + }, + { + "$ref": "#/$defs/BaseLlmConfig" + }, + { + "$ref": "#/$defs/BaseLlmNode" + }, + { + "$ref": "#/$defs/BaseMCPTool" + }, + { + "$ref": "#/$defs/BaseMCPToolBox" + }, + { + "$ref": "#/$defs/BaseMCPToolSpec" + }, + { + "$ref": "#/$defs/BaseMTlsOracleDatabaseConnectionConfig" + }, + { + "$ref": "#/$defs/BaseManagerWorkers" + }, + { + "$ref": "#/$defs/BaseMapNode" + }, + { + "$ref": "#/$defs/BaseMessageSummarizationTransform" + }, + { + "$ref": "#/$defs/BaseMessageTransform" + }, + { + "$ref": "#/$defs/BaseNode" + }, + { + "$ref": "#/$defs/BaseOAuthClientConfig" + }, + { + "$ref": "#/$defs/BaseOAuthConfig" + }, + { + "$ref": "#/$defs/BaseOciAgent" + }, + { + "$ref": "#/$defs/BaseOciClientConfig" + }, + { + "$ref": "#/$defs/BaseOciClientConfigWithApiKey" + }, + { + "$ref": "#/$defs/BaseOciClientConfigWithInstancePrincipal" + }, + { + "$ref": "#/$defs/BaseOciClientConfigWithResourcePrincipal" + }, + { + "$ref": "#/$defs/BaseOciClientConfigWithSecurityToken" + }, + { + "$ref": "#/$defs/BaseOciGenAiConfig" + }, + { + "$ref": "#/$defs/BaseOllamaConfig" + }, + { + "$ref": "#/$defs/BaseOpenAiCompatibleConfig" + }, + { + "$ref": "#/$defs/BaseOpenAiConfig" + }, + { + "$ref": "#/$defs/BaseOracleDatabaseConnectionConfig" + }, + { + "$ref": "#/$defs/BaseOracleDatabaseDatastore" + }, + { + "$ref": "#/$defs/BaseOutputMessageNode" + }, + { + "$ref": "#/$defs/BaseParallelFlowNode" + }, + { + "$ref": "#/$defs/BaseParallelMapNode" + }, + { + "$ref": "#/$defs/BasePostgresDatabaseConnectionConfig" + }, + { + "$ref": "#/$defs/BasePostgresDatabaseDatastore" + }, + { + "$ref": "#/$defs/BaseRemoteAgent" + }, + { + "$ref": "#/$defs/BaseRemoteTool" + }, + { + "$ref": "#/$defs/BaseRemoteTransport" + }, + { + "$ref": "#/$defs/BaseSSETransport" + }, + { + "$ref": "#/$defs/BaseSSEmTLSTransport" + }, + { + "$ref": "#/$defs/BaseServerTool" + }, + { + "$ref": "#/$defs/BaseSpecializedAgent" + }, + { + "$ref": "#/$defs/BaseStartNode" + }, + { + "$ref": "#/$defs/BaseStdioTransport" + }, + { + "$ref": "#/$defs/BaseStreamableHTTPTransport" + }, + { + "$ref": "#/$defs/BaseStreamableHTTPmTLSTransport" + }, + { + "$ref": "#/$defs/BaseSwarm" + }, + { + "$ref": "#/$defs/BaseTlsOracleDatabaseConnectionConfig" + }, + { + "$ref": "#/$defs/BaseTlsPostgresDatabaseConnectionConfig" + }, + { + "$ref": "#/$defs/BaseTool" + }, + { + "$ref": "#/$defs/BaseToolBox" + }, + { + "$ref": "#/$defs/BaseToolNode" + }, + { + "$ref": "#/$defs/BaseVllmConfig" + }, + { + "$ref": "#/$defs/ComponentReferenceWithNestedReferences" + } + ] + } + }, + "ComponentReference": { + "type": "object", + "properties": { + "$component_ref": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "$component_ref" + ] + }, + "ComponentReferenceWithNestedReferences": { + "type": "object", + "properties": { + "$component_ref": { + "type": "string" + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + } + }, + "additionalProperties": false, + "required": [ + "$component_ref", + "$referenced_components" + ] + }, + "VersionedA2AAgent": { + "anyOf": [ + { + "$ref": "#/$defs/BaseA2AAgent" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedA2AConnectionConfig": { + "anyOf": [ + { + "$ref": "#/$defs/BaseA2AConnectionConfig" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedAgent": { + "anyOf": [ + { + "$ref": "#/$defs/BaseAgent" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedAgentNode": { + "anyOf": [ + { + "$ref": "#/$defs/BaseAgentNode" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedAgentSpecializationParameters": { + "anyOf": [ + { + "$ref": "#/$defs/BaseAgentSpecializationParameters" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedAgenticComponent": { + "anyOf": [ + { + "$ref": "#/$defs/BaseAgenticComponent" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedApiNode": { + "anyOf": [ + { + "$ref": "#/$defs/BaseApiNode" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedBranchingNode": { + "anyOf": [ + { + "$ref": "#/$defs/BaseBranchingNode" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedBuiltinTool": { + "anyOf": [ + { + "$ref": "#/$defs/BaseBuiltinTool" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedCatchExceptionNode": { + "anyOf": [ + { + "$ref": "#/$defs/BaseCatchExceptionNode" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedClientTool": { + "anyOf": [ + { + "$ref": "#/$defs/BaseClientTool" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedClientTransport": { + "anyOf": [ + { + "$ref": "#/$defs/BaseClientTransport" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedComponentWithIO": { + "anyOf": [ + { + "$ref": "#/$defs/BaseComponentWithIO" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedControlFlowEdge": { + "anyOf": [ + { + "$ref": "#/$defs/BaseControlFlowEdge" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedConversationSummarizationTransform": { + "anyOf": [ + { + "$ref": "#/$defs/BaseConversationSummarizationTransform" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedDataFlowEdge": { + "anyOf": [ + { + "$ref": "#/$defs/BaseDataFlowEdge" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedDatastore": { + "anyOf": [ + { + "$ref": "#/$defs/BaseDatastore" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedDbmsVectorChainLlmConfig": { + "anyOf": [ + { + "$ref": "#/$defs/BaseDbmsVectorChainLlmConfig" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedEndNode": { + "anyOf": [ + { + "$ref": "#/$defs/BaseEndNode" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedFlow": { + "anyOf": [ + { + "$ref": "#/$defs/BaseFlow" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedFlowNode": { + "anyOf": [ + { + "$ref": "#/$defs/BaseFlowNode" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedGeminiAIStudioAuthConfig": { + "anyOf": [ + { + "$ref": "#/$defs/BaseGeminiAIStudioAuthConfig" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedGeminiAuthConfig": { + "anyOf": [ + { + "$ref": "#/$defs/BaseGeminiAuthConfig" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedGeminiConfig": { + "anyOf": [ + { + "$ref": "#/$defs/BaseGeminiConfig" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedGeminiVertexAIAuthConfig": { + "anyOf": [ + { + "$ref": "#/$defs/BaseGeminiVertexAIAuthConfig" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedInMemoryCollectionDatastore": { + "anyOf": [ + { + "$ref": "#/$defs/BaseInMemoryCollectionDatastore" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedInputMessageNode": { + "anyOf": [ + { + "$ref": "#/$defs/BaseInputMessageNode" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedLlmConfig": { + "anyOf": [ + { + "$ref": "#/$defs/BaseLlmConfig" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedLlmNode": { + "anyOf": [ + { + "$ref": "#/$defs/BaseLlmNode" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedMCPTool": { + "anyOf": [ + { + "$ref": "#/$defs/BaseMCPTool" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedMCPToolBox": { + "anyOf": [ + { + "$ref": "#/$defs/BaseMCPToolBox" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedMCPToolSpec": { + "anyOf": [ + { + "$ref": "#/$defs/BaseMCPToolSpec" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedMTlsOracleDatabaseConnectionConfig": { + "anyOf": [ + { + "$ref": "#/$defs/BaseMTlsOracleDatabaseConnectionConfig" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedManagerWorkers": { + "anyOf": [ + { + "$ref": "#/$defs/BaseManagerWorkers" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedMapNode": { + "anyOf": [ + { + "$ref": "#/$defs/BaseMapNode" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedMessageSummarizationTransform": { + "anyOf": [ + { + "$ref": "#/$defs/BaseMessageSummarizationTransform" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedNode": { + "anyOf": [ + { + "$ref": "#/$defs/BaseNode" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedOAuthClientConfig": { + "anyOf": [ + { + "$ref": "#/$defs/BaseOAuthClientConfig" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedOAuthConfig": { + "anyOf": [ + { + "$ref": "#/$defs/BaseOAuthConfig" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedOciAgent": { + "anyOf": [ + { + "$ref": "#/$defs/BaseOciAgent" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedOciClientConfig": { + "anyOf": [ + { + "$ref": "#/$defs/BaseOciClientConfig" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedOciClientConfigWithApiKey": { + "anyOf": [ + { + "$ref": "#/$defs/BaseOciClientConfigWithApiKey" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedOciClientConfigWithInstancePrincipal": { + "anyOf": [ + { + "$ref": "#/$defs/BaseOciClientConfigWithInstancePrincipal" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedOciClientConfigWithResourcePrincipal": { + "anyOf": [ + { + "$ref": "#/$defs/BaseOciClientConfigWithResourcePrincipal" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedOciClientConfigWithSecurityToken": { + "anyOf": [ + { + "$ref": "#/$defs/BaseOciClientConfigWithSecurityToken" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedOciGenAiConfig": { + "anyOf": [ + { + "$ref": "#/$defs/BaseOciGenAiConfig" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedOllamaConfig": { + "anyOf": [ + { + "$ref": "#/$defs/BaseOllamaConfig" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedOpenAiCompatibleConfig": { + "anyOf": [ + { + "$ref": "#/$defs/BaseOpenAiCompatibleConfig" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedOpenAiConfig": { + "anyOf": [ + { + "$ref": "#/$defs/BaseOpenAiConfig" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedOracleDatabaseDatastore": { + "anyOf": [ + { + "$ref": "#/$defs/BaseOracleDatabaseDatastore" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedOutputMessageNode": { + "anyOf": [ + { + "$ref": "#/$defs/BaseOutputMessageNode" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedParallelFlowNode": { + "anyOf": [ + { + "$ref": "#/$defs/BaseParallelFlowNode" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedParallelMapNode": { + "anyOf": [ + { + "$ref": "#/$defs/BaseParallelMapNode" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedPostgresDatabaseDatastore": { + "anyOf": [ + { + "$ref": "#/$defs/BasePostgresDatabaseDatastore" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedRemoteAgent": { + "anyOf": [ + { + "$ref": "#/$defs/BaseRemoteAgent" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedRemoteTool": { + "anyOf": [ + { + "$ref": "#/$defs/BaseRemoteTool" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedRemoteTransport": { + "anyOf": [ + { + "$ref": "#/$defs/BaseRemoteTransport" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedSSETransport": { + "anyOf": [ + { + "$ref": "#/$defs/BaseSSETransport" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedSSEmTLSTransport": { + "anyOf": [ + { + "$ref": "#/$defs/BaseSSEmTLSTransport" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedServerTool": { + "anyOf": [ + { + "$ref": "#/$defs/BaseServerTool" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedSpecializedAgent": { + "anyOf": [ + { + "$ref": "#/$defs/BaseSpecializedAgent" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedStartNode": { + "anyOf": [ + { + "$ref": "#/$defs/BaseStartNode" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedStdioTransport": { + "anyOf": [ + { + "$ref": "#/$defs/BaseStdioTransport" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedStreamableHTTPTransport": { + "anyOf": [ + { + "$ref": "#/$defs/BaseStreamableHTTPTransport" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedStreamableHTTPmTLSTransport": { + "anyOf": [ + { + "$ref": "#/$defs/BaseStreamableHTTPmTLSTransport" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedSwarm": { + "anyOf": [ + { + "$ref": "#/$defs/BaseSwarm" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedTlsOracleDatabaseConnectionConfig": { + "anyOf": [ + { + "$ref": "#/$defs/BaseTlsOracleDatabaseConnectionConfig" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedTlsPostgresDatabaseConnectionConfig": { + "anyOf": [ + { + "$ref": "#/$defs/BaseTlsPostgresDatabaseConnectionConfig" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedTool": { + "anyOf": [ + { + "$ref": "#/$defs/BaseTool" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedToolBox": { + "anyOf": [ + { + "$ref": "#/$defs/BaseToolBox" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedToolNode": { + "anyOf": [ + { + "$ref": "#/$defs/BaseToolNode" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedVllmConfig": { + "anyOf": [ + { + "$ref": "#/$defs/BaseVllmConfig" + }, + { + "$ref": "#/$defs/ComponentReference" + } + ], + "properties": { + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + } + }, + "VersionedComponentReferenceWithNestedReferences": { + "type": "object", + "properties": { + "$component_ref": { + "type": "string" + }, + "$referenced_components": { + "$ref": "#/$defs/ReferencedComponents" + }, + "agentspec_version": { + "$ref": "#/$defs/AgentSpecVersionEnum" + } + }, + "additionalProperties": false, + "required": [ + "$component_ref", + "$referenced_components" + ] + } + }, + "anyOf": [ + { + "$ref": "#/$defs/VersionedA2AAgent" + }, + { + "$ref": "#/$defs/VersionedA2AConnectionConfig" + }, + { + "$ref": "#/$defs/VersionedAgent" + }, + { + "$ref": "#/$defs/VersionedAgentNode" + }, + { + "$ref": "#/$defs/VersionedAgentSpecializationParameters" + }, + { + "$ref": "#/$defs/VersionedAgenticComponent" + }, + { + "$ref": "#/$defs/VersionedApiNode" + }, + { + "$ref": "#/$defs/VersionedBranchingNode" + }, + { + "$ref": "#/$defs/VersionedBuiltinTool" + }, + { + "$ref": "#/$defs/VersionedCatchExceptionNode" + }, + { + "$ref": "#/$defs/VersionedClientTool" + }, + { + "$ref": "#/$defs/VersionedClientTransport" + }, + { + "$ref": "#/$defs/VersionedComponentReferenceWithNestedReferences" + }, + { + "$ref": "#/$defs/VersionedComponentWithIO" + }, + { + "$ref": "#/$defs/VersionedControlFlowEdge" + }, + { + "$ref": "#/$defs/VersionedConversationSummarizationTransform" + }, + { + "$ref": "#/$defs/VersionedDataFlowEdge" + }, + { + "$ref": "#/$defs/VersionedDatastore" + }, + { + "$ref": "#/$defs/VersionedDbmsVectorChainLlmConfig" + }, + { + "$ref": "#/$defs/VersionedEndNode" + }, + { + "$ref": "#/$defs/VersionedFlow" + }, + { + "$ref": "#/$defs/VersionedFlowNode" + }, + { + "$ref": "#/$defs/VersionedGeminiAIStudioAuthConfig" + }, + { + "$ref": "#/$defs/VersionedGeminiAuthConfig" + }, + { + "$ref": "#/$defs/VersionedGeminiConfig" + }, + { + "$ref": "#/$defs/VersionedGeminiVertexAIAuthConfig" + }, + { + "$ref": "#/$defs/VersionedInMemoryCollectionDatastore" + }, + { + "$ref": "#/$defs/VersionedInputMessageNode" + }, + { + "$ref": "#/$defs/VersionedLlmConfig" + }, + { + "$ref": "#/$defs/VersionedLlmNode" + }, + { + "$ref": "#/$defs/VersionedMCPTool" + }, + { + "$ref": "#/$defs/VersionedMCPToolBox" + }, + { + "$ref": "#/$defs/VersionedMCPToolSpec" + }, + { + "$ref": "#/$defs/VersionedMTlsOracleDatabaseConnectionConfig" + }, + { + "$ref": "#/$defs/VersionedManagerWorkers" + }, + { + "$ref": "#/$defs/VersionedMapNode" + }, + { + "$ref": "#/$defs/VersionedMessageSummarizationTransform" + }, + { + "$ref": "#/$defs/VersionedNode" + }, + { + "$ref": "#/$defs/VersionedOAuthClientConfig" + }, + { + "$ref": "#/$defs/VersionedOAuthConfig" + }, + { + "$ref": "#/$defs/VersionedOciAgent" + }, + { + "$ref": "#/$defs/VersionedOciClientConfig" + }, + { + "$ref": "#/$defs/VersionedOciClientConfigWithApiKey" + }, + { + "$ref": "#/$defs/VersionedOciClientConfigWithInstancePrincipal" + }, + { + "$ref": "#/$defs/VersionedOciClientConfigWithResourcePrincipal" + }, + { + "$ref": "#/$defs/VersionedOciClientConfigWithSecurityToken" + }, + { + "$ref": "#/$defs/VersionedOciGenAiConfig" + }, + { + "$ref": "#/$defs/VersionedOllamaConfig" + }, + { + "$ref": "#/$defs/VersionedOpenAiCompatibleConfig" + }, + { + "$ref": "#/$defs/VersionedOpenAiConfig" + }, + { + "$ref": "#/$defs/VersionedOracleDatabaseDatastore" + }, + { + "$ref": "#/$defs/VersionedOutputMessageNode" + }, + { + "$ref": "#/$defs/VersionedParallelFlowNode" + }, + { + "$ref": "#/$defs/VersionedParallelMapNode" + }, + { + "$ref": "#/$defs/VersionedPostgresDatabaseDatastore" + }, + { + "$ref": "#/$defs/VersionedRemoteAgent" + }, + { + "$ref": "#/$defs/VersionedRemoteTool" + }, + { + "$ref": "#/$defs/VersionedRemoteTransport" + }, + { + "$ref": "#/$defs/VersionedSSETransport" + }, + { + "$ref": "#/$defs/VersionedSSEmTLSTransport" + }, + { + "$ref": "#/$defs/VersionedServerTool" + }, + { + "$ref": "#/$defs/VersionedSpecializedAgent" + }, + { + "$ref": "#/$defs/VersionedStartNode" + }, + { + "$ref": "#/$defs/VersionedStdioTransport" + }, + { + "$ref": "#/$defs/VersionedStreamableHTTPTransport" + }, + { + "$ref": "#/$defs/VersionedStreamableHTTPmTLSTransport" + }, + { + "$ref": "#/$defs/VersionedSwarm" + }, + { + "$ref": "#/$defs/VersionedTlsOracleDatabaseConnectionConfig" + }, + { + "$ref": "#/$defs/VersionedTlsPostgresDatabaseConnectionConfig" + }, + { + "$ref": "#/$defs/VersionedTool" + }, + { + "$ref": "#/$defs/VersionedToolBox" + }, + { + "$ref": "#/$defs/VersionedToolNode" + }, + { + "$ref": "#/$defs/VersionedVllmConfig" + } + ] +} diff --git a/docs/pyagentspec/source/agentspec/language_spec_26_3_0.rst b/docs/pyagentspec/source/agentspec/language_spec_26_3_0.rst new file mode 100644 index 00000000..71ee6a1a --- /dev/null +++ b/docs/pyagentspec/source/agentspec/language_spec_26_3_0.rst @@ -0,0 +1,3477 @@ +.. _agentspecspec_v26.3.0: + +========================================= +Agent Spec specification (version 26.3.0) +========================================= + +Language specification +====================== + +This document outlines the language specification for the Open Agent Specification, abbreviated as Agent Spec. + +This specification defines the foundational parts that compose the language, including the expected format +and the respective semantic. + +The class definition in the following sections is outlined via Python below for ease of comprehension. +This structure can be serialized into JSON, YAML, or other formats. +The respective serialization is also called Agent Spec configuration (or representation). + +.. warning:: + Agent Spec serialized specifications are not supposed to contain executable code. + Users should adopt security measures in their serialization and deserialization code when using formats + that support such a functionality (e.g., using safe loading and dumping with YAML). + + +Base ``Component`` +------------------ + +The base of Agent Spec is a ``Component``, which by itself can be used to +describe any instance of any type, guaranteeing flexibility to adapt to future changes. + +Note that Agent Spec does not need to encapsulate the implementation for code it describes; +it simply needs to be able to express enough information to instantiate a uniquely-identifiable +object of a specific type with a specific set of property values. + +.. code-block:: python + + class Component: + # All components have a set of fixed common properties + id: str # Unique identifier for the component. Typically generated from component type plus a unique token. + type: str # Concrete type specifier for this component. Can be (but is not required to be) a class name. + name: str # Name of the component, if applicable + description: Optional[str] # Description of the component, if applicable + metadata: Optional[Dictionary[str, Any]] # Additional metadata that could be used for extra information + + +The ``metadata`` field contains all the additional information that +could be useful in different usages of the component. For example, GUIs will +require to include some information about the components (e.g., position +coordinates, size, color, notes, ...), so that they will be able to +visualise them properly even after an export-import process. Metadata is +optional, and empty by default. + +Not all the building blocks of Agent Spec are necessarily ``Components``. +Some other convenient classes can be defined in order to more easily +describe the configurations of the agents (e.g., ``JSONSchemaValue``, ``LlmGenerationConfig``). +Those classes do not have to align to the interface of a Component. + +.. _symbolic_reference_nightly: + +Symbolic references (configuration components) +---------------------------------------------- + +When a component wishes to reference another component entity (e.g., as a property value), +there is a simple symbolic syntax to accomplish this: an object with a single property +"$component_ref" whose value is the id of the referenced component. +This type of relationship is applicable to any component. + +.. code-block:: JSON + + {"$component_ref": "{COMPONENT_ID}"} + +Note that a subcomponent reused in multiple parts of a complex nested component +(for example, a ClientTool used in both an AgentNode and a ToolNode as part of +a flow) must be defined using a component reference. If two components in an +Agent Spec configuration use the same `id`, the configuration will be considered +invalid. + + +Input/output schemas +-------------------- + +Components might need some input data in order to perform their task, +and expose some output as a result of their work. + +These inputs and outputs must be declared and described in the exported configuration, so +that users, developers, and other components of the Agent as well, are +aware of what is exposed by component that require inputs, or provide +outputs. We call these input and output descriptions as input/output +schemas, and we add a subclass of Component called ComponentWithIO that +adds them to the base Component class. + +.. code-block:: python + + class ComponentWithIO(Component): + inputs: List[JSONSchemaValue] + outputs: List[JSONSchemaValue] + +Note that we do not add input/output schemas directly to the base +Component class, as there are a few cases where they do not really apply +(e.g., LlmConfig, edges, see their definition in the next sections). + +Input and output properties +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Input and output schemas are used to define which values the different +components accept as input, or provide as output. We call these values +"properties". + +The term "properties" comes from `JSON +schema `__: indeed, in order to specify all the +inputs and outputs, we rely on this widely adopted and consolidated +standard. + +In particular, we ask to specify a list of JSON schemas definitions, one +for each input and output property. For more information about JSON +schema definition, please check the official website at +https://json-schema.org/understanding-json-schema. + +In order for the schema to be valid, we require to specify some attributes: + +- title: the name of the property +- type: the type of the property, following the type system described + below + +Additionally, users can specify: + +- default: the default value of this property +- description: a textual description for the property + +These are the minimal attributes (or *annotations*, in JSON Schema terminology) +that must be supported to ensure compatibility with Agent Spec. + +Type system +~~~~~~~~~~~ + +We rely on the typing system defined by the JSON schema standard +(see https://json-schema.org/understanding-json-schema/reference/type). + +At its core, JSON Schema defines the following basic types: + +- `string `__ +- `number `__ +- `integer `__ +- `object `__ +- `array `__ +- `boolean `__ +- `null `__ + +These types have analogs in most programming languages, though they may +go by different names. + +Note that some types (array, object) require additional annotations with +respect to the ones listed in the previous section. Please refer to +their JSON schema definition for more information. + +Type compatibility rules +~~~~~~~~~~~~~~~~~~~~~~~~ + +To connect a component's output to another component's input, the output type must be compatible with the input type. +Specifically, the output type should be a subtype of the input type. +For instance, an output of type ``number`` can connect to an input of type ``[number, null]``, +but an output of type ``string`` cannot connect to an input of type ``number``. + +In order to further simplify input-output connections, we define some simple type compatibility rules that are accepted by Agent Spec. + +- Every type can be converted to string. +- Numeric types (i.e., ``integer`` and ``number``) can be converted to each other. + Note that this conversion could cause the loss of decimals. +- The Boolean type can be converted to numeric ones and vice-versa. + The convention for the conversion is the one adopted by most programming languages: + 0 refers to ``false``, while any other number refers to ``true``. + +These rules apply recursively in complex types: + +- To the types of the elements of an ``array``; +- To the types of the ``properties`` of an ``object``. + + +Inputs and outputs of nested components +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +In case of nested components (e.g., using an agent inside a flow, or a +flow inside another flow, or even just a step in a flow), the wrapping +component is supposed to expose a (sub)set of the inputs/outputs +provided by the inner components, united to, potentially, additional +inputs/outputs it generates itself. + +We require to replicate the JSON schema of each value in the +inputs/outputs lists of every component that exposes it, i.e., if a +wrapper component exposes some inputs and outputs of some of its +internal components, it will have to include them in its inputs/outputs +lists as well. + +This makes the representation of the components a bit more verbose, but +more clear and readable. Parsers of the representation are required to +ensure the consistency of the input/output schemas defined in it. + +|image6| + +In the example above we have that the wrapper Flow Component (see +sections below for more details about flows) requires two inputs that +coincide with two of the inputs exposed by the Node components in it (see +sections below for more details about nodes), and exposes one output among +those exposed by the inner nodes, plus an additional one it computes +itself. This corresponds to the following code definition. + +**Input output schema example** + +.. code-block:: python + + # String with "input_1_default" as default value + input_1 = StringType(title="Input_1", default="input_1_default") + + # Dictionary with string keys and integers as values, default set to empty dictionary + input_2 = ObjectType(title="Input_2", properties={}, additional_properties=NumberType(), default={}) + + # Nullable string, default value set to null + input_3 = StringType(title="Input_3", nullable=True, default=None) + + output_1 = StringType(title="Output_1") + output_2 = BooleanType(title="Output_2", default=True) + output_3 = ArrayType(title="Output_3", items=ArrayType(items=NumberType()), default=[[1], [2], [3]]) + + # Note that we envision a fluent-classes way of defining inputs and outputs + # These classes get serialized to their respective JSON schema equivalent + + # VLlmConfig is a subclass of Component, we will define it more in detail later + # This Component does not have input/output schemas, the default should be an empty list + vllm = VllmConfig(name="LLama 3.1 8b", model_id="llama3.1-8b-instruct", url="url.to.my.llm.com/hostedllm:12345") + + # Node is a subclass of Component, we will define it more in detail later + node_1 = StartNode(name="Node 1", input_values=[input_1, input_2, input_3]) + node_2 = LLMNode(name="Node 2", llm=vllm, ...) + node_3 = EndNode(name="Node 3", output_values=[output_1, output_2, output_3]) + + # Flow is a subclass of Component, we will define it more in detail later + flow = Flow(nodes=[node_1, node_2, node_3], ..., inputs=[input_1, input_2], outputs=[output_2, output_3]) + +The presence of the inputs and outputs in the representation of every +component does not imply that they always have to be explicitly defined +by users. + +Indeed, they could be automatically and dynamically generated by the +components (e.g., inputs and outputs inferred from a prompt of an LLMNode). + +Validation of specified inputs/outputs +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Some inputs and outputs schema are automatically generated by components +based on their configuration. However, to improve the clarity and readability +of Agent Spec, descriptions of all inputs and outputs should always be explicitly +included in the representation. + +As a consequence, when a representation is read and imported by a runtime, or an +SDK, the inputs and outputs configuration of each component must be +validated against the one generated by its configuration (if any). + +Note that the input/output schema could be a specialization of the one +generated automatically by the component (e.g., an additional +description is added, a more precise type is defined, ...). + +For this reason, we do not enforce the generated I/O schemas to be +perfectly equal to the one reported in the configuration, but we require that: + +- The set of input and output property names are equivalent +- The type of each property in the generated I/O schemas can be casted + to the type of the corresponding property reported in the configuration. + +If these two requirements are not met, the Agent Spec configuration should be considered invalid. + +Specifying inputs through placeholders +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Some Components might infer inputs from special parts of their +attributes. For example, some nodes (e.g., the LLMNode) extract inputs +from a property called ``prompt_template``, so that its value can be +adapted based on previous computations. + +We let users define placeholders in some attributes, by simply wrapping +the name of the property among a double set of curly brackets. The node +will automatically infer the inputs by interpreting those +configurations, and the actual value of the input/output will be +replaced at execution time. Whether an attribute accepts placeholders +depends on the definition of the component itself. + +For example, setting the prompt_template configuration of an LLMNode to +"You are a great assistant. Please help the user with this request: +{{request}}" will generate an input called "request" of type string. + +We do not require the support for complex types and collections like +lists (array) and dictionaries (object) in placeholders: placeholders +are typically transformed in inputs of type string, unless differently +specified by the definition of the Component. In general, dictionaries +and lists should be used directly in inputs and outputs with a matching +type definition. The latter could also be used as input to the MapNode +(see definition below), which applies the same flow to all the elements +in the list and collects the output. + +In the future, we will consider adding support for complex types (e.g., +for loops over lists, dictionary access) through templating, for example +by adopting a subset of the functionalities available in a common standard like +`jinja `__. + +Component families +------------------ + +The base ``Component`` type is extended by several component families +which represent groups of elements that are commonly used in agentic +systems, such as (but not limited to) LLMs and Tools. + +Describing these component families with specific types achieves the +following goals : + +- Type safety + Usage hints for GUIs + + - *Example: if a component's property is expecting an LLM, only allow + LLM's to be connected to it.* + +- Static analysis + validation + + - *Examples: if a component is a ``Flow``, it must have a start node. + If a component is an Agent, it must have an LLM.* + +- Ease of programmatic use + + - Component families each have corresponding class definitions in the + Agent Spec SDK. This allows consumers (agent execution environments, + editing GUIs) to utilise concrete classes, rather than having to + implicitly understand which type has which properties. + + +.. list-table:: Component families + :header-rows: 1 + + * - Component Family + - Notes + - Example + * - Agentic Component + - A top-level, interactive entity that can **receive messages** and **produce messages**. + ``Flow``, ``Agent``, ``RemoteAgent``, ``Swarm``, ``ManagerWorkers`` and ``SpecializedAgent`` are + all specialisations of this family. + - * A multi-step approval flow + * A ReACT Agent + * A remotely-hosted OCI Generative AI Agent + * - Agent + - An agent is an interactive entity that can converse and use tools. + Generic Agents can be specialized for specific tasks and use-cases. + - * A ReACT agent with tools + * - Remote Agent + - An ``AgenticComponent`` whose implementation is **defined and executed + outside** the current runtime (e.g. via REST/RPC). Provides an easy way + to orchestrate SaaS or micro-service based agents. + - * A Slackbot backed by an external service + * - Flow + - An execution graph with a fixed layout, potentially containing branches and loops. + - A workflow with multiple, well known and well defined approval actions to be pursued based on some condition + * - Node (flow) + - A Flow consists of a series of Node instances. There is a "standard library" of nodes + for things such as executing a prompt with a LLM, branching, etc. + - * A step in the execution of a flow, it corresponds to a specific action + * - Relations / edges (flow) + - Flow of control and I/O (data) are defined by explicit relationships in Agent Spec. + - * Define which is the sequence of nodes that should be executed + * Define which outputs should be used to fill which inputs + * - LLM (config) + - The configuration needed to reach out to an LLM + - * URL and id of a model deployed remotely through vLLM + * - Tool + - A procedural function that can be made available to an Agent + - * A tool to calculate the Levenshtein distance between two strings + * A tool to execute a remote API on OCI + * - ToolBox + - A component that exposes a set of Tools to Agentic Components. ToolBoxes are + discovery/aggregation constructs, not executable tools. MCPToolBox exposes tools + discovered from an MCP server. + - * A ToolBox connecting to an MCP server and exposing its tools to an Agent + + * - Datastore + - A component that provides storage capabilities for agentic systems, enabling them to store and access data. + - * An in-memory datastore for testing + * An Oracle Database datastore and a Postgres Database datastore for production use + + + +Agentic Components +~~~~~~~~~~~~~~~~~~ + +An **Agentic Component** represents anything you can "talk" to – i.e. an entity +that consumes a conversation (or other structured context), performs some work, +and returns a result that can be rendered as messages or structured data. It also represents +the entry point for interactions with the agentic system. It extends from ``ComponentWithIO`` and can have inputs +and outputs. + +Three concrete kinds are currently defined: + +1. ``Flow`` – a self-contained execution graph (described later + in the document). +2. ``Agent`` – an in-process, conversational entity that can reason, call + tools, and maintain state. +3. ``RemoteAgent`` – a conversational entity that is defined **remotely** and + invoked through an RPC or REST call. +4. ``Swarm`` – a multi-agent conversational component in which each agent can call to other agents based on a list of pre-defined relationships. +5. ``ManagerWorkers`` – a multi-agent conversational component in which a manager agent can assign tasks to worker agents. + +Agent +~~~~~ + +The ``Agent`` is the top-level construct that holds shared resources such +as conversation memory and tools. It also represents the entry point +for interactions with the agentic system. + +.. code-block:: python + + class Agent(ComponentWithIO): + system_prompt: str + llm_configuration: LlmConfig + tools: List[Tool] + toolboxes: List[ToolBox] + human_in_the_loop: bool + transforms: List[MessageTransform] + +Its main goal is to accomplish any task assigned in the ``system_prompt`` and terminate, +with or without any interaction with the user. +If outputs are defined, structured generation is enabled, and the Agent should also fill the values for +all the properties defined in the outputs attribute (or at least those that do not have a default value +defined) before terminating. + +It's important to have a separate definition for Agents as components, +so that the same Agent can be reused several times, e.g., in different flows, +without replicating its definition multiple times. + +Note that we do not require to define any output parser for an Agent. +Outputs (including their types, descriptions, ...) are defined at representation +level, and the Agent will fill their values either directly, or through tools. +If a special output format is required, users can specify a tool to fit the requirement. + +It is also not required to specify the presence of tools in the ``system_prompt``. +Agent Spec assumes that the list of available tools, including their description, parameters, etc. +is handled by the runtime implementation, that should inform the agent's LLM of their existence. + +Toolboxes, when present, augment the set of tools available to the Agent at runtime through +discovery/aggregation. + +When an Agent consumes multiple ToolBoxes and tools, runtimes MAY merge them into a single tool +registry for the Agent. In case of tool name collisions across different sources: + +- The specification recommends optional namespacing (e.g., toolbox_name.tool_name) and leaves + the exact behavior to the runtime. Runtimes MAY error on collisions. +- Implementations SHOULD provide clear diagnostics when collisions occur. + +Transforms, when present, apply transformations to the messages before they are passed to the agent's LLM, +allowing for message summarization, conversation summarization, or other modifications to optimize the agent's context. + +This Component will be expanded as more functionalities will be added to +Agent Spec (e.g., memory, planners, ...). + +Human-in-the-Loop in Agents +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The concept of human-in-the-loop in agents refers to integrating user +oversight or intervention points into automated processes. +When an agent is designed with a human in the loop, it pauses at defined +moments, such as (but not limited to) before taking key actions or making +critical decisions, to allow a human user to review, approve, or modify the outcome. +There are many possible ways to implement this concept, +such as (but not limited to) introducing special +tools, intercepting large language model (LLM) outputs, or pausing +execution before certain actions. The specific implementation details +are left to the runtime; the essential requirement is +that user control is provided at the appropriate time when requested by +the agent. In contrast, agents without human-in-the-loop capability +operate fully autonomously, without waiting for user input. + +Agent Specialization for Reusability and Flexibility +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +In complex automation and orchestration tasks, agent reusability is +vital for maintainability and scalability. The Open Agent Specification +introduces an extension mechanism where a base agent can be tailored for +specialized tasks, enhancing its core capabilities with refined +instructions and additional toolsets. This approach promotes building +simple, generic agents that can be flexibly adapted to diverse use cases +within a flow or as standalone agents. + +A ``SpecializedAgent`` is created using the ``AgentSpecializationParameters``. +The base ``agent``'s instructions are refined by merging them with the +``additional_instructions`` in the specialization parameters, and its tools +can be extended with the ``additional_tools``. Specialization can optionally +override the behaviour of the agent from human-in-the-loop to fully autonomous, +or vice versa (``human_in_the_loop=None`` in the specialization parameters +preserves the base agent's behavior). + +.. code-block:: python + + class AgentSpecializationParameters(ComponentWithIO): + additional_instructions: Optional[str] = None + additional_tools: Optional[List[Tool]] = None + human_in_the_loop: Optional[bool] = None + + class SpecializedAgent(AgenticComponent): + agent: Agent + agent_specialization_parameters: AgentSpecializationParameters + + +``AgentSpecializationParameters`` can use placeholders (as introduced in the previous sections) +in the ``additional_instructions``, which will define the inputs to the +``AgentSpecializationParameters`` component. +The inputs of the ``SpecializiedAgent`` will be the union of the specialization parameters' inputs +and the ones of the base agent. +If the same input or output property is defined in both the agent and the +specialization parameters, the respective instances must have the same type. +``AgentSpecializationParameters`` do not define any outputs, therefore the outptus of the +``SpecializiedAgent`` will be the same as the ones of the underlying base ``Agent``. + + +LLM +~~~ + +LLMs are used in agents, as well as flow nodes that need one. + +In order to make them usable, we need to let users specify all the +details needed to configure the LLM, like the connection details, the +generation parameters, etc. + +We define a Component called LlmConfig that contains all the details: + +.. code-block:: python + + class LlmConfig(Component): + model_id: str + provider: Optional[str] + api_provider: Optional[str] + api_type: Optional[str] + url: Optional[str] + api_key: SensitiveField[Optional[str]] + default_generation_parameters: Optional[Dict[str, Any]] + retry_policy: Optional[RetryPolicy] + +The ``model_id`` field is required and identifies the model to use, as expected by the selected API provider. +The ``provider`` field is optional and identifies the model provider (e.g. ``"openai"``, ``"meta"``, ``"anthropic"``, ``"cohere"``). +The ``api_provider`` field is optional and identifies the API provider serving the model (e.g. ``"openai"``, ``"oci"``, ``"vllm"``, ``"ollama"``, ``"aws_bedrock"``, ``"vertex_ai"``). +The ``api_type`` field is optional and identifies the API format to use (e.g. ``"chat_completions"``, ``"responses"``). +The ``url`` field is optional and specifies the URL of the API endpoint (e.g. ``"https://api.openai.com/v1"``). If not specified, the default API URL of the API provider (if any) is used. +The ``api_key`` field is optional and specifies an API key for the remote LLM. When the configuration is exported, the value is replaced by a reference. +The ``default_generation_parameters`` field specifies the default generation parameters that should be used when prompting the LLM. +These parameters are specified as a dictionary of parameter names and respective values. +The names are strings, while values can be of any type compatible with the JSON schema standard. +Additionally, an optional Retry policy can be specified to configure the how remote LLM calls are retried +in case of network failures. + +Retry Policy +^^^^^^^^^^^^ + +Users can provide an optional retry policy configuration for remote LLM calls. +This policy is provider-agnostic and can be interpreted by runtimes to implement +consistent retry behavior across different LLM providers. + +.. code-block:: python + + class RetryPolicy: + max_attempts: int + request_timeout: Optional[float] + initial_retry_delay: float + max_retry_delay: float + backoff_factor: float + jitter: Optional[Literal["equal", "full", "full_and_equal_for_throttle", "decorrelated"]] + service_error_retry_on_any_5xx: bool + recoverable_statuses: Dict[str, List[str]] + + +* ``max_attempts``: maximum number of retries for a request that fails with a recoverable status. +* ``request_timeout``: timeout parameter to pass to underlying HTTP clients for slow endpoints such + as transcription or inference services, in seconds. +* ``initial_retry_delay``: minimum amount of time to wait between retries in seconds. +* ``max_retry_delay``: maximum amount of time to wait between retries in seconds. +* ``backoff_factor``: for exponential backoff with jitter, the exponent which we will raise to the power + of the number of attempts. +* ``jitter``: type of backoff we want to do. Defaults to ``"full_and_equal_for_throttle"``. + + * ``None``: No jitter. ``t = min(min_wait * (backoff_factor ** attempts), max_wait)`` + * ``"full"``: ``t = min(random(0, min_wait * (backoff_factor ** attempts)), max_wait)`` + * ``"equal"``: ``t = min(min_wait * (backoff_factor ** attempts), max_wait) * (1 + random(0, 1)) / 2`` + * ``"full_and_equal_for_throttle"``: full for 5xx errors and equal for 4xx errors + * ``"decorrelated"``: ``t = min(min_wait * (backoff_factor ** attempts) + random(0, 1), max_wait)`` + +* ``service_error_retry_on_any_5xx``: whether to retry on all 5xx errors, except 501 (network errors). +* ``recoverable_statuses``: configure what HTTP statuses (e.g. 429) to retry on and, optionally, whether + the textual code (e.g. TooManyRequests) matches a given value. The expected format is a dictionary where + the key is a string representing the HTTP status, and the value is a ``List[str]`` where we will test + if the textual code in the service error is a member of the list. If an empty list is provided, then + only the numeric status is checked for retry purposes. + +When all retries fail, an error is raised and the execution is interrupted. + +.. note:: + Runtimes MUST NOT treat TLS/certificate verification errors as retryable. + Runtimes SHOULD NOT retry on authentication/authorization errors (e.g., HTTP 401/403) + or validation errors (e.g., HTTP 400/422). + +.. note:: + + The extra parameters should never include sensitive information. + Make sure the extra parameter values are validated either by the runtime if supported, + or explicitly before importing the configuration into the runtime. + +Null value is equivalent to an empty dictionary, i.e., no default generation parameter is specified. + +``LlmConfig`` can be used directly for any LLM provider by setting the appropriate ``provider``, ``api_provider``, +and ``api_type`` values. Specific extensions of ``LlmConfig`` for the most common providers are also provided +for convenience, offering additional provider-specific configuration options. + +DBMS Vector Chain +^^^^^^^^^^^^^^^^^ + +``DbmsVectorChainLlmConfig`` configures an LLM request executed by Oracle +Database through ``DBMS_VECTOR_CHAIN``. + +.. code-block:: python + + class DbmsVectorChainLlmConfig(LlmConfig): + model_id: str + provider: str + url: str + credential_name: Optional[str] + host: Optional[Literal["local"]] + transfer_timeout: Optional[int] + connection_config: Optional[OracleDatabaseConnectionConfig] + +The ``connection_config`` field is an optional Oracle Database connection +configuration. If ``connection_config`` is not specified, the runtime must use +an existing Oracle Database connection from its execution context. If no +connection is available, the runtime must raise an error. + +The ``host`` field can only be set to ``"local"`` and is supported only when +``provider`` is ``"openai"`` or ``"ollama"``. It indicates that the provider +is running locally and disables database credential authentication. When +``host`` is specified, ``credential_name`` must be omitted. + +The ``transfer_timeout`` field specifies the maximum number of seconds to wait +for the provider request to complete. If it is not specified, +``DBMS_VECTOR_CHAIN`` uses its default timeout of 60 seconds. + +The runtime maps these fields to the corresponding ``DBMS_VECTOR_CHAIN`` +parameters: ``model_id`` to ``model``, and ``provider``, ``url``, +``credential_name``, ``host``, and ``transfer_timeout`` to parameters with the +same names. ``connection_config`` configures the runtime's database connection +and is not a ``DBMS_VECTOR_CHAIN`` parameter. + +For a remote provider, ``credential_name`` identifies a credential created in +Oracle Database with ``DBMS_VECTOR_CHAIN.CREATE_CREDENTIAL``. For a local +OpenAI or Ollama provider, set ``host`` to ``"local"`` and omit +``credential_name``. Refer to the `DBMS_VECTOR_CHAIN documentation `_ +for supported providers and provider-specific parameters. + +Structured Generation +^^^^^^^^^^^^^^^^^^^^^ + +Structured generation in LLMs refers to the process of producing outputs that adhere to specific, +predefined formats or structures. This approach is valuable for use cases requiring machine-readable responses, +data extraction, program synthesis, or integrating LLM outputs with downstream systems. + +Structured generation in LLMs typically works by guiding the model through tailored prompts or instructions +that specify the required format. +In Agent Spec we allow users to define the expected output format by defining output properties in Components. +In other words, users must define the JSON schema of the different properties that the LLM is supposed to generate. +Each component defines if structured generation is supported, and how it is enabled. + +In the current version of Agent Spec, the components that support structured generation are: + +- LlmNode +- Agent + +More information is provided in their respective definitions. + +.. note:: + Note that values, despite well-structured, will be LLM-generated. + Therefore users should consider them untrusted, and they should perform proper validation before usage. + +Agent Spec Runtimes can implement structured generation by generating all the properties in the same request, +but they should expose them as separate outputs. +For example, assuming to have the following component that supports structured generation: + +.. code-block:: json5 + + { + "component_type": "ComponentWithStructuredGeneration", + // ... + "llm_config": { + // ... + }, + "prompt": "What is the fastest italian car?", + "outputs": [ + {"title": "brand", "type": "string", "description": "The brand of the car"}, + {"title": "model", "type": "string", "description": "The name of the car's model"}, + {"title": "hp", "type": "integer", "description": "The horsepower amount, which expresses the car's power"} + ] + } + +Can generate an object as follows: + +.. code-block:: json + + {"brand": "Pininfarina", "model": "Battista", "hp": 1400} + +But it should then expose each property separately. Note that the descriptions of properties could be forwarded +to LLMs to improve the quality of the generation. +Therefore, providing a proper description might improve the quality of the final outcome. + +.. note:: + The current version Agent Spec does not define how structured generation is enforced on the LLM. + In case structured generation is requested to an LLM that does not natively support it, it's up to + the Agent Spec Runtime implementation to raise an exception, or to implement it in a different form. + +.. _openaicompatiblellms: + +OpenAI Compatible LLMs +^^^^^^^^^^^^^^^^^^^^^^ + +This class of LLMs groups all the LLMs that are compatible with either the +`OpenAI chat completions APIs `_ or the `OpenAI Responses APIs `_. +The API type can be configured by using the ``api_type`` parameter, which takes one of 2 string values, namely +``chat_completions`` or ``responses``. By default, the API type is set to chat completions. Additionally, an optional +``api_key`` can be set for the remote LLM model. OpenAI-compatible LLMs also support optional certificate +configuration for HTTPS and mTLS connections to private endpoints. + +.. code-block:: python + + class OpenAiCompatibleConfig(LlmConfig): + model_id: str + url: str + api_type: Literal["chat_completions", "responses"] = "chat_completions" + api_key: SensitiveField[Optional[str]] = None + key_file: SensitiveField[Optional[str]] = None + cert_file: SensitiveField[Optional[str]] = None + ca_file: SensitiveField[Optional[str]] = None + +* ``key_file`` is the path to an optional client private key file (PEM format). +* ``cert_file`` is the path to an optional client certificate chain file (PEM format). +* ``ca_file`` is the path to an optional trusted CA certificate file (PEM format) used to verify the server. + +Based on this class of LLMs, we provide two main implementations. + +vLLM +'''' + +Used to indicate all the LLMs deployed through `vLLM `_. + +.. code-block:: python + + class VllmConfig(OpenAiCompatibleConfig): + pass + +Ollama +'''''' + +Used to indicate all the LLMs deployed through `Ollama `_. +Note that as of November 2025, Ollama does not support the `OpenAI Responses APIs `_, +so using this API type may lead to unexpected behavior. + +.. code-block:: python + + class OllamaConfig(OpenAiCompatibleConfig): + pass + + +OpenAI +^^^^^^ + +This class of LLMs refers to the models offered by `OpenAI `_. +Similar to :ref:`OpenAI Compatible LLMs `, you can also configure the ``api_type`` parameter, +which takes one of 2 string values, namely ``chat_completions`` or ``responses``. +By default, the API type is set to chat completions. Additionally, an optional +``api_key`` can be set for the remote LLM model. + +.. code-block:: python + + class OpenAiConfig(LlmConfig): + model_id: str + api_type: Literal["chat_completions", "responses"] = "chat_completions" + api_key: SensitiveField[Optional[str]] = None + +OCI GenAI +^^^^^^^^^ + +This class of LLMs refers to the models offered by +`Oracle GenAI `_. + +OCI models require to specify the authentication method to be used when accessing the models. +Therefore, besides the attributes required to select the model to use, the ``OciGenAiConfig`` contains +the authentication information as part of the ``LlmConfig``. + +The ``serving_mode`` parameter determines whether the model is served on-demand or in a dedicated capacity. +On-demand serving is suitable for variable workloads, while dedicated serving provides consistent performance +for high-throughput applications. + +The ``provider`` parameter specifies the underlying model provider, META for Llama models or COHERE for cohere +models. When ``None``, it is automatically detected based on the ``model_id``. When the ``serving_mode`` is ``DEDICATED`` +the ``model_id`` does not indicate what ``provider`` should be used so the user has to specify it manually, +knowing what model it is. + +The API type can be configured by using the ``api_type`` parameter, which takes one of 3 string values, namely ``oci``, +``openai_chat_completions`` and ``openai_responses``. By default, the API type is set to ``oci``. +The ``conversation_store_id`` parameter allows to specify the conversation store OCI resource to use top persist +conversations when using the openai responses API. + +.. code-block:: python + + class OciGenAiConfig(LlmConfig): + model_id: str + compartment_id: str + serving_mode: Literal["ON_DEMAND", "DEDICATED"] = "ON_DEMAND" + provider: Optional[Literal["META", "XAI", "GROK", "COHERE", "OTHER"]] = None + client_config: OciClientConfig + api_type: Literal["chat_completions", "responses"] = "chat_completions" + conversation_store_id: Optional[str] = None + +.. note:: + While both ``GROK`` and ``XAI`` values for ``provider`` are supported and refer to xAI models, we recommend + to use the value ``XAI``. + +.. note:: + The authentication components must not contain any sensitive information about the authentication, + like secrets, API keys, passwords, etc. Users must not include this information anywhere, as exported + Agent Spec configurations should never include sensitive data. + +OCI Client Configuration +'''''''''''''''''''''''' + +The ``OciClientConfig`` contains all the settings needed to perform the authentication to use OCI services. +This object is used by the ``OciGenAiConfig`` in order to access the Gen AI services of OCI and perform LLM calls. +This client configuration is also used by other components that require access to the OCI services (e.g., the +``OciAgent`` presented later in this document). +More information about how to perform authentication in OCI is available on the +`Oracle website `_. + +.. code-block:: python + + class OciClientConfig(Component): + service_endpoint: str + auth_type: Literal["SECURITY_TOKEN", "INSTANCE_PRINCIPAL", "RESOURCE_PRINCIPAL", "API_KEY"] + +Based on the type of authentication the user wants to adopt, different specifications of the ``OciClientConfig`` +are defined. In the following sections we show what client extensions are available and their specific parameters. + + +OciClientConfigWithSecurityToken +'''''''''''''''''''''''''''''''' + +Client configuration that should be used if users want to use authentication through security token. + +.. code-block:: python + + class OciClientConfigWithSecurityToken(OciClientConfig): + auth_profile: str + auth_file_location: SensitiveField[str] + auth_type: Literal["SECURITY_TOKEN"] = "SECURITY_TOKEN" + +OciClientConfigWithApiKey +''''''''''''''''''''''''' + +Client configuration that should be used if users want to use authentication with API key. + +.. code-block:: python + + class OciClientConfigWithApiKey(OciClientConfig): + auth_profile: str + auth_file_location: SensitiveField[str] + auth_type: Literal["API_KEY"] = "API_KEY" + +OciClientConfigWithInstancePrincipal +'''''''''''''''''''''''''''''''''''' + +Client configuration that should be used if users want to use instance principal authentication. + +.. code-block:: python + + class OciClientConfigWithInstancePrincipal(OciClientConfig): + auth_type: Literal["INSTANCE_PRINCIPAL"] = "INSTANCE_PRINCIPAL" + +OciClientConfigWithResourcePrincipal +'''''''''''''''''''''''''''''''''''' + +Client configuration that should be used if users want to use resource principal authentication. + +.. code-block:: python + + class OciClientConfigWithResourcePrincipal(OciClientConfig): + auth_type: Literal["RESOURCE_PRINCIPAL"] = "RESOURCE_PRINCIPAL" + +Gemini +^^^^^^ + +This class of LLMs refers to the Gemini family of models offered by `Google `_. +Google offers Gemini through two services: `AI Studio `_ and `Vertex AI `_. + +.. code-block:: python + + class GeminiConfig(LlmConfig): + model_id: str + auth: SerializeAsAny[GeminiAuthConfig] + +.. code-block:: python + + class GeminiAuthConfig(Component, abstract=True): + pass + +AI Studio uses API key-based authentication: + +.. code-block:: python + + class GeminiAIStudioAuthConfig(GeminiAuthConfig): + api_key: SensitiveField[Optional[str]] = None + +When ``api_key`` is not specified, runtimes may try to load it from the ``GEMINI_API_KEY`` +environment variable. + +Meanwhile, the Vertex AI service can be authenticated with Google Cloud credentials. These credentials can be provided with a `service account JSON key `_ +either inline or through a local file path. When omitted, runtimes may rely on Google Application Default Credentials (ADC), such as +the ``GOOGLE_APPLICATION_CREDENTIALS`` environment variable, credentials made available through the local Google Cloud environment, +or an attached service account. Even with ADC, the ``project_id`` may still need to be provided explicitly when it cannot be +resolved from the local Google Cloud configuration: + +.. code-block:: python + + class GeminiVertexAIAuthConfig(GeminiAuthConfig): + project_id: Optional[str] = None + location: str = "global" + credentials: SensitiveField[Optional[Union[str, Dict[str, Any]]]] = None + +Here, ``credentials`` accepts either a local file path (``str``) to a Google Cloud JSON +credential file, such as a service-account key file, or an inline ``dict`` containing the +parsed JSON contents of that file. + +See `Using Gemini API keys `_, +`Application Default Credentials `_, +and `Create and delete service account keys `_ +for more details. +``GeminiConfig.auth`` stays inline when serialized. If ``api_key`` or ``credentials`` of the auth object is +specified, only that sensitive field is externalized. Otherwise, if ``api_key`` or ``credentials`` is +unset, that field serializes as ``null``. + +Tools +~~~~~ + +A tool is a procedural function or a flow that can be made available to +an Agent to execute. In a flexible Agent context, the Agent can decide +to call a tool based on its signature and description. In a flow +context, a node would need to be configured to call a specific tool. + +Where the actual tool functionality is actually executed or run depends +on the type of the tool: + +- **ServerTools** are executed in the same runtime environment that the + Agent is being executed in. The definitions of these tools therefore + must be available to the Agent's environment. It is expected that + this type of tool will be very limited in number, and relatively + general in functionality (e.g., human-in-the-loop). +- **BuiltinTools** are defined and implemented by the execution engine. + Which built-in tools are available, their semantics and how to configure + them depends on, and should be verified by, the execution engine. + Built-in tools are expected to be executed in the same runtime + environment that the Agent is being executed in. +- **ClientTools** are not executed by the executor, the client must + execute the tool and provide the results back to the executor (similar + to OpenAI's function calling model) +- **RemoteTools** are run in an external environment, but triggered by + an RPC or REST call from the Agent executor. +- **MCPTools** interact with Model Context Protocol (MCP) servers to + support remote tool execution. + +Agent Spec is not supposed to provide an implementation of how these types of +tools should work, but provide their representation, so that they can be +correctly interpreted and ported across platforms and languages. + +That said, as previously argued, a tool is a function that is called +providing some parameters (i.e., inputs), performs some transformation, +and returns some values (i.e, outputs). Of course the function has a +name, and a description that can be used by an LLM in order to +understand when it's relevant to perform a task. Therefore, we can +define a Tool as a simple extension of ComponentWithIO. + +We let tools specify **multiple outputs**. In this case, the expected return value +of the tool is a dictionary where each entry corresponds to an element +specified in the outputs. The key of the dictionary entry must match the +name of the property specified in the outputs. The runtime will parse the +dictionary in order to extract the different outputs and bind them correctly. + +Tools also include a boolean ``requires_confirmation`` attribute. +When set to true, this signals that execution environments should require +user/operator approval before running the tool, which is especially relevant +for tools performing critical or sensitive actions. + +While ServerTool and ClientTool do not require specific additional +parameters, the RemoteTool, MCPTool and the BuiltinTool require to include also +the details needed to perform the remote call to the tool or to identify +the correct built-in tool. + +.. code-block:: python + + class Tool(ComponentWithIO): + # Flag to make tool require user confirmation before execution. + requires_confirmation: bool + + class ClientTool(Tool): + pass + + class ServerTool(Tool): + pass + + class RemoteTool(Tool): + # Basic parameters needed for a remote API call + url: str + http_method: str + api_spec_uri: Optional[str] + data: Any + query_params: Dict[str, Any] + headers: Dict[str, Any] + sensitive_headers: SensitiveField[Dict[str, Any]] + url_allow_list: Optional[List[str]] + retry_policy: Optional[RetryPolicy] + + class MCPTool(Tool): + client_transport: ClientTransport + retry_policy: Optional[RetryPolicy] + + class BuiltinTool(Tool): + tool_type: str + configuration: Optional[Dict[str, Any]] + executor_name: Optional[Union[str, List[str]]] + tool_version: Optional[str] + +An important security aspect of Agent Spec is the exclusion of arbitrary +code from the agent representation. The representation contains a complete description +of the tool—its attributes, inputs, outputs, and related metadata—but does +not embed executable code. + +.. _remotetools: + +Remote Tools +^^^^^^^^^^^^ + +One common way to make tools available to agents, is to make them +available under the for of REST APIs that the agent can call when the +tool has to be executed. + +Compared to ServerTools and ClientTools, these tools require more +information to be specified, in order to perform the actual request. + +We add the same parameters as the APINode (see the Nodes library in the +Flows - Nodes section below), in order to perform a complete REST call +in the right manner. + +Note that, similarly to the APINode, we allow users to use placeholders +(as described in section 5.3.4) in all the string attributes of the +RemoteTool (i.e., url, http_method, api_spec_uri, and in the dict values +of data, query_params, headers, and sensitive_headers). The ``data`` parameter in +``RemoteTool`` is what ends up in the body of the request. +If ``data`` is specified as a string, the runtime must encode and pass it as the body of the HTTP request, +otherwise the value of ``data`` should be JSON serializable and runtimes are +expected to dump it when adding it in the body of the HTTP request. +The data is passed to the request as Form Data if the header ``{"Content-Type": "application/x-www-form-urlencoded"}`` is specified. + +The ``url_allow_list`` field is an optional list of developer-controlled URLs or URL patterns that runtimes +or adapters can use to validate the rendered request URL. The intended matching behavior is exact matching on +scheme and authority (host and port), together with prefix matching on the path. Query parameters, URL params, +and fragments are not used for matching. + +These patterns are simple URL prefixes rather than wildcards or regexes. For example: + +* ``https://example.com`` allows ``https://example.com/page`` because scheme, host, and port match and the path prefix is ``/``. +* ``https://example.com/orders/`` allows ``https://example.com/orders/123`` and ``https://example.com/orders/123/items``. +* ``https://example.com/orders/`` does not allow ``https://example.com/customers/123`` because the path prefix differs. +* ``http://example.com/orders/`` does not allow ``https://example.com/orders/123`` because the scheme differs. + +For higher security, authors should strongly prefer configuring ``url_allow_list`` whenever templating is used in +``url``, especially if placeholders can affect the destination part of the URL (scheme, host, or port). +However, Agent Spec does not require runtime adapters to enforce this rule: whether to accept +or reject ``url`` containing placeholders without an explicit ``url_allow_list`` is left to runtime implementations. +The recommended pattern is to keep the base URL developer-controlled and template only path, query, or body values. + +MCP Tools +^^^^^^^^^ + +MCP Tools are a specialized type of tool that connects to servers implementing +the Model Context Protocol (MCP). They extend the base ``Tool`` with transport +configurations for secure, remote execution. + +Like other tools, MCP Tools define inputs, outputs, and metadata but include a +``client_transport`` for managing connections (e.g., via SSE or Streamable HTTP +with mTLS) (details about the client transport can be found in +:ref:`the MCP section ` below). + + +.. code-block:: python + + class MCPTool(Tool): + client_transport: ClientTransport + retry_policy: Optional[RetryPolicy] + +MCP Tools follow the same security principles as other tools: no arbitrary code is +embedded in the representation. Execution occurs remotely via the specified transport, with the +runtime handling session management and data relay. + +``retry_policy`` optionally specifies a ``RetryPolicy`` for semantic MCP tool +resolution and execution retries. For this use, runtimes should use only the +attempt and backoff fields: ``max_attempts``, ``initial_retry_delay``, +``max_retry_delay``, ``backoff_factor``, and ``jitter``. + +Resolution means locating and validating the configured MCP tool against the +tools currently exposed by the MCP server before the tool is called. Execution +means invoking that selected MCP tool after resolution has succeeded. + +For direct tool resolution, the policy should apply when the configured tool +name is missing from a successful MCP tool-list or resolution result. For +execution, the policy should apply when the MCP server reports that the selected +tool is temporarily unavailable or missing, for example a tool-not-found error +for a tool that was configured or previously exposed. + +This is distinct from retry policies on remote MCP transports, which apply to +requests sent through the transport layer. The ``request_timeout``, +``service_error_retry_on_any_5xx``, and ``recoverable_statuses`` fields apply to +transport/request retry policies, not to MCP semantic retry. Runtimes should not +apply MCP semantic retry to invalid configuration, authentication or +authorization failures, TLS/certificate verification failures, validation +failures, or signature/schema mismatches. + +For example, an MCP Tool might use an ``SSEmTLSTransport`` to securely call a remote +function for data processing. + + +ToolBoxes +^^^^^^^^^ + +A ToolBox is a component that exposes one or more tools to components. ToolBoxes are not +executable by themselves; they are discovery/aggregation mechanisms. A component can +receive tools from both: + +- tools (concrete Tool instances), and +- toolboxes (ToolBox instances that expose tools discovered elsewhere). + +When the set of tools is dynamic, its content should be established just before use by the +consuming component. ToolBoxes do not embed executable code or the full discovered tool list +at serialization time. Discovery happens at runtime. + +A ToolBox also exposes a flag ``requires_confirmation`` (default to False); it exists to let users enforce +confirmation for the entire toolbox without having to set ``requires_confirmation=True`` on every +tool it provides. If the tool box does not require confirmation, confirmation is still required +for individual tools which explicitly specify it. + +.. code-block:: python + + class ToolBox(Component): + requires_confirmation: bool = False + + +MCPToolBox +'''''''''' + +MCPToolBox connects to an MCP server via a ClientTransport and exposes tools discovered +from that server to components. + + +.. code-block:: python + + class MCPToolBox(ToolBox): + client_transport: ClientTransport + retry_policy: Optional[RetryPolicy] + tool_filter: Optional[List[Union[str, MCPToolSpec]]] + + +The ``client_transport`` specifies the MCP ClientTransport used to discover remote +MCP tools and route calls to them. + +``retry_policy`` optionally specifies a ``RetryPolicy`` for semantic MCP toolbox +discovery/resolution retries and for execution retries of tools generated by the +toolbox. For this use, runtimes should use only the attempt and backoff fields: +``max_attempts``, ``initial_retry_delay``, ``max_retry_delay``, +``backoff_factor``, and ``jitter``. + +Discovery/resolution means listing the tools currently exposed by the MCP server +and matching them against ``tool_filter`` before the toolbox provides tools to a +consuming component. Execution means invoking a selected tool generated by the +toolbox after discovery/resolution has succeeded. + +For toolbox discovery/resolution, the policy should apply only when +``tool_filter`` is configured and one or more expected tools are missing from a +successful MCP ``list_tools`` response. When ``tool_filter`` is null, the +runtime cannot know whether a successful response is incomplete, so this +semantic retry does not apply to discovery. For execution of tools generated by +the toolbox, the policy should apply when the MCP server reports that the +selected tool is temporarily unavailable or missing. + +This is distinct from retry policies on remote MCP transports, which apply to +requests sent through the transport layer. The ``request_timeout``, +``service_error_retry_on_any_5xx``, and ``recoverable_statuses`` fields apply to +transport/request retry policies, not to MCP semantic retry. Runtimes should not +apply MCP semantic retry to invalid configuration, authentication or +authorization failures, TLS/certificate verification failures, validation +failures, or signature/schema mismatches. + +.. _mcp_toolfilter_rules: + +By default the ``tool_filter`` parameter is null and the MCPToolBox exposes all tools +discovered from the MCP server. When the ``tool_filter`` is not null, the toolbox only +exposes the listed tools. For each entry in the list: + +- If an entry is a ``str``, the MCP server MUST expose a tool with the exact name, or the configuration is invalid. +- If an entry is an ``MCPToolSpec``, it is treated as a strict signature to validate against the tool exposed by the MCP server: + + - The name MUST exactly match a name of an exposed tool. + - If description is provided, it overrides the exposed tool description. + - If inputs are provided, they MUST exactly match the exposed tool input schema by name and JSON Schema type. + On mismatch, the configuration is invalid. + - If outputs are provided, they MUST consist of exactly one string-typed property with the expected tool output + name and optional description. + - If the :ref:`tool confirmation flag ` is provided, its value is used; otherwise, it defaults to False. + +The ``tool_filter`` information should be validated against the exposed MCP tools each time the tools +are provided to the consuming component (as the toolbox content may change dynamically). If any of the +constraints described above is not respected, the runtime can raise an error. + +.. note:: + + ToolBoxes are not valid values for ToolNode.tool; + ToolNode requires a concrete Tool (including MCPTool). + + +MCPToolSpec +''''''''''' + +``MCPToolSpec`` is a declarative tool signature used inside ``MCPToolBox.tool_filter`` to +pin and validate specific remote MCP tools. + +.. _mcp_tool_spec_def: +.. code-block:: python + + class MCPToolSpec(ComponentWithIO): + requires_confirmation: bool + +See the :ref:`filter rules for the MCPToolBox ` to see how the ``MCPToolSpec`` +is used. + +Built-in Tools +^^^^^^^^^^^^^^ + +Agent Spec execution engines may offer built-in tools out of the box and ready to use. +For example, + +- An executor could provide a tool to search websites. + Users do not need to provide a tool implementation, but only need to specify which website to search on. +- An executor running in a database might expose a native vector retrieval tool + with the possibility to configure which tables to search on or how many relevant results to return. + +Built-in tools in Agent Spec let users configure these types of tools. + +.. code-block:: python + + class BuiltinTool(Tool): + tool_type: str + configuration: Optional[Dict[str, Any]] + executor_name: Optional[Union[str, List[str]]] + tool_version: Optional[str] + +- ``tool_type`` identifies the runtime-provided built-in tool. +- ``configuration`` allows to configure the tool. + Legal configurations should be documented by the runtime providing the tool. + Not all built-in tools require configuration. +- ``executor_name`` optionally allows to specify the runtime name providing the tool for validation purposes + (i.e., for the runtime to check that the tool config is intended for it). + The correct value to put should be documented by the runtime providing the tool. +- ``tool_version``: if the runtime provides multiple versions of the tool, the built-in tool version can be specified here. + +The advantage of built-in tools is that no tool implementation needs to be provided by the users. +The disadvantage is that built-in tools are specific to the Agent Spec execution runtime which provides them. +In general, which built-in tools are available differs for each execution runtime, and using them might limit cross-framework portability. + + +Implementing Built-in Tools as Execution Engine Developer +''''''''''''''''''''''''''''''''''''''''''''''''''''''''' + +For Agent Spec execution engine developers implementing built-in tools, +the following documentation should be provided to users. + +- The semantics of tool +- The name of the tool, passed to the ``tool_type`` field +- The expected input and output properties (title and type), if any +- The expected static configuration parameters, if any +- The executor name. Users can optionally specify this field s.t. + the execution engine can validate the tool is intended for it. +- The tool version. If there are multiple versions of the tool, + this field can be used to select which tool to run. Furthermore, it should + be documented what the default behavior is if multiple tool versions are + available but no ``tool_version`` is specified. + +Execution engine developers should validate that inputs, outputs and configuration +parameters of the built-in tool component provided in the configuration are as expected. + +Furthermore, to prevent unintended behavior, execution engine developers should +validate the ``executor_name`` and ``tool_version`` fields, if they are provided, +and raise an error if the values are not expected. + +.. collapse:: Example documentation for built-in tool... + + **Vector Search Built-in Tool** + + Retrieves the most relevant documents from a table based on cosine similarity + between a query text and stored embeddings. + + **Tool Name:** "vector_retrieval_tool" + + **Inputs:** + + - ``StringProperty(title="query")``: user query text. + + **Outputs:** + + - ``ListProperty(title="results", item_type=StringProperty())``: + list of retrieved document contents. + + **Configuration Parameters:** + + - ``schema`` (str): schema containing the table. + - ``target_table`` (str): name of the table storing embeddings. + - ``target_column`` (str): column containing embedding vectors. + - ``top_k`` (int): number of nearest results to return. + + **Executor Name:** "my_execution_engine>=25.4.1". + + **Tool Version:** Our vector_retrieval_tool is not versioned. + + +Execution flows +~~~~~~~~~~~~~~~ + +Execution flows (or graphs) are directed, potentially-cyclic workflows. +They can be thought of as "subroutines" that encapsulate consistently-repeatable processes. + +Each execution flow requires 0 or more inputs (e.g., any input edges expressed on the +starting node of the graph) and may produce 0 or more outputs (e.g., any +output edges expressed by the terminal nodes of the graph - note that a +graph can have more than one terminal node, in the event of branching logic). + +Flow +^^^^ + +``Flow`` objects specify the entry point to the graph, the nodes, and edges for the graph. +Inner Components of the flow are described in subsequent sections. + +.. code-block:: python + + class Flow(ComponentWithIO): + start_node: Node + nodes: List[Node] + control_flow_connections: List[ControlFlowEdge] + data_flow_connections: Optional[List[DataFlowEdge]] + +A flow exposes a (sub)set of the union of all the outputs exposed by all the EndNodes. +However, if an output defined in the flow does not have a corresponding output with the same name in every EndNode, +then a default value for that output in the flow definition must be specified. +The default value is used in case the EndNode executed at runtime does not expose an output with that name. +It is not possible to define two outputs with the same name and different types in two distinct EndNodes of the same flow. +It is instead possible to define different default values for outputs with the same name in distinct EndNodes. + +For example, let's assume to have a flow with two EndNodes, called respectively ``end_node_a`` and ``end_node_b``. +``end_node_a`` exposes one output called ``output_a``, while ``end_node_b`` exposes another output called ``output_b``. +We want to expose both the outputs from the flow, so we will include them in the ``outputs`` definition of the flow +and, since they are not common to all the EndNodes in the flow, we must define a default value for them. +We assign ``default_value_a`` as default for ``output_a`` and ``default_value_b`` as default for ``output_b``. +Note that these default values are required to be included in the flow specification, not in the EndNodes. +Let's now assume that at runtime we execute the flow, and we complete it through the ``end_node_a`` with a value +for ``output_a`` set to ``value_a``. This means that as output values of the flow we will get ``value_a`` for +``output_a``, as it was generated by the EndNode that was executed, while we will get ``default_value_b`` for ``output_b``, +as no value was generated for ``output_b`` by the EndNode being executed. + +As inputs, a flow must expose all the inputs that are defined in its StartNode. +Note that flows must have a unique StartNode, which is defined by the ``start_node`` parameter, +and it should appear in the list of ``nodes``. + +Conversation +^^^^^^^^^^^^ + +At the core of the execution of a conversational agent there's the +conversation itself. The conversation is implicitly passed to all the +components throughout the flow. It contains the messages being produced +by the different nodes: each node in a flow and each agent can append +messages to the conversation if needed. Messages should have, at least, +a type (e.g., system, agent, user), content, the sender, and the recipient. + +Sub-flows and sub-agents contained in a Flow (i.e., as part of AgentNode and FlowNode, +see the Node section for more information) share the same conversation of the Flow they belong to. +Sub-agents and sub-flows should have access to all the messages in the conversation, +and they should append in the same conversation all the messages that are generated during +their execution. At the end of the execution of a sub-flow/sub-agent, the Flow that +contains them should have access to all the messages generated before and during the execution +of the sub-flow/sub-agent. + +.. warning:: + The conversation of the parent Flow is shared with sub-flows and sub-agents and vice-versa. + Do not use sub-flows or sub-agents for information isolation purposes. + For example, if a sub-agent calls a remote model, it may forward the entire conversation to that model. + +I/O Data Space +^^^^^^^^^^^^^^ + +Alongside the conversation, flows have another data space composed of inputs and outputs generated by nodes. + +Depending to their definition, nodes can require inputs and generate outputs. +When a node generates an output, this is exposed by the node itself, so that other nodes can consume it if needed. +In order to use an output's value as a component's input, there are two options: + +- A data flow edge between the output and the input is created (see :ref:`Data relationships `); +- In case the data flow is not defined (see :ref:`Optionality of data relationships `), + the input and output property names match. + +An output must be generated before it can be used to fulfil an input. +In other words, the node generating an output must be executed before the node consuming it as input. +An exception is made if the input property has a default value defined. +In that case, if the respective output was not generated, the default value is used instead. + +.. note:: + The Conversation and the I/O Data Space concepts are disjoint and independent from each other. + Agent Spec Runtimes are free to implement them separately or not (e.g., with the conversation + as a special entry in the I/O space), as long as the Agent Spec language semantic is respected. + +Node +^^^^ + +A ``Node`` is a vertex in an execution flow. + +.. code-block:: python + + class Node(ComponentWithIO): + branches: List[str] + +Being an extension of ComponentWithIO, the Node already has the +attributes of Component (id, name, ...) and ComponentWithIO (inputs and +outputs). + +Additionally, the each Node class has an attribute called branches used +to define the names of the branches that can follow the current node. +This is used to support cases where branching in control flow is needed +(see the respective section in this page). For example: + +- In a flow, at some point we need to take different paths based on the + value of an input, or the result of a condition (e.g., the equivalent + of an in-else statement) +- A flow used in a subflow has multiple end nodes, that can be reached + as a result of an earlier branching, and that might result in + different follow-up operations, so the different outgoing branches + have to be exposed + +Most nodes that are part of the flow have at least one default branch, +used to define which is the following node to be executed, that we +define with the name ``next`` . Note that the branches attribute of the +Node is typically managed (i.e., automatically inferred) by the +implementation of the Node, but it will appear in the representation. + +Each type of node can also have other specific attributes that define +its configuration. They are defined in the table in this section +containing the list of node types available in Agent Spec. + +The list of inputs and outputs exposed by a Node is supposed to be +automatically (and dynamically) generated by the Node instance itself in +one of the SDKs. The user is still allowed to explicitly define them +when generating the representation from an SDK, but the set of inputs and/or outputs +manually specified must match with the expected one as per node's +generation (i.e., all the expected inputs and/or outputs must be present +in the list specified by the user). This allows user specifying better +the type of an input/output with respect to the one inferred +automatically by the node (e.g., specify that a value in a prompt +template is supposed to be an integer instead of a generic string). The +lists of inputs and outputs will always appear in the representation of every node. + +The standard library of available nodes is described in a separate +section later. + +Some nodes infer inputs from special parts of their configurations. For +example, some nodes (e.g., the LLMNode) extract inputs from a property +called ``prompt_template``. We let users define placeholders in the +configuration parts that allow that, by simply wrapping the name of the +property among a double set of curly brackets. The node will +automatically infer the inputs by interpreting those configurations, and +the actual value of the input/output will be replaced at execution time. + +For example, setting the prompt_template configuration of an LLMNode to +"You are a great assistant. Please help the user with this request: +{{request}}" will generate an input called "request" of type string. + +Relationships / Edges +^^^^^^^^^^^^^^^^^^^^^ + +For flows, Agent Spec defines separate relationships (edges) for both +properties and control flow. This allows Agent Spec to unambiguously handle +patterns such as conditional branches and loops, which are very +challenging to support with data-only flow control. + +Relations are only applicable for flows. All relations express a +transition from some source node to some target node. + +Control edges +''''''''''''' + +A control relationship defines a *potential* transition from a branch of +some node to another. The actual transition to be taken during a given +execution is defined by the implementation of a specific node. + +.. code-block:: python + + class ControlFlowEdge(Component): + from_node: Node + from_branch: Optional[str] + to_node: Node + +The from_branch attribute of a ControlFlowEdge is set to null by +default, which means that it will connect the default branch (i.e., +``next`` ) of the source node. + +Data relationships (I/O system components) +'''''''''''''''''''''''''''''''''''''''''' + +Considering the I/O system, a component id alone is not sufficient to +identify a data relationship. A component may take multiple input +parameters and/or produce multiple output values. We must be able to +determine what value or reference is mapped to each input, and where +each output is used. + +Sources (inputs) can be connected to outputs of another node, or to +static values. + +Destination (outputs) can be connected to inputs of another node, or +left unconnected. + +.. code-block:: python + + class DataFlowEdge(Component): + source_node: Node + source_output: str + destination_node: Node + destination_input: str + +Connecting multiple data edges +'''''''''''''''''''''''''''''' + +In the case of control flow, multi-connections from the same outgoing branch +of a node are not allowed (note that we do not enable parallelism through edges). +Edges define which are the allowed "next step" transitions, +so that it's clear that it's possible to execute one node after the execution of another. +For more information about scenarios with multiple outgoing branches (e.g., based on the value +of a condition), please refer to the section about Conditional branching. + +For what concerns the data flow, instead, it is sometimes necessary to connect two +different data outputs to the same input. Two simple examples follow: + +|image7| + +Therefore, we let users connect multiple data outputs to the same input. +If multiple outputs are connected to the same input, the last node that +was executed and that has an output connected to the input will have the priority. +In other words, the behavior is similar to having the input exposed as a public +variable, and every node that has an output connected to that input updates its value. + +Optionality of data relationships +''''''''''''''''''''''''''''''''' + +Some frameworks do not require to specify the data flow, as they assume +that all the properties are publicly exposed in a shared "variable +space" that any component can access, and the read/write system is +simply name-based. + +In practice, this means that: + +- when a component has an input with a specific name, it will look into + the public variable space to read the value of the variable with that + name +- when a component has an output with a specific name, it will write (or + overwrite if it already exists) into the public variable space the + variable with that name + +The main advantage of this approach is that users do not have to define +any data flow, making the building experience faster. + +We let users adopt this type of I/O system by setting the +``data_flow_connections`` parameter of the Flow to None. + +In this case, a name-based approach explained above will be adopted, which means that +if a component writes an output whose name matches a variable space entry, +the respective value in the variable space is overwritten. + +Note that this name-based approach can be expressed defining explicitly +Data Flow connections (but the opposite is not possible) by connecting +all the outputs to the inputs with the same name, following the control +flow connections in the flow in order to account for overwrites. + +.. collapse:: How to translate public values into data flow edges... + + As just affirmed, it's possible to transform the name-based approach to + the data flow one, by creating automatically the right set of + DataFlowEdges. + + As a basic approach, this translation can be done by simply connecting + all the outputs to all the inputs with the same name. The priority-based + solution for value updates explained in previous section 5.4.4.4.3 will + ensure the correct behavior of the data flow. + + In order to minimize the amount of connections created, it's possible to + also adopt different solutions that do a simulation of an execution by + following the control flow connections. SDKs are allowed to implement + one version of this smarter solution. + +Conditional branching +^^^^^^^^^^^^^^^^^^^^^ + +Being able to follow different paths (or branches) based on some +conditions is an important feature required to represent processes as +flows (e.g., flowcharts). + +Conditional branching in Agent Spec is supported through a special step called +BranchingNode (detailed later), a node that maps input values to +different branches through a key-value mapping. + +Nodes can have multiple outgoing branches, as previously mentioned in this page. +The ``branches`` attribute of the Node is automatically filled and managed +by the implementation of the Node, but it will appear in the representation. Both +``Node.branches`` and ``ControlFlowEdge.from_branch`` are set to null by +default, and that is the default behavior in case one single branch is +going out of a node (this is compatible with the definition of Node and +ControlFlowEdge in this page). If a value is specified for the branches +of the from_node of a ControlFlowEdge, then also from_branch must be set +to one of the values in branches. GUIs will show the different branches +as output flows of the node, so that each of them can be connected in a +1-to-1 manner with another node's flow input. + +.. _parallelization: + +Parallelization +^^^^^^^^^^^^^^^ + +Support for parallelism in Agent Spec is limited to specific nodes that explicitly claim to support it. +The boundaries of parallelism (i.e., fork and join) are well defined, and coincide with those of the node where +parallelism is allowed. + +.. note:: + + Agent Spec does not enforce a specific way to implement parallelism. Runtimes can implement parallelism with different + techniques (including, but not limited to, multi-processing and multi-threading), as long as they respect + Agent Spec language directives and :ref:`security guidelines `. + Developers creating Agent Spec configurations should not make any assumption on how parallelism is implemented. + +During parallel execution, the execution order among parallel flows is not guaranteed. +Therefore, no assumptions should be made about the order or timing of operations, nor their atomicity. + +Consequently, take special precautions in flows and nodes that are supposed to be executed in parallel: + +- Avoid placing interrupts or user input/output operations (including, but not limited to, message nodes, + agent invocations, client tools) within parallel branches, as this can result in unpredictable behavior. +- Do not perform write operations on shared stateful objects in parallel nodes to prevent race conditions or inconsistent states. +- Parallel blocks should ideally be limited to independent, stateless tasks. + Carefully review flows with parallelism to ensure they remain safe and predictable. + + +Standard library of nodes to use in flows +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Here's the list of nodes supported in Agent Spec: + +- LLMNode: uses a LLM to generate some text from a prompt +- APINode: performs an API call with the given configuration and inputs +- AgentNode: runs a multi-round conversation with an Agentic Component, used to + better structure agentic components and easily reuse them. +- FlowNode: runs a flow inline, used to better structure and easily + reuse Flows +- CatchExceptionNode: Used to catch exceptions that may be raised during the + execution of a given subflow. +- MapNode: performs a map-reduce operation on a given input collection, + applying a specified Flow to each element in the collection +- StartNode: entry point of a flow +- EndNode: exit point of a flow +- BranchingNode: allows conditional branching based on the value of an input +- ToolNode: executes a tool +- InputMessageNode: interrupts temporarily the execution to retrieve user input +- OutputMessageNode: appends an agent message to the conversation +- ParallelMapNode: performs a parallel map-reduce operation on a given input collection +- ParallelFlowNode: execute a list of subflows in parallel + + +A more detailed description of each node follows. + +.. list-table:: + :widths: 5 20 15 15 15 15 + :class: wideoutertable + :header-rows: 1 + + * - Name + - Description + - Parameters + - Input + - Output + - Outgoing branches + * - LlmNode + - * Uses a LLM to generate some text or a structured output from a prompt + * Configured with a prompt template, a LLM and optionally generation parameters (number of tokens, etc) + * Single round + - .. list-table:: + :header-rows: 1 + :widths: 20 35 15 15 15 + :class: mywideinnertable + + * - Name + - Description + - Type + - Mandatory + - Default + * - prompt_template + - Defines the prompt sent to the model. Allows placeholders, which can define inputs + - string + - Yes + - - + * - llm_configuration + - The LLM to use for this generation + - LlmConfig + - Yes + - - + + - One per variable in the prompt template + - Two alternatives are available: + + * A single string property, that represents the raw text generated by the LLM + * A non-string property, or multiple properties. In this case, structured generation is triggered, following the property schemas provided as outputs + + - One, the default next + * - ApiNode + - * Performs an API call with the given settings and inputs + * Provides the parts of the API call response as outputs + - .. list-table:: + :header-rows: 1 + :widths: 20 35 15 15 15 + :class: mywideinnertable + + * - Name + - Description + - Type + - Mandatory + - Default + * - url + - The URL to send the request. Allows placeholders, which can define inputs + - string + - Yes + - - + * - http_method + - The HTTP method. Allows placeholders, which can define inputs + - string + - Yes + - - + * - api_spec_uri + - The path to get the specification json. Allows placeholders, which can define inputs + - string | null + - No + - null + * - data + - The data to send to this API call. Behaves the same way as the ``data`` parameter in :ref:`RemoteTool `. + - any + - No + - {} + * - query_params + - Query parameters for the API call. Allows placeholders in dict values, which can define inputs + - object[str, any] + - No + - {} + * - headers + - Additional headers for the API call. Allows placeholders in dict values, which can define inputs + - object[str, any] + - No + - {} + * - sensitive_headers + - Additional headers for the API call. These additional headers are merged with the headers provided + as `headers` when executing the request, but these headers are excluded from exported configuration + thus they are better suited to contain sensitive information not intended to be shared as widely as + the exported configuration. + - object[str, any] + - No + - {} + * - url_allow_list + - Optional list of allowed URLs or URL patterns for the rendered request URL. The intended semantics are + the same as for ``RemoteTool.url_allow_list``. For example, ``https://example.com`` allows any path + on that origin, while ``https://example.com/orders/`` allows only the ``/orders/`` subtree. + - array[string] | null + - No + - null + * - retry_policy + - Optional retry policy configuration applied to this API call. + - RetryPolicy | null + - No + - null + + - Inferred from the json spec retrieved from API Spec URI, if available and reachable. + Empty otherwise (users will have to manually specify them) + - Inferred from the json spec retrieved from API Spec URI, if available and reachable. + Empty otherwise (users will have to manually specify them) + - One, the default next + * - AgentNode + - * Runs a conversation with an Agentic Component (potentially multi-round) + * The component is started giving the specified inputs + * The component provides the specified outputs + * By separating the component definition from the node executing it (this node), + we can handle cases where the same component (defined once) is executed in several places of a flow, + or by different flows (future versions) + * If the agentic component is a flow, the flow is ran in a different conversation (isolated) + - .. list-table:: + :header-rows: 1 + :widths: 20 35 15 15 15 + :class: mywideinnertable + + * - Name + - Description + - Type + - Mandatory + - Default + * - agent + - The agent to be executed + - Agent + - Yes + - - + + - The ones defined by the Agent + - The ones defined by the Agent + - One, the default next + * - FlowNode + - * Runs a Flow + * Used to better structure agents and easily reuse flows across them + * The flow is started giving the specified inputs + * The flow provides the specified outputs + * The flow is run as it was inlined with the overall flow + - .. list-table:: + :header-rows: 1 + :widths: 20 35 15 15 15 + :class: mywideinnertable + + * - Name + - Description + - Type + - Mandatory + - Default + * - subflow + - The flow to be executed + - Flow + - Yes + - - + + - Inferred from the inner structure. + It's the sets of inputs required by the StartNode of the inner flow + - Inferred from the inner structure. + It's the set of outputs defined in the ``subflow``'s specification + - Inferred from the inner flow: one per unique ``branch_name`` of the ``subflow``'s EndNodes + * - MapNode + - The MapNode is used when we need to map a sequence of nodes to each of the values + defined in a list (from output of a previous node). This node is responsible to + asynchronically map each value of a collection (defined in input_schema) to the first node + of the 'subflow' and reduce the outputs of the last node of the 'subflow' to + defined variables (defined in output_schema) + - .. list-table:: + :header-rows: 1 + :widths: 20 35 15 15 15 + :class: mywideinnertable + + * - Name + - Description + - Type + - Mandatory + - Default + * - subflow + - The flow that should be applied to all the input values + - Flow + - Yes + - - + * - reducers + - The way the outputs of the different executions (map) should be collected together (reduce). + It's a dictionary mapping the name of an output to the respective reduction method. + Currently supported reduction methods are: ``append``, ``sum``, ``average``, ``max``, ``min``. + Allowed methods depend on the type of the output: + + - ``sum``, ``average``, ``max``, ``min`` are applicable only to ``integer`` and ``number`` types + - ``append`` is applicable to all the types + + - object[str, str] | null + - No + - null, each output is aggregated through value concatenation (append) + + - Inferred from the inner structure (as defined in FlowNode). + The names of the inputs will be the ones of the inner flow, + complemented with the ``iterated_`` prefix. Their type is + ``Union[inner_type, List[inner_type]]``, where ``inner_type`` + is the type of the respective input in the inner flow. + + - If an input of type ``inner_type`` is connected, the same value will used in + all the executions of the inner flow + - If an input of type ``List[inner_type]`` is connected, the input values will be iterated over + + Note that all the input lists must have the same length, otherwise a runtime error will be thrown. + + - Inferred from the inner structure (as defined in FlowNode), + combined with the reducer method of each output. + The names of the outputs will be the ones of the inner flow, + complemented with the ``collected_`` prefix. Their type depends + on the ``reduce`` method specified for that output: + + - ``List`` of the respective output type in case of ``append`` + - same type of the respective output type in case of ``sum``, ``avg`` + + - One, the default next + * - CatchExceptionNode + - * Used to catch exceptions that may be raised during the execution of a given subflow. + * If an exception is caught during the subflow execution, branches out to an exception branch. + Otherwise, uses the transitions/branches specified for the subflow. + * Exposes the inputs of the subflow and outputs the subflow outputs with additional information + about a potentially caught exception (for more information read the :ref:`security guidelines `). + * When the subflow runs to completion without failure, this node returns the subflow outputs. + Otherwise, it returns the default values for the subflow outputs, along with the exception + information. + - .. list-table:: + :header-rows: 1 + :widths: 20 35 15 15 15 + :class: mywideinnertable + + * - Name + - Description + - Type + - Mandatory + - Default + * - subflow + - Flow to execute and catch errors from + - Flow + - Yes + - - + + - Same as the inputs from the ``sub_flow``. + + - Composed of: + + * The outputs of the ``sub_flow``. If an exception is raised, will output the default values + of each output property of the subflow. As a consequence, all outputs of the node must have + default values. If they are not already specified at the subflow level, the developer must + specify them in the output properties of the CatchExceptionNode node. + * The caught exception information, named ``caught_exception_info``: (optional string, + default to null when no exception is raised). + + - Composed of: + + * The branches of the ``sub_flow``; + * A branch ``caught_exception_branch`` for when an exception is caught. + * - ParallelMapNode + - The ParallelMapNode is used when we need to map a sequence of nodes to each of the values + defined in a list (from output of a previous node). Its functionality is equivalent to the MapNode, + the only difference is that in this node the map operation is supposed to be performed in parallel. + Please check the concerns regarding parallel execution depicted in the :ref:`parallelization section `. + - .. list-table:: + :header-rows: 1 + :widths: 20 35 15 15 15 + :class: mywideinnertable + + * - Name + - Description + - Type + - Mandatory + - Default + * - subflow + - The flow that should be applied to all the input values + - Flow + - Yes + - - + * - reducers + - The way the outputs of the different executions (map) should be collected together (reduce). + It's a dictionary mapping the name of an output to the respective reduction method. + Currently supported reduction methods are: ``append``, ``sum``, ``average``, ``max``, ``min``. + Allowed methods depend on the type of the output: + + - ``sum``, ``average``, ``max``, ``min`` are applicable only to ``integer`` and ``number`` types + - ``append`` is applicable to all the types + + - object[str, str] | null + - No + - null, each output is aggregated through value concatenation (append) + + - Inferred from the inner structure (as defined in FlowNode). + The names of the inputs will be the ones of the inner flow, + complemented with the ``iterated_`` prefix. Their type is + ``Union[inner_type, List[inner_type]]``, where ``inner_type`` + is the type of the respective input in the inner flow. + + - If an input of type ``inner_type`` is connected, the same value will used in + all the executions of the inner flow + - If an input of type ``List[inner_type]`` is connected, the input values will be iterated over + + Note that all the input lists must have the same length, otherwise a runtime error will be thrown. + + - Inferred from the inner structure (as defined in FlowNode), + combined with the reducer method of each output. + The names of the outputs will be the ones of the inner flow, + complemented with the ``collected_`` prefix. Their type depends + on the ``reduce`` method specified for that output: + + - ``List`` of the respective output type in case of ``append`` + - same type of the respective output type in case of ``sum``, ``avg`` + + - One, the default next + * - ParallelFlowNode + - Execute a list of subflows in parallel. + Please check the concerns regarding parallel execution depicted in the :ref:`parallelization section `. + - .. list-table:: + :header-rows: 1 + :widths: 20 35 15 15 15 + :class: mywideinnertable + + * - Name + - Description + - Type + - Mandatory + - Default + * - subflows + - The flows that should be executed in parallel + - List[Flow] + - Yes + - - + + - Inferred from the inner structure. It's the union of the sets of inputs of the inner flows. + Inputs of different inner flows that have the same name are merged if they have the same type. + Inputs of different inner flows with the same name but different types are not allowed. + + - Inferred from the inner structure. It's the union of the outputs of the inner flows. + Outputs of different inner flows with the same name are not allowed. + + - One, the default next + * - StartNode + - * Entry point of a flow + * Defines the flow inputs + - None + - The list of inputs that should be exposed by the flow + - Inferred form the inputs. If a value is given, it must match exactly the list of properties defined in inputs + - One, the default next + * - EndNode + - * End point of a flow + * Defines exported outputs of the flow + - .. list-table:: + :header-rows: 1 + :widths: 20 35 15 15 15 + :class: mywideinnertable + + * - Name + - Description + - Type + - Mandatory + - Default + * - branch_name + - The name of the branch that corresponds to the branch that gets closed by this node, + which will be exposed by the Flow + - string | null + - No + - null (the default ``next`` branch value is used) + + - Inferred form the outputs. If a value is given, it must match exactly the list of properties defined in outputs + - The list of outputs that should be exposed by the flow + - None (note that the branch_name is used by the Flow, not by this node) + * - BranchingNode + - * Control flow branching point + * Defines which branch to follow based on the value of a given input + * Each input value is mapped to a different outgoing branch + - .. list-table:: + :header-rows: 1 + :widths: 20 35 15 15 15 + :class: mywideinnertable + + * - Name + - Description + - Type + - Mandatory + - Default + * - mapping + - Mapping between the value of the input and the name of the outgoing branch that will + be taken when that input value is given + - object[str, str] + - Yes + - - + + - The input value that should be used as key for the mapping + - None + - One for each value in the mapping, plus a branch called default, which is the branch taken by the flow + when mapping fails (i.e., the input does not match any key in the mapping) + * - ToolNode + - Executes the given tool + - .. list-table:: + :header-rows: 1 + :widths: 20 35 15 15 15 + :class: mywideinnertable + + * - Name + - Description + - Type + - Mandatory + - Default + * - tool + - The tool to be executed + - Tool + - Yes + - - + + - Inferred from the definition of the tool to execute + - Inferred from the definition of the tool to execute + - One, the default next + * - InputMessageNode + - * Appends an agent message to the conversation, if given + * Interrupts the execution of the flow in order to wait for a user input and restarts after getting it + * User input is appended to the conversation as a user message + * User input is also returned as a string property from the node. + - .. list-table:: + :header-rows: 1 + :widths: 20 35 15 15 15 + :class: mywideinnertable + + * - Name + - Description + - Type + - Mandatory + - Default + * - message + - Content of the agent message to append to the conversation before waiting for user input. + Allows placeholders, which can define inputs + - str + - No + - null (no agent message is appended) + + - One per variable in the message + - One string property that represents the content of the input user message + - One, the default next + * - OutputMessageNode + - Appends an agent message to the ongoing flow conversation + - .. list-table:: + :header-rows: 1 + :widths: 20 35 15 15 15 + :class: mywideinnertable + + * - Name + - Description + - Type + - Mandatory + - Default + * - message + - Content of the agent message to append. Allows placeholders, which can define inputs + - str + - Yes + - - + + - One per variable in the message + - No outputs + - One, the default next + +A2AAgent +~~~~~~~~ + +``A2AAgent`` is an implementation of ``AgenticComponent`` which uses the A2A protocol to communicate with a remote server agent. It handles all necessary data transformation and communication logic to and from the server agent. +For more details on the A2A protocol, refer to the :ref:`A2A (Agent to Agent Protocol) section `. + +.. code-block:: python + + class A2AAgent(AgenticComponent): + agent_url: str + connection_config: A2AConnectionConfig + session_parameters: Optional[Dict[str, Any]] + +- ``agent_url``: Specifies the URL of the remote server agent for establishing a connection, serving as the endpoint for communications. +- ``connection_config``: Contains HTTP connection details, including timeouts, SSL/TLS security settings, and optional retry policy configuration, ensuring a secure and optimized connection with necessary certificates. +- ``session_parameters``: Defines session behavior settings such as polling timeouts and retry mechanisms, enabling precise control over interactions with the remote server to manage interruptions or delays. + These settings include ``timeout``, specifying the maximum wait time in seconds before deeming a session unresponsive; + ``poll_interval``, defining the interval in seconds between polling attempts to check for server responses; + and ``max_retries``, indicating the maximum number of retry attempts to establish a connection or obtain a response before aborting. + +RemoteAgent +~~~~~~~~~~~ + +A ``RemoteAgent`` is an ``AgenticComponent`` whose logic executes outside the +current process, typically behind a remote endpoint. The representation must therefore +capture enough information for an executor to perform the remote invocation and +to relay messages back and forth. + + +OciAgent +~~~~~~~~ + +``OciAgent`` is a concrete implementation of ``RemoteAgent`` running on +Oracle Cloud Infrastructure. It adds OCI-specific authentication and connection details. + +.. code-block:: python + + class OciAgent(ComponentWithIO): + agent_endpoint_id: str + client_config: OciClientConfig # contains all OCI authentication related configurations + retry_policy: Optional[RetryPolicy] # Optional retry configuration for calls sent to the remote OCI agent + +- ``agent_endpoint_id`` identifies the OCI AI Agent endpoint that should receive the request. +- ``client_config`` contains the OCI client and authentication configuration used to connect to the service. +- ``retry_policy`` optionally specifies a ``RetryPolicy`` for outbound requests to the remote OCI agent. + + +Swarm +~~~~~ + +A ``Swarm`` is an ``AgenticComponent`` that enables multi-agent collaboration. +Unlike a single agent, a Swarm defines a group of agents that can communicate +and delegate tasks among each other based on a set of predefined relationships. +Agents in Swarm can be any ``AgenticComponent``. +Swarm preserves the standard messaging and execution semantics of ``AgenticComponent``. + +.. code-block:: python + + class Swarm(AgenticComponent): + first_agent: AgenticComponent + relationships: List[Tuple[AgenticComponent, AgenticComponent]] + handoff: Literal["never", "optional", "always"] + +When a Swarm is initialized, the conversation always begins with the ``first_agent``— this is the agent that interacts directly with the human user. + +From there, the ``first_agent`` may: + +1. Response directly to the user's query, or +2. Call another agent (that it has a defined relationship with) to handle a specialized subtask. + +The human user remains in conversation with the ``first agent`` during this time. +The called agent can, in turn, call other agents it has relationships with to further handle a subtask. + +If ``handoff="optional"``, the first_agent also gains a third option: +it may hand off the entire conversation to another agent. +Once the handoff occurs, the receiving agent becomes the new primary point of contact for the human user. This mechanism can significantly reduce latency by avoiding unnecessary back-and-forth message passing between agents. + +When ``handoff="always"`` is enabled, an agent cannot call another agent and wait for a reply (i.e. it does not have the second option mentioned above). +Delegation is only possible through a full handoff of the conversation. +Handing off the conversation establishes a strict chain-of-ownership: each agent must transfer the entire dialogue context when involving another agent. + +Each relationship is defined as a tuple ``(caller_agent, recipient_agent)`` +which represents a one-way communication link from ``caller_agent`` to ``recipient_agent``. + +ManagerWorkers +~~~~~~~~~~~~~~ + +A ``ManagerWorkers`` is an ``AgenticComponent`` designed for manager-worker style collaboration among multiple agents. +It consists of a manager agent that coordinates and assigns tasks to a group of worker agents. +Agents in ManagerWorkers can be any ``AgenticComponent``. +The ManagerWorkers component maintains the standard messaging and execution semantics of an ``AgenticComponent``. + +.. code-block:: python + + class ManagerWorkers(AgenticComponent): + group_manager: AgenticComponent + workers: List[AgenticComponent] + +The ManagerWorkers has two main parameters: + +- ``group_manager`` + An agentic component (e.g. Agent) that is used as the group manager, + responsible for coordinating and assigning tasks to the workers. + +- ``workers`` - List of agentic components + These agentic components serve as the workers within the group and are coordinated by the group manager. + + - Workers cannot interact with the end user directly. + - When invoked, each worker can leverage its equipped tools to complete the assigned task and report the result back to the group manager. + + +Datastores +~~~~~~~~~~ + +Datastores are the AgentSpec abstraction that enables agentic systems to store and access data. + +.. code-block:: python + + class Datastore(Component, abstract=True): + pass + +All datastores currently defined represent collections of objects defined by their type as an ``Entity``, however the Datastore parent type is left without constraint to enable future extension or the development of plugins with different Datastore structure (e.g. key-value stores, or graph database, or unstructured data storage) + +An ``Entity`` is a property that has a JSON schema equivalent to an object property; an object property would work. + +Relational datastores & In-Memory datastore +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Relational datastores (``OracleDatabaseDatastore``, ``PostgresDatabaseDatastore``) should support SQL queries. For development use, we have a drop-in replacement for them (``InMemoryCollectionDatastore``). +Both of these contain many collections (tables) where each collection is a set entities. They need to have a predefined fixed schema. A schema is a mapping from a collection name to an Entity object defining the entity(row). + +.. code-block:: python + + class InMemoryCollectionDatastore(Datastore): + datastore_schema: Dict[str, Entity] + + class RelationalDatastore(Datastore, is_abstract=True): + datastore_schema: Dict[str, Entity] + +OracleDatabaseDatastore +^^^^^^^^^^^^^^^^^^^^^^^ + +The ``OracleDatabaseDatastore`` is a relational datastore that requires an ``OracleDatabaseConnectionConfig`` object for configuration. Sensitive information (e.g., wallets, keys) is excluded from exported configurations and should be loaded at deserialization time. + +.. code-block:: python + + class OracleDatabaseDatastore(RelationalDatastore): + connection_config: OracleDatabaseConnectionConfig + +``OracleDatabaseConnectionConfig`` is the abstraction we use for configuring connections to Oracle Database. + +.. code-block:: python + + class OracleDatabaseConnectionConfig(Component, is_abstract=True): + pass + +``TlsOracleDatabaseConnectionConfig`` For standard TLS Oracle Database connections. + +.. code-block:: python + + class TlsOracleDatabaseConnectionConfig(OracleDatabaseConnectionConfig): + user: SensitiveField[str] + password: SensitiveField[str] + dsn: SensitiveField[str] + protocol: Literal["tcp", "tcps"] + config_dir: Optional[str] + +- ``user``: User used to connect to the database +- ``password``: Password for the provided user +- ``dsn``: Connection string for the database +- ``protocol``: 'tcp' or 'tcps' indicating whether to use unencrypted network traffic or encrypted network traffic (TLS) +- ``config_dir``: Configuration directory for the database connection. Set this if you are using an alias from your tnsnames.ora files as a DSN. Make sure that the specified DSN is appropriate for TLS connections. + +``MTlsOracleDatabaseConnectionConfig`` For Oracle DB connections using mutual TLS (wallet authentication). + +.. code-block:: python + + class MTlsOracleDatabaseConnectionConfig(TlsOracleDatabaseConnectionConfig): + + wallet_location: SensitiveField[str] + wallet_password: SensitiveField[str] + +- ``wallet_location``: Location where the Oracle Database wallet is stored. +- ``wallet_password``: Password for the provided wallet. + +PostgresDatastore +^^^^^^^^^^^^^^^^^ + +The ``PostgresDatabaseDatastore`` is a relational datastore intended to store entities in PostgreSQL databases. It requires a ``PostgresDatabaseConnectionConfig`` object for connection and authentication details. Sensitive information (e.g., user, password, SSL keys) is excluded from exported configurations and should be loaded at deserialization time. + +.. code-block:: python + + class PostgresDatabaseDatastore(RelationalDatastore): + connection_config: PostgresDatabaseConnectionConfig + + class PostgresDatabaseConnectionConfig(Component, is_abstract=True): + pass + + class TlsPostgresDatabaseConnectionConfig(PostgresDatabaseConnectionConfig): + user: SensitiveField[str] + password: SensitiveField[str] + url: str + sslmode: Literal["disable","allow", "prefer","require","verify-ca","verify-full"] + sslcert: Optional[str] + sslkey: Optional[SensitiveField[str]] + sslrootcert: Optional[str] + sslcrl: Optional[str] + +- ``user``: User of the postgres database +- ``password``: Password of the postgres database +- ``url``: URL to access the postgres database +- ``sslmode``: SSL mode for the PostgreSQL connection. +- ``sslcert``: Path of the client SSL certificate, replacing the default ``~/.postgresql/postgresql.crt``. Ignored if an SSL connection is not made. +- ``sslkey``: Path of the file containing the secret key used for the client certificate, replacing the default ``~/.postgresql/postgresql.key``. Ignored if an SSL connection is not made. +- ``sslrootcert``: Path of the file containing SSL certificate authority (CA) certificate(s). Used to verify server identity. +- ``sslcrl``: Path of the SSL server certificate revocation list (CRL). Certificates listed will be rejected while attempting to authenticate the server's certificate. + + +Transforms +^^^^^^^^^^ + +Transforms extend the base ``MessageTransform`` component that can be used in agents. They apply a transformation to the messages before they are passed to the agent's LLM. + +.. code-block:: python + + class MessageTransform(Component, abstract=True): + pass + +MessageSummarizationTransform +''''''''''''''''''''''''''''' + +Summarizes messages exceeding a given number of characters using an LLM and caches the summaries in a ``Datastore``. This is useful for conversations with long messages where the context can become too large for the agent LLM to handle. + +.. code-block:: python + + class MessageSummarizationTransform(MessageTransform): + llm: LlmConfig # LLM to use for the summarization. + max_message_size: int # The maximum size in number of characters for the content of a message. + summarization_instructions: str # Instruction for the LLM on how to summarize the messages. + summarized_message_template: str # Jinja2 template on how to present the summary (with variable `summary`) to the agent using the transform. + datastore: Optional[Datastore] # Datastore on which to store the cache. If None, no caching happens. + max_cache_size: Optional[int] # The number of cache entries (messages) kept in the cache. If None, there is no limit on cache size and no eviction occurs. + max_cache_lifetime: Optional[int] # max lifetime of a message in the cache in seconds. If None, cached data persists indefinitely. + cache_collection_name: str # Name of the collection in the cache for storing summarized messages. + +ConversationSummarizationTransform +'''''''''''''''''''''''''''''''''' + +Summarizes conversations exceeding a given number of messages using an LLM and caches conversation summaries in a ``Datastore``. This is useful to reduce long conversation history into a concise context for downstream LLM calls. + +.. code-block:: python + + class ConversationSummarizationTransform(MessageTransform): + llm: LlmConfig # LLM to use for the summarization. + max_num_messages: Optional[int] # Conversation-level message-count threshold. Mutually exclusive with ``max_num_characters``. + max_num_characters: Optional[int] # Total number of characters in the conversation after which we trigger summarization. Can be used together with ``min_num_messages``, but not with ``max_num_messages``. + min_num_messages: int # Number of recent messages to keep from summarizing, regardless of whether summarization is triggered by ``max_num_messages`` or ``max_num_characters``. + summarization_instructions: str # Instruction for the LLM on how to summarize the conversation. + summarized_conversation_template: str # Jinja2 template on how to present the summary (with variable ``summary``) to the agent using the transform. + datastore: Optional[Datastore] # Datastore on which to store the cache. If None, no caching happens. + max_cache_size: Optional[int] # The maximum number of entries kept in the cache + max_cache_lifetime: Optional[int] # max lifetime of an element in the cache in seconds + cache_collection_name: str # Name of the collection in the cache datastore where summarized conversations will be stored + +Versioning +---------- + +Every Agent Spec configuration should include a property called ``agentspec_version`` at the top level. +The value of ``agentspec_version`` specifies the Agent Spec specification version the configuration is +written for. When this field is missing from the configuration, the latest (current) Agent Spec version +is used instead. + +For example, the JSON serialized version of an Agent should look like the following: + +.. code-block:: json + + { + "component_type": "Agent", + "name": "my agent", + "id": "my_agent_id", + "description": "", + "human_in_the_loop": true, + "llm_config": { + "component_type": "VllmConfig", + "name": "my llm", + "id": "my_llm_id", + "description": "", + "default_generation_parameters": {}, + "model_id": "my/llm", + "url": "my.llm.url" + } + "tools": [], + "agentspec_version": "26.3.0" + } + +For release versioning, Agent Spec follows the format YEAR.QUARTER.PATCH. Agent Spec follows a +quarterly release cadence, and it aligns versioning to that cadence such that YEAR corresponds +to the last two digits of the year, and QUARTER refers to the quarter of the release (from 1 to 4). +The first release is 25.4.1. + +Updates in the PATCH version must not introduce new features or behavioral changes, +they should only cover security concerns and clarifications if needed. +However, any version update, including PATCH ones, could contain breaking changes. + +Breaking changes include all the modifications that would make a configuration written in +the previous version invalid or semantically different, for example: + +- Removing/modifying components +- Removing/renaming an attribute +- Supporting new, or changing existing behavior of components, + even when the existing signature is unchanged + +Non-breaking changes include: + +- Adding new components +- Adding new attributes +- Disambiguation or clarification of underspecified components' structure or behavior that do not + contradict previous versions + +It's the responsibility of the maintainers of Agent Spec Runtimes, SDKs, and Adapters to keep up +to date with the latest changes in the Agent Spec language specification, and to report +the compatibility of their artifacts with the different Agent Spec specification versions. + +Due to backward compatibility reasons, we recommend to create Agent Spec configurations with the +minimum version supported by all the Components in the configuration with the desired behavior. + +Backward compatibility +~~~~~~~~~~~~~~~~~~~~~~ + +All breaking changes must go through a deprecation cycle of one year. + +Whenever a breaking change is introduced, a deprecation notice must be provided +in the language specification. + +Deprecated features can be removed in any quarter release after 1 year from the deprecation notice, +but it should not be done in patch releases, unless required for security reasons. + +In any case, removed features must be announced in the release notes. + + +.. _agentspecauthspec_nightly: + + +Authentication +~~~~~~~~~~~~~~ + +Authentication configurations are used by components that communicate with remote services requiring +credentials (this may include tools, nodes, remote agents, and other components). In order to make +these tools usable in a secure and portable way, Agent Spec needs a standardized way for users to +declare the authentication method and the information required by the execution runtime to apply it +(for example, running an OAuth authorization flow to connect to a protected MCP Server). + +We define a new Component called ``AuthConfig`` that acts as the base type for all authentication configurations: + +.. code-block:: python + + class AuthConfig(Component): + pass + + +Concrete authentication mechanisms are represented as subclasses of ``AuthConfig``. Runtimes are +responsible for interpreting these configurations, enforcing security best practices (such as treating +secrets as sensitive fields), and performing any interactive steps required to obtain credentials. + + +OAuth Authentication (``OAuthConfig``) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +OAuth authentication enables components to obtain and refresh access tokens via an OAuth 2.x authorization +server. ``OAuthConfig`` is intentionally generic so it can be used for MCP servers (per MCP authorization +requirements) and for non-MCP tools and other compnoents (e.g., ``ApiNode``) that call standard OAuth-protected +APIs. + +.. code-block:: python + + class OAuthConfig(AuthConfig): + issuer: Optional[str] + endpoints: Optional[OAuthEndpoints] + client: OAuthClientConfig + + redirect_uri: str + scopes: Optional[Union[str, List[str]]] + scope_policy: Optional[Literal["use_challenge_or_supported", "fixed"]] + + pkce: Optional[PKCEPolicy] + resource: Optional[str] + + +* ``issuer`` is the authorization server issuer URL used for discovery (e.g., OpenID Connect + (OIDC) discovery or RFC 8414). If provided, runtimes should discover endpoints and server + capabilities from this issuer. +* ``endpoints`` provides explicit OAuth endpoints (authorization, token, refresh, etc.). + If provided, runtimes should use these endpoints directly instead of discovery. +* ``client`` defines how the OAuth client identity is established (pre-registered client, client-id + metadata document, or dynamic client registration). + +* ``redirect_uri`` is the callback URI where the authorization server redirects the user agent + after consent (authorization code flow). +* ``scopes`` optionally defines requested scopes as a space-delimited string or list of strings. +* ``scope_policy`` specifies how the runtime selects scopes: + + * ``"use_challenge_or_supported"`` means the runtime should prefer scopes indicated by runtime + challenges/metadata (for MCP, this aligns with using ``WWW-Authenticate`` scope when present, + otherwise metadata-supported scopes). + * ``"fixed"`` means the runtime should request exactly the provided ``scopes``. + +* ``pkce`` configures Proof Key for Code Exchange (PKCE) behavior. For MCP authorization code flows, + runtimes should require PKCE and should use ``S256`` when technically capable. +* ``resource`` optionally sets a resource indicator (RFC 8707). For MCP this is typically derived + from the MCP server URL / protected resource metadata, but exposing it here allows explicit + configuration for non-MCP integrations or constrained environments. + + +OAuth Client Configuration (``OAuthClientConfig``) +'''''''''''''''''''''''''''''''''''''''''''''''''' + +OAuth client configuration specifies how the runtime identifies itself to the authorization server +and (optionally) how it registers as a client. This is used by ``OAuthConfig.client``. + +.. code-block:: python + + class OAuthClientConfig(Component): + type: Literal["pre_registered", "client_id_metadata_document", "dynamic_registration"] + + client_id: Optional[str] + client_secret: Optional[SensitiveField[str]] + token_endpoint_auth_method: Optional[str] + + client_id_metadata_url: Optional[str] + + registration_endpoint: Optional[str] + +* ``type`` selects the client identity / registration approach: + + * ``"pre_registered"`` uses client credentials that were registered out-of-band with the authorization server. + * ``"client_id_metadata_document"`` uses a URL-formatted ``client_id`` pointing to a hosted client metadata + JSON document (Client ID Metadata Document approach). + * ``"dynamic_registration"`` uses OAuth dynamic client registration (RFC 7591) to obtain a client id at runtime. + +* ``client_id`` is the OAuth client identifier (used for ``"pre_registered"``). +* ``client_secret`` is the client secret (used for confidential ``"pre_registered"`` clients). It SHOULD be omitted + for public clients. +* ``token_endpoint_auth_method`` specifies how the client authenticates to the token endpoint (e.g., ``"client_secret_basic"``, + ``"client_secret_post"``, ``"private_key_jwt"``, or ``"none"``). + +* ``client_id_metadata_url`` is the HTTPS URL used as the OAuth ``client_id`` when ``type="client_id_metadata_document"``. + This URL points to the client metadata JSON document. + +* ``registration_endpoint`` optionally specifies the dynamic client registration endpoint when ``type="dynamic_registration"``. + If omitted, runtimes SHOULD obtain it from authorization server discovery metadata when available. + + +OAuth Endpoints +''''''''''''''' + +OAuth-based tools sometimes need explicit endpoints (manual configuration) instead of discovery. ``OAuthEndpoints`` +groups these URLs so ``OAuthConfig`` stays readable and reusable across MCP tools and non-MCP tools. + +.. code-block:: python + + class OAuthEndpoints: + authorization_endpoint: str + token_endpoint: str + refresh_endpoint: Optional[str] + revocation_endpoint: Optional[str] + userinfo_endpoint: Optional[str] + +* ``authorization_endpoint`` is the authorization URL where the user agent is redirected to authenticate and grant consent. +* ``token_endpoint`` is the endpoint where the client exchanges an authorization code (or a refresh token) for access + (and optionally refresh) tokens. +* ``refresh_endpoint`` optionally overrides where refresh token requests are sent. If not provided, runtimes typically + reuse ``token_endpoint`` for refresh. +* ``revocation_endpoint`` optionally specifies where tokens can be revoked. +* ``userinfo_endpoint`` optionally specifies an OpenID Connect UserInfo endpoint when OIDC claims are needed. + + +PKCE Policy +''''''''''' + +The MCP authorization spec requires PKCE for authorization-code flows, and the reference ``OAuthClientProvider`` +enforces PKCE via server metadata (``code_challenge_methods_supported``). +``PKCEPolicy`` makes that behavior explicit and reusable for non-MCP tools. + +.. code-block:: python + + class PKCEPolicy: + required: bool + method: Literal["plain", "S256"] + +* ``required`` indicates whether the runtime must refuse to proceed if PKCE cannot be used/verified + via discovery metadata. Defaults to ``True``. +* ``method`` identifies the PKCE code challenge method. + + * with ``"plain"`` the code challenge is equal to code verifier. + * with ``"S256"`` the code verifier is hashed using SHA-256. + + Runtimes SHOULD use ``"S256"`` when technically capable. Defaults to ``"S256"``. + + +Token handling and session scope +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Runtimes that use ``AuthConfig`` (including ``OAuthConfig``) are responsible for obtaining, using, +and protecting credentials (e.g., access/refresh tokens). + +To keep configurations portable and safe (find more information in the +:ref:`security guidelines `): + +- **Treat tokens as secrets**: never expose tokens in exported configurations, UI output, tool traces, + prompts, or error messages. +- **Avoid URL-derived trust**: do not rely solely on a conversation/response identifier (especially + one that appears in a URL) as the "authentication" mechanism for accessing cached OAuth tokens. +- **Bind tokens to an authenticated principal**: in non-development deployments, token caches **should** + be scoped to an authenticated end-user and protected by server-side authorization checks. +- **Prefer short-lived access tokens** and use refresh tokens only when necessary; apply least-privilege + scopes and audience/resource restrictions. +- **Support safe interactive flows**: enforce PKCE for authorization-code flows when applicable, validate + ``state``, and restrict/validate redirect URIs. +- **Require user confirmation for discovered/auth-server trust & dynamic registration**: when using + discovery-based configuration (``issuer``) and/or ``client.type="dynamic_registration"``, runtimes/clients + **should** require an explicit user confirmation step. The prompt should clearly display the **authorization + server domain** and the **target resource/MCP server URL**, and warn when dynamic registration will create/modify + client state on the authorization server. Runtimes should also verify that discovered metadata corresponds to + the originally requested protected resource/server to reduce impersonation/phishing risk. + + + +.. _agentspecmcpspec_nightly: + +MCP (Model Context Protocol) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Model Context Protocol (MCP) provides a standardized way for LLM-based Assistants to +interact with remote servers that expose tools and data sources. MCP enables communication +over various transports such as Streamable HTTP, Server-Sent Events (SSE), or even local +stdio for prototyping. + +In Agent Spec, MCP components define configurations for establishing client sessions +and transports to MCP servers. These are used primarily for tools (via ``MCPTool``) +but can be extended to other remote interactions in future versions. + +MCP Client transport +^^^^^^^^^^^^^^^^^^^^ + +The core abstraction is ``ClientTransport``, which manages connections and sessions. + +.. code-block:: python + + class ClientTransport(Component): + session_parameters: Optional[Dict[str, Any]] + +The ``session_parameters`` should be used to specify parameters of the MCP client session, +such as the session name and version, or the session timeout. +These parameters are specified as a dictionary of parameter names and respective values. +The names are strings, while values can be of any type compatible with the JSON schema standard. + +MCP transports such as ``StdioTransport`` directly extend the ``ClientTransport`` component. + + +Stdio Transport +''''''''''''''' + +The ``StdioTransport`` component should be used for connecting to an MCP server via +subprocess with stdio. This transport must support being passed a the executable +command to run to start the servers as well as a list of command line arguments to +pass to the executable. It can also support being passed environment variables as +well as the working directory to use when spawning the process. + +.. code-block:: python + + class StdioTransport(ClientTransport): + command: str + args: List[str] + env: Optional[Dict[str, str]] + cwd: Optional[str] + + + +Remote MCP transports +^^^^^^^^^^^^^^^^^^^^^ + +Another category of MCP client transports rely on remote connections to MCP servers. +Those components should extend the ``RemoteTransport`` component and should support +the ``url``, ``auth``, ``headers``, ``sensitive_headers`` and optional +``retry_policy`` fields. + +.. code-block:: python + + class RemoteTransport(ClientTransport): + url: str + auth: Optional[AuthConfig] + headers: Dict[str, str] + sensitive_headers: SensitiveField[Dict[str, str]] + retry_policy: Optional[RetryPolicy] + + +- ``url`` is a string representing the URL to send the request. +- ``auth`` optionally specifies an ``AuthConfig`` to authenticate requests sent to the remote + MCP server. When set, runtime should use this configuration to attach credentials to requests + and/or to initiate interactive authentication flows as required. +- ``headers`` is a dictionary of additional headers to use in the client. +- ``sensitive_headers`` are additional headers that should be merged with the ``headers`` provided + when executing the request, but these headers are excluded from exported configuration thus they + are better suited to contain sensitive information not intended to be shared as widely as the + exported configuration. +- ``retry_policy`` optionally specifies a ``RetryPolicy`` for requests sent through the remote MCP transport. + + +SSE Transport +''''''''''''' + +The server-sent events (SSE) transport should be used to connect to +MCP servers via Server-Sent Events. + +.. code-block:: python + + class SSETransport(RemoteTransport): + pass + + +Streamable HTTP Transport +''''''''''''''''''''''''' + +The Streamable HTTP transport should be used to connect to MCP servers via the +`Streamable HTTP `_ +transport. + +.. code-block:: python + + class StreamableHTTPTransport(RemoteTransport): + pass + + +The transports defined above can be used in components like ``MCPTool`` +(see the Tools section) to connect to MCP servers. + + + +Additions to MCP transports +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Finally, individual MCP client transports should be extended to support additional +functionalities, such as mutual Transport Layer Security (mTLS) connections. + + +SSE Transport with mTLS +''''''''''''''''''''''' + +.. code-block:: python + + class SSEmTLSTransport(SSETransport): + key_file: SensitiveField[str] + cert_file: SensitiveField[str] + ca_file: SensitiveField[str] + +- ``key_file`` is the path to the client's private key file (PEM format). +- ``cert_file`` is the path to the client's certificate chain file (PEM format). +- ``ca_file`` is the path to the trusted CA certificate file (PEM format) to verify the server. + + +Streamable HTTP Transport with mTLS +''''''''''''''''''''''''''''''''''' + +.. code-block:: python + + class StreamableHTTPmTLSTransport(StreamableHTTPTransport): + key_file: SensitiveField[str] + cert_file: SensitiveField[str] + ca_file: SensitiveField[str] + + +- ``key_file`` is the path to the client's private key file (PEM format). +- ``cert_file`` is the path to the client's certificate chain file (PEM format). +- ``ca_file`` is the path to the trusted CA certificate file (PEM format) to verify the server. + + +.. warning:: + + For production use, always prefer secure transports like those with mTLS to ensure + mutual authentication. + +.. _agentspeca2aspec_nightly: + +A2A (Agent to Agent Protocol) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Agent to Agent Protocol (A2A) is a protocol designed to facilitate communication among agents. +This protocol enables agents to exchange messages, delegate tasks, and collaborate effectively. +It can also be used to establish a connection with a remote agent, allowing for distributed agentic systems. + +The connection to a remote agent or between agents is configured using ``A2AConnectionConfig``, +which defines the parameters for establishing a secure and reliable HTTP connection. Session +behavior such as polling is configured with ``A2AAgent.session_parameters``, while request retry +behavior can be configured with ``A2AConnectionConfig.retry_policy``. Together these settings +cover timeouts, polling, retries, and SSL/TLS security for encrypted and authenticated +communication. + +.. code-block:: python + + class A2AConnectionConfig(Component): + timeout: float # Connection timeout in seconds + headers: Optional[Dict[str, str]] # A dictionary of HTTP headers to include in requests + verify: bool # Boolean to enable HTTPS or not + key_file: Optional[str] # Path to the client's private key file + cert_file: Optional[str] # Path to the client's certificate chain file + ssl_ca_cert: Optional[str] # Path to private key file + ca_path: Optional[str] # Path to the trusted CA certificate file + retry_policy: Optional[RetryPolicy] # Optional retry configuration for requests sent through this A2A connection + +Ecosystem of plugins +-------------------- + +In order to support a wide range of use-cases, Agent Spec allows the creation of +custom components through a plugin system. Plugins are extensions +which can include new components or variants of the built-in Agent Spec +components. For supporting reading, writing and executing plugin components, +the serialization and deserialization logic must be added in an Agent Spec SDK +and the execution logic of the plugin component must be added in an Agent Spec +Runtime of choice. + +To enable the support of plugins in Agent Spec SDK and Runtime, every component +from a plugin should specify its ``component_type``. This is needed for +serialization and deserialization in order to be able to select the right +plugin serializer or deserializer. Additionally, the name and version of plugins +are needed, because this information will help track and understand the provenance +and compatibility of various components from the plugin ecosystem. + +Additionally, if a plugin component is intended as a subtype of another component +(for example a ``Node``, such that the new component can be included in a Flow +which expects sub components of type ``Node``), it must then have the same +unmodified attributes as the parent type and can only add new attributes. + +.. literalinclude:: ../agentspec_config_examples/example_plugin_component.json5 + :language: JSON5 + +Assuming an SDK in the Python programming language, as an example (see +:ref:`the sdk detailed below `), the abstract interface +implemented for a plugin should be similar to this: + +.. code-block:: python + + class ComponentSerializationPlugin: + + @abstractmethod + def plugin_name(self) -> str: + """Return the plugin name.""" + pass + + @abstractmethod + def plugin_version(self) -> str: + """Return the plugin version.""" + pass + + @abstractmethod + def supported_component_types(self) -> List[str]: + """Indicate what component types the plugin supports.""" + pass + + @abstractmethod + def serialize(self, component: Component, serialization_provider) -> Dict[str, Any]: + """Method to implement to serialize a component that the plugin should be able to support.""" + pass + + + +And similarly for deserialization: + +.. code-block:: python + + class ComponentDeserializationPlugin: + def plugin_name(self) -> str: ... + + def plugin_version(self) -> str: ... + + def supported_component_types(self) -> List[str]: ... + + @abstractmethod + def deserialize(self, serialized_component: Dict[str, Any], deserialization_provider: DeserializationProvider) -> Component: + """Method to implement to deserialize a component that the plugin should be able to support.""" + pass + + +Disaggregated components +------------------------ + +There are scenarios where some parts of an Agent Spec configuration should not be exported +as part of the main assistant's configuration. For example when: + +- Some parts of a Component configuration contain sensitive information; +- It's useful to plug different Component versions in a configuration, based on the use case + (e.g., use different LlmConfigurations in development and production environment), but + without duplicating the configuration for every Component's version. + +.. important:: + Agent Spec configurations intended for storage, sharing, version control, logging, + or deployment should not contain sensitive information. Sensitive values should be + represented as references and supplied through trusted runtime or deserialization + mechanisms. + +For this reason, Agent Spec supports referencing **components and values** that are not serialized +in the same configuration, therefore called *disaggregated*. +A serialized configuration of disaggregated components contains only the dictionary of ``$referenced_components``, +and it does not contain any component at the top level. +These disaggregated components must be provided when the main configuration is deserialized, +otherwise the deserialization should fail. +Additional disaggregated components that are not part of any exported configuration (e.g., those containing +potentially sensitive information) can be additionally provided at deserialization time by the SDK or the Runtime +performing it. + +The disaggregated components references follow the same :ref:`reference system ` described for components, based on ID matching. +The ID used for matching the disaggregated component is, by default, the key used in the ``$referenced_components`` dictionary. +Users can override this behavior at deserialization time by mapping it to a different ID to match a component reference. +A component reference and its matched disaggregated component must be type-compatible. + +Note that also values (e.g., the ``prompt_template`` in an ``LlmNode``, or the ``url`` of a ``VllmConfig``) +can become referenced components. This means that in the main configuration from which they are disaggregated, +they will appear as component references in the ``$referenced_components`` dictionary, +with an ID assigned to them (i.e., the key of the dictionary entry). +The value should replace the reference in the main configuration during deserialization. + +Here's an example of an Agent Spec configuration that uses disaggregated components: + +.. code-block:: JSON5 + + { + "component_type": "Agent", + "id": "powerful_agent_id", + "name": "Powerful agent", + "description": null, + "human_in_the_loop": true + "metadata": {}, + "llm_config": { + "component_type": "VllmConfig", + "default_generation_parameters": null, + "description": null, + "id": "llama_llm_id", + "metadata": {}, + "model_id": "meta/llama-3.3-70b", + "name": "vllm", + "url": { "$component_ref": "llm_url_id" } + }, + "system_prompt": "You are a powerful agent", + "tools": [ + { "$component_ref": "powerful_tool_id" } + ], + "inputs": [], + "outputs": [] + } + +And here's the configuration containing the disaggregated components: + +.. code-block:: JSON5 + + { + "$referenced_components": { + "powerful_tool_id": { + "component_type": "ClientTool", + "name": "powerful_tool", + "id": "powerful_tool_id", + "description": "Does powerful things", + "metadata": {}, + "inputs": [], + "outputs": [] + }, + "llm_url_id": "my.url.to.llm" + } + } + + + + +.. _agentspecsensitivefield_nightly: + +Sensitive fields +---------------- + +Some of the fields in the configuration of Agent Spec components may contain sensitive information +such as API keys or authentication tokens. To help developers securely support these components, +AgentSpec runtimes and SDKs must follow these three guidelines: + +- **When exporting configurations**: Sensitive fields should be excluded by default and represented + as references. Any export that intentionally includes sensitive values must be treated as secret + material, not as an ordinary portable Agent Spec configuration. +- **When loading configurations**: Runtime adapters and SDKs must allow passing the excluded sensitive information. +- **When loading configurations**: If developers have already inlined the sensitive information into their + configurations, runtimes and SDKs should allow loading these configurations too. + +See all the fields below that are considered sensitive fields: + ++----------------------------------+--------------------+ +| Component | Attribute | ++==================================+====================+ +| OpenAiCompatibleConfig | api_key | ++----------------------------------+--------------------+ +| OpenAiCompatibleConfig | key_file | ++----------------------------------+--------------------+ +| OpenAiCompatibleConfig | cert_file | ++----------------------------------+--------------------+ +| OpenAiCompatibleConfig | ca_file | ++----------------------------------+--------------------+ +| OpenAiConfig | api_key | ++----------------------------------+--------------------+ +| LlmConfig | api_key | ++----------------------------------+--------------------+ +| GeminiAIStudioAuthConfig | api_key | ++----------------------------------+--------------------+ +| GeminiVertexAIAuthConfig | credentials | ++----------------------------------+--------------------+ +| OciClientConfigWithSecurityToken | auth_file_location | ++----------------------------------+--------------------+ +| OciClientConfigWithApiKey | auth_file_location | ++----------------------------------+--------------------+ +| RemoteTool | sensitive_headers | ++----------------------------------+--------------------+ +| ApiNode | sensitive_headers | ++----------------------------------+--------------------+ +| RemoteTransport | sensitive_headers | ++----------------------------------+--------------------+ +| SSEmTLSTransport | key_file | ++----------------------------------+--------------------+ +| SSEmTLSTransport | cert_file | ++----------------------------------+--------------------+ +| SSEmTLSTransport | ca_file | ++----------------------------------+--------------------+ +| StreamableHTTPmTLSTransport | key_file | ++----------------------------------+--------------------+ +| StreamableHTTPmTLSTransport | cert_file | ++----------------------------------+--------------------+ +| StreamableHTTPmTLSTransport | ca_file | ++----------------------------------+--------------------+ + +For example, ``GeminiAIStudioAuthConfig.api_key`` or +``GeminiVertexAIAuthConfig.credentials`` may become references while the enclosing +``auth`` component remains inline. + + +For example, the following component produced using the `pyagentspec` SDK: + +.. code-block:: python + + OpenAiCompatibleConfig( + name="llm-config", + id="llm-config-id", + url="https://some.api.com/v2", + model_id="some_model_id", + api_key="THIS_IS_SECRET", + key_file="/etc/certs/client.key", + cert_file="/etc/certs/client.pem", + ca_file="/etc/certs/ca.pem", + ) + +should produce the following configuration, in which sensitive fields are replaced by references + +.. code-block:: JSON5 + + { + "component_type" : "OpenAiCompatibleConfig", + "id" : "llm-config-id", + "name" : "llm-config", + "url" : "https://some.api.com/v2", + "model_id" : "some_model_id", + "api_key" : { + "$component_ref" : "llm-config-id.api_key" + }, + "key_file" : { + "$component_ref" : "llm-config-id.key_file" + }, + "cert_file" : { + "$component_ref" : "llm-config-id.cert_file" + }, + "ca_file" : { + "$component_ref" : "llm-config-id.ca_file" + }, + } + +Such a configuration would then require to pass the referenced sensitive information when loading, as +in the example code below: + +.. code-block:: python + + llm_config = AgentSpecDeserializer().from_json( + serialized_llm, + components_registry={ + "llm-config-id.api_key": "THIS_IS_SECRET", + "llm-config-id.key_file": "/etc/certs/client.key", + "llm-config-id.cert_file": "/etc/certs/client.pem", + "llm-config-id.ca_file": "/etc/certs/ca.pem", + }, + ) + + +Additional components for future versions +----------------------------------------- + +In this first version we focused on the foundational elements of agents, +but some concepts are not yet covered, and should be part of further +discussions, and included in future versions. + +Among them, we can highlight: + +- Memory +- Planning + +These topics will be covered in future versions of Agent Spec. + +Language examples +================= + +Standalone agent +---------------- + +Agents are capable of leveraging tools in multi-turn conversation in +order to reach a specific goal. + +Agents do not express flow of control: the layout for these components +simply expresses property assignments. + +In the following example we provide a generic implementation of a +flexible agent that includes all the components it can take advantage of +(e.g., tools, llms). In a future extension of the language, more +components can be added to this spec (e.g., memory, planner). + +|image2| + +In the following example, we show the representation of an agent focused on +benefits, which has the same structure depicted above. + +.. collapse:: Agent Spec representation + + .. literalinclude:: ../agentspec_config_examples/standalone_agent.json5 + :language: JSON5 + + +Standalone flow +--------------- + +The following diagram illustrates a flow that could be used in an online +store's customer support system. + +The flow checks if an order is eligible for a replacement, and if that +is the case, which products should be offered to the user in exchange +for their defective item. + +The flow may be invoked by a conversational agent or executed upon +configuration via another user interface. + +|image3| + +This graph is implemented by the following Agent Spec representation: + +.. collapse:: Agent Spec representation + + .. literalinclude:: ../agentspec_config_examples/standalone_flow.json5 + :language: JSON5 + + +Agents in flow +-------------- + +In this example we are going to show how to use agents in a flow. + +We propose a Coding Agent where different specialized agents operate in +sequence in order to generate code based on a user request. + +The flow consists of a main conversational agent that talks with a user +to gather the request, then three agents that take care of the code +generation and its review. + +The advantage of having this type of agent implemented as a graph is +that we have control on what is the sequence of calls and events that +should happen before approving the generated code. + +In particular, we can ensure that the code gets generated by the +respective agent, and before being passed back to the main assistant +that interacts with the user, it is passed through the two reviewer +agents, and we must get both validations before going back to the main +user facing agent. + +For brevity, in the diagram we omit the internal structure of the +agents, which is comparable to the one defined in example 5.1. + +Put name of the agent in the diagram. Show that same agent can be +executed in different places. + +|image5| + +This graph is implemented by the following Agent Spec representation: + +.. collapse:: Agent Spec representation + + .. literalinclude:: ../agentspec_config_examples/flow_calling_agent.json5 + :language: JSON5 + + +(Generated) JSON Language spec +============================== + +We put here the current JSON spec of the Agent Spec language. + +.. collapse:: JSON Schema + + .. literalinclude:: json_spec/agentspec_json_spec_26_3_0.json + :language: json + +Note about serialization of components +-------------------------------------- + +Every ``Component`` is supposed to be serialized with an additional field +called ``component_type``, which defines what type of component is being described. +The value of ``component_type`` should coincide with the name of the specific component's class. + +Examples of JSON serialization for a few common Components follow. + + +.. code-block:: JSON5 + + // VllmConfig serialization + { + "id": "vllm", + "name": "VLLM Model", + "description": null, + "metadata": {}, + "default_generation_parameters": {}, + "url": "http://url.of.my.vllm", + "model_id": "vllm_model_id", + "component_type": "VllmConfig", + "agentspec_version": "25.4.1" + } + + +.. code-block:: JSON5 + + // ServerTool serialization + { + "id": "get_weather_tool", + "name": "get_weather", + "description": "Gets the weather in specified city", + "metadata": {}, + "inputs": [ + { + "title": "city_name", + "type": "string" + } + ], + "outputs": [ + { + "title": "forecast", + "type": "string" + } + ], + "component_type": "ServerTool", + "agentspec_version": "25.4.1" + } + + +.. code-block:: JSON5 + + // Agent serialization + { + "id": "expert_agent_id", + "name": "Adaptive expert agent", + "description": null, + "metadata": {}, + "inputs": [ + { + "title": "domain_of_expertise", + "type": "string" + } + ], + "outputs": [], + "human_in_the_loop": true + "llm_config": { + "id": "llama_model_id", + "name": "Llama 3.1 8B instruct", + "description": null, + "metadata": {}, + "default_generation_parameters": {}, + "url": "url.of.my.llm.deployment:12345", + "model_id": "meta-llama/Meta-Llama-3.1-8B-Instruct", + "component_type": "VllmConfig" + }, + "system_prompt": "You are an expert in {{domain_of_expertise}}. Please help the users with their requests.", + "tools": [], + "component_type": "Agent", + "agentspec_version": "26.3.0" + } + + +Note about references +--------------------- + +To avoid duplicating the same component serialization in a document, we use references. +For this reason, we need two types for each component in the JSON schema specification of Agent Spec. +For example, ``Agent`` becomes a union to specify that it can be replaced by a reference: + +.. code-block:: json + + "Agent": { + "anyOf": [ + { "$component_ref": "#/$defs/ComponentReference" }, + { "$component_ref": "#/$defs/RawAgent" } + ] + } + +The ``ComponentReference`` is the same for every component, and it represents the way component references +are specified according to the Agent Spec language specification. + +.. code-block:: json + + "ComponentReference": { + "type": "object", + "properties": { + "$component_ref": {"type": "string"} + } + } + +``RawAgent`` is the un-changed version with the exception that some reference like ``#/$defs/LlmConfig`` +now are pointing to the type of the schema that can be potentially replaced by a ref + +.. code-block:: json + + "RawAgent": { + "description": "An agent is a component that can do se.... ", + "properties": { } + } + +JSON Example +~~~~~~~~~~~~ + +Here's an example of a Flow's definition in Python, and the respective +representation generated in JSON. We use PyAgentSpec in order to define the Agent Spec's Flow. + +.. collapse:: Python Flow example + + .. literalinclude:: ../code_examples/pyagentspec_example.py + :language: python + :start-after: .. start-code: + :end-before: .. end-code + +.. collapse:: Flow's representation + + .. literalinclude:: ../agentspec_config_examples/pyagentspec_example_config.json + :language: json + +.. _sdk-agent-spec_nightly: + +SDKs for consuming/producing Agent Spec +======================================= + +A SDK for Agent Spec would be developed, to guarantee adherence to the +specification, as well as providing easy to use APIs to +serialize/deserialize Agent Spec representation, or create them from code. + +All SDKs should provide the following: + +- classes to represent components +- APIs to help with serialization/deserialization + +PyAgentSpec represents the implementation of an Agent Spec SDK in python. +The API documentation of PyAgentSpec is available at :doc:`this link <../api/index>`. + + +Serialization/deserialization APIs +---------------------------------- + +The serialization/deserialization would be served by the following APIs + +.. code-block:: python + + class AgentSpecSerializer: + def __init__(self, plugins: Optional[List[ComponentSerializationPlugin]] = None) -> None: + ... + + def to_json(self, component: Component) -> str: + # gets the JSON string representation for the component + pass + + class AgentSpecDeserializer: + def __init__(self, plugins: Optional[List[ComponentDeserializationPlugin]] = None) -> None: + ... + + def from_json(self, json_content: str) -> Component: + # creates a component, given its JSON string representation + pass + +So that it is possible to execute the following code: + +.. code-block:: python + + # Create a Python object Agent Spec flow + node_1 = StartNode(id="NODE_1", name="node_1") + node_2 = LlmNode(id="NODE_2", name="node_2", prompt_template="Hi!") + end_node = EndNode(id="end_node", name="End node") + + control_flow_edges = [ + ControlFlowEdge(id="1to2", name="1->2", from_node=node_1, to_node=node_2), + ControlFlowEdge(id="2toend", name="2->end", from_node=node_2, to_node=end_node), + ] + + flow = Flow( + id="FLOW_1", + name="My Flow", + start_node=node_1, + nodes=[node_1, node_2, end_node], + control_flow_connections=control_flow_edges, + data_flow_connections=[], + ) + + # Serialize it to JSON + serializer = AgentSpecSerializer() + serialized_flow = serializer.to_json(flow) + +and the converse for deserialization. + +The documentation of PyAgentSpec serialization is available at :doc:`this link <../api/serialization>`. + + +.. |image2| image:: ../_static/agentspec_spec_img/standalone_agent.png +.. |image3| image:: ../_static/agentspec_spec_img/standalone_flow.png +.. |image4| image:: ../_static/agentspec_spec_img/agent_calling_flows.png +.. |image5| image:: ../_static/agentspec_spec_img/flow_calling_agent.png +.. |image6| image:: ../_static/agentspec_spec_img/flow_io_example.png +.. |image7| image:: ../_static/agentspec_spec_img/flow_connections.png diff --git a/docs/pyagentspec/source/agentspec/language_spec_nightly.rst b/docs/pyagentspec/source/agentspec/language_spec_nightly.rst index 80aeb86d..fc3d9dad 100644 --- a/docs/pyagentspec/source/agentspec/language_spec_nightly.rst +++ b/docs/pyagentspec/source/agentspec/language_spec_nightly.rst @@ -617,6 +617,49 @@ Null value is equivalent to an empty dictionary, i.e., no default generation par and ``api_type`` values. Specific extensions of ``LlmConfig`` for the most common providers are also provided for convenience, offering additional provider-specific configuration options. +DBMS Vector Chain +^^^^^^^^^^^^^^^^^ + +``DbmsVectorChainLlmConfig`` configures an LLM request executed by Oracle +Database through ``DBMS_VECTOR_CHAIN``. + +.. code-block:: python + + class DbmsVectorChainLlmConfig(LlmConfig): + model_id: str + provider: str + url: str + credential_name: Optional[str] + host: Optional[Literal["local"]] + transfer_timeout: Optional[int] + connection_config: Optional[OracleDatabaseConnectionConfig] + +The ``connection_config`` field is an optional Oracle Database connection +configuration. If ``connection_config`` is not specified, the runtime must use +an existing Oracle Database connection from its execution context. If no +connection is available, the runtime must raise an error. + +The ``host`` field can only be set to ``"local"`` and is supported only when +``provider`` is ``"openai"`` or ``"ollama"``. It indicates that the provider +is running locally and disables database credential authentication. When +``host`` is specified, ``credential_name`` must be omitted. + +The ``transfer_timeout`` field specifies the maximum number of seconds to wait +for the provider request to complete. If it is not specified, +``DBMS_VECTOR_CHAIN`` uses its default timeout of 60 seconds. + +The runtime maps these fields to the corresponding ``DBMS_VECTOR_CHAIN`` +parameters: ``model_id`` to ``model``, and ``provider``, ``url``, +``credential_name``, ``host``, and ``transfer_timeout`` to parameters with the +same names. ``connection_config`` configures the runtime's database connection +and is not a ``DBMS_VECTOR_CHAIN`` parameter. + +For a remote provider, ``credential_name`` identifies a credential created in +Oracle Database with ``DBMS_VECTOR_CHAIN.CREATE_CREDENTIAL``. For a local +OpenAI or Ollama provider, set ``host`` to ``"local"`` and omit +``credential_name``. Refer to the `DBMS_VECTOR_CHAIN documentation `_ +for supported providers and provider-specific parameters. + Structured Generation ^^^^^^^^^^^^^^^^^^^^^ @@ -2227,6 +2270,9 @@ The ManagerWorkers has two main parameters: - Workers cannot interact with the end user directly. - When invoked, each worker can leverage its equipped tools to complete the assigned task and report the result back to the group manager. +The ``ManagerWorkers`` input and output schemas must match those of its ``group_manager``. +In particular, the two components must declare the same input property names and the same output property names, +and each corresponding property must have the same type. Datastores ~~~~~~~~~~ @@ -2409,7 +2455,7 @@ For example, the JSON serialized version of an Agent should look like the follow "url": "my.llm.url" } "tools": [], - "agentspec_version": "26.1.2" + "agentspec_version": "26.4.0" } For release versioning, Agent Spec follows the format YEAR.QUARTER.PATCH. Agent Spec follows a @@ -3218,7 +3264,7 @@ We put here the current JSON spec of the Agent Spec language. .. collapse:: JSON Schema - .. literalinclude:: json_spec/agentspec_json_spec_26_2_0.json + .. literalinclude:: json_spec/agentspec_json_spec_26_4_0.json :language: json Note about serialization of components @@ -3301,7 +3347,7 @@ Examples of JSON serialization for a few common Components follow. "system_prompt": "You are an expert in {{domain_of_expertise}}. Please help the users with their requests.", "tools": [], "component_type": "Agent", - "agentspec_version": "26.1.2" + "agentspec_version": "26.4.0" } diff --git a/docs/pyagentspec/source/agentspec_config_examples/howto_managerworkers.json b/docs/pyagentspec/source/agentspec_config_examples/howto_managerworkers.json index 1159e7f9..221088a6 100644 --- a/docs/pyagentspec/source/agentspec_config_examples/howto_managerworkers.json +++ b/docs/pyagentspec/source/agentspec_config_examples/howto_managerworkers.json @@ -4,7 +4,16 @@ "name": "managerworkers", "description": null, "metadata": {}, - "inputs": [], + "inputs": [ + { + "title": "customer_id", + "type": "string" + }, + { + "title": "company_policy_info", + "type": "string" + } + ], "outputs": [], "group_manager": { "component_type": "Agent", @@ -208,5 +217,5 @@ "model_id": "llama-4-maverick" } }, - "agentspec_version": "26.1.0" + "agentspec_version": "26.4.0" } diff --git a/docs/pyagentspec/source/agentspec_config_examples/howto_managerworkers.yaml b/docs/pyagentspec/source/agentspec_config_examples/howto_managerworkers.yaml index fb74f087..01bb633f 100644 --- a/docs/pyagentspec/source/agentspec_config_examples/howto_managerworkers.yaml +++ b/docs/pyagentspec/source/agentspec_config_examples/howto_managerworkers.yaml @@ -9,7 +9,11 @@ id: 248045cb-ca6f-4d6f-9e22-d28b452a25da name: managerworkers description: null metadata: {} -inputs: [] +inputs: +- title: company_policy_info + type: string +- title: customer_id + type: string outputs: [] group_manager: component_type: Agent @@ -225,4 +229,4 @@ $referenced_components: default_generation_parameters: null url: http://url.to.my.vllm.server/llama4mav model_id: llama-4-maverick -agentspec_version: 26.1.0 +agentspec_version: 26.4.0 diff --git a/docs/pyagentspec/source/api/llmmodels.rst b/docs/pyagentspec/source/api/llmmodels.rst index c1db63fd..c17c8997 100644 --- a/docs/pyagentspec/source/api/llmmodels.rst +++ b/docs/pyagentspec/source/api/llmmodels.rst @@ -11,6 +11,14 @@ LlmConfig :exclude-members: model_post_init, model_config +DBMS Vector Chain LLM Config +---------------------------- + +.. _dbmsvectorchainllmconfig: +.. autoclass:: pyagentspec.llms.dbmsvectorchainllmconfig.DbmsVectorChainLlmConfig + :exclude-members: model_post_init, model_config + + LLM Generation Config --------------------- diff --git a/docs/pyagentspec/source/changelog.rst b/docs/pyagentspec/source/changelog.rst index db693b2a..c9ceb2b5 100644 --- a/docs/pyagentspec/source/changelog.rst +++ b/docs/pyagentspec/source/changelog.rst @@ -4,9 +4,36 @@ Changelog Agent Spec |release| -------------------- +New features +^^^^^^^^^^^^ + +Improvements +^^^^^^^^^^^^ + +* ** Added back the CrewAI adapter** + + The CrewAI adapter has been added back. + +Bug fixes +^^^^^^^^^ + +Breaking Changes +^^^^^^^^^^^^^^^^ + +Agent Spec 26.3.0 +----------------- + Improvements ^^^^^^^^^^^^ +* **Updated Microsoft Agent Framework dependency** + + Updated ``agent-framework`` to version ``1.13.0``. + +* **Updated OpenAI Agents SDK dependency** + + Updated ``openai-agents`` to version ``0.19.4``. + * **Sensitive field export opt-in** Serializers now support ``include_sensitive_fields`` to include sensitive field values in @@ -39,6 +66,30 @@ Bug fixes New features ^^^^^^^^^^^^ +* **DBMS Vector Chain LLM configuration** + + Added ``DbmsVectorChainLlmConfig`` for configuring LLM requests executed + through Oracle Database ``DBMS_VECTOR_CHAIN``. + +* **ManagerWorkers I/O update** + + The ManagerWorkers language specification now requires its input and output + schemas to use the same property names and types as its group manager. + This allows exposing inputs required by the manager agent (e.g., the placeholders + in its system prompt). + + We thank @spichen for the contribution! + +* **ManagerWorkers support in the LangGraph adapter** + + The LangGraph adapter now converts ``ManagerWorkers`` into hierarchical graphs: + the group manager delegates tasks to workers and receives their results before + producing a final response. Nested ``ManagerWorkers`` can be used as workers. + The adapter also supports ``ManagerWorkers`` in Flow ``AgentNode`` steps with + one string output only. + + We thank @spichen for the contribution! + * **MCP tool retry policies** Added ``retry_policy`` support to ``MCPTool`` and ``MCPToolBox`` so runtimes can @@ -48,6 +99,10 @@ New features Breaking Changes ^^^^^^^^^^^^^^^^ +* **Removed CrewAI adapter** + + The CrewAI adapter and its optional dependencies have been removed from ``pyagentspec`` due to a CVE in a pinned dependency of CrewAI (Chroma DB 1.1.0) at the time of our release. + Agent Spec 26.1.2 ----------------- diff --git a/docs/pyagentspec/source/code_examples/adapter_agent_framework_quickstart.py b/docs/pyagentspec/source/code_examples/adapter_agent_framework_quickstart.py index 537cf3dc..961a804f 100644 --- a/docs/pyagentspec/source/code_examples/adapter_agent_framework_quickstart.py +++ b/docs/pyagentspec/source/code_examples/adapter_agent_framework_quickstart.py @@ -48,7 +48,7 @@ def subtract(a: float, b: float) -> float: return a - b async def main(): - from agent_framework import TextContent + from agent_framework import Content loader = AgentSpecLoader(tool_registry={"subtraction-tool": subtract}) assistant = loader.load_json(agentspec_config) @@ -58,7 +58,7 @@ async def main(): break result = await assistant.run(user_input) agent_message = result.messages[-1].contents[-1] - if not isinstance(agent_message, TextContent): + if not isinstance(agent_message, Content): raise ValueError(f"Unexpected agent_message type {type(agent_message)}") print(f"AGENT >> {agent_message.text}") @@ -69,7 +69,7 @@ async def main(): # .. end-agentspec_to_runtime # .. start-runtime_to_agentspec # Create an Agent Framework Agent -from agent_framework import ChatAgent, tool +from agent_framework import Agent, tool from agent_framework.openai import OpenAIChatClient @tool() @@ -85,11 +85,11 @@ def get_weather(city: str) -> str: """ return f"The weather in {city} is sunny." -agent_framework_agent = ChatAgent( - chat_client=OpenAIChatClient( +agent_framework_agent = Agent( + client=OpenAIChatClient( api_key="ollama", base_url="url.to.agi.model", - model_id="agi_ollama_model", + model="agi_ollama_model", ), name="Weather Agent", instructions="You are a weather agent. Use the provided tool to get data related to the weather based on the city mentioned in the user query.", diff --git a/docs/pyagentspec/source/code_examples/adapter_openai_quickstart.py b/docs/pyagentspec/source/code_examples/adapter_openai_quickstart.py index 54ec20fc..ab3a0308 100644 --- a/docs/pyagentspec/source/code_examples/adapter_openai_quickstart.py +++ b/docs/pyagentspec/source/code_examples/adapter_openai_quickstart.py @@ -66,7 +66,7 @@ async def main(): # .. end-agentspec_to_runtime # .. start-runtime_to_agentspec # Create an OpenAI Agent -from agents.agent import Agent, function_tool +from agents import Agent, function_tool @function_tool def subtraction_tool(a: float, b: float) -> float: diff --git a/docs/pyagentspec/source/code_examples/howto_llm_from_different_providers.py b/docs/pyagentspec/source/code_examples/howto_llm_from_different_providers.py index 44544b42..f1ab2a3e 100644 --- a/docs/pyagentspec/source/code_examples/howto_llm_from_different_providers.py +++ b/docs/pyagentspec/source/code_examples/howto_llm_from_different_providers.py @@ -23,6 +23,19 @@ ) # .. llmconfig-end +# .. dbmsvectorchain-start +from pyagentspec.llms import DbmsVectorChainLlmConfig + +llm = DbmsVectorChainLlmConfig( + name="database-openai", + model_id="gpt-4o", + provider="openai", + url="https://api.openai.com/v1/chat/completions", + credential_name="MY_OPENAI_CREDENTIAL", + transfer_timeout=120, +) +# .. dbmsvectorchain-end + # .. oci-start from pyagentspec.llms import OciGenAiConfig from pyagentspec.llms import LlmGenerationConfig diff --git a/docs/pyagentspec/source/docs_home.rst b/docs/pyagentspec/source/docs_home.rst index 78a752e6..9a4ee17d 100644 --- a/docs/pyagentspec/source/docs_home.rst +++ b/docs/pyagentspec/source/docs_home.rst @@ -118,6 +118,7 @@ Agent Spec is developed jointly between Oracle Cloud Infrastructure and Oracle L :caption: Adapters :hidden: + Oracle Select AI LangGraph WayFlow CrewAI @@ -125,7 +126,6 @@ Agent Spec is developed jointly between Oracle Cloud Infrastructure and Oracle L Agent Framework OpenAI Agents - .. toctree:: :maxdepth: 1 :caption: Ecosystem diff --git a/docs/pyagentspec/source/howtoguides/howto_llm_from_different_providers.rst b/docs/pyagentspec/source/howtoguides/howto_llm_from_different_providers.rst index d0f393ce..89294df5 100644 --- a/docs/pyagentspec/source/howtoguides/howto_llm_from_different_providers.rst +++ b/docs/pyagentspec/source/howtoguides/howto_llm_from_different_providers.rst @@ -7,6 +7,7 @@ field to describe any provider, or you can use a dedicated subclass for provider The available LLM configurations are: - :ref:`LlmConfig ` (generic, provider-agnostic) +- :ref:`DbmsVectorChainLlmConfig ` - :ref:`OpenAiConfig ` - :ref:`GeminiConfig ` - :ref:`OciGenAiConfig ` @@ -81,6 +82,73 @@ or when you want a simple, portable configuration. :end-before: .. llmconfig-end +DbmsVectorChainLlmConfig +======================== + +Use ``DbmsVectorChainLlmConfig`` when Oracle Database executes the LLM request +through ``DBMS_VECTOR_CHAIN``. It refers to a credential managed by the +database. + +Refer to the `DBMS_VECTOR_CHAIN documentation `_ +for supported ``provider`` values and provider-specific configuration +requirements. + +``credential_name`` is the name of an existing database credential, not an API +key or credential value. Create the credential with +``DBMS_VECTOR_CHAIN.CREATE_CREDENTIAL``; refer to the linked documentation for +credential creation instructions. For a remote provider, specify +``credential_name`` and omit ``host``. For a local OpenAI or Ollama provider, +specify ``host="local"`` and omit ``credential_name``. + +**Parameters** + +.. option:: model_id: str + + Name of the text-generation model used by the database. + +.. option:: provider: str + + Database-supported LLM provider, such as ``openai``, ``cohere``, or + ``ollama``. + +.. option:: url: str + + Provider endpoint URL used by Oracle Database. + +.. option:: credential_name: str, null + + Name of a credential created in Oracle Database with + ``DBMS_VECTOR_CHAIN.CREATE_CREDENTIAL``. Required unless ``host`` is set. + +.. option:: host: "local", null + + Set to ``"local"`` for a local OpenAI or Ollama provider. When set, omit + ``credential_name``. + +.. option:: transfer_timeout: int, null + + Maximum number of seconds the database waits for the provider request. + +.. option:: connection_config: OracleDatabaseConnectionConfig, null + + Optional Oracle Database connection configuration. + +.. option:: default_generation_parameters: dict, null + + Default generation settings, such as ``temperature`` and ``max_tokens``. + +.. option:: retry_policy: dict, null + + Agent Spec retry settings for recoverable LLM-request failures. + +**Examples** + +.. literalinclude:: ../code_examples/howto_llm_from_different_providers.py + :language: python + :start-after: .. dbmsvectorchain-start + :end-before: .. dbmsvectorchain-end + + OciGenAiConfig ============== diff --git a/docs/pyagentspec/source/oracle_select_ai.rst b/docs/pyagentspec/source/oracle_select_ai.rst new file mode 100644 index 00000000..0e5ddf9c --- /dev/null +++ b/docs/pyagentspec/source/oracle_select_ai.rst @@ -0,0 +1,22 @@ +Oracle Select AI +================ + +Import and Export Select AI Agent Teams +--------------------------------------- + +Oracle Autonomous AI Database supports importing and exporting Select AI agent team +definitions by using the ``DBMS_CLOUD_AI_AGENT.IMPORT_TEAM`` and +``DBMS_CLOUD_AI_AGENT.EXPORT_TEAM`` APIs. You can import agent team definitions from a +JSON specification, an Object Storage file, or a directory file, and export agent teams +as a CLOB or directly to Object Storage or a directory file. These APIs simplify +migration, backup, version control, and sharing of Select AI agent teams across +environments. + +See: + +* `Portable Oracle Select AI Agent Teams: From Development to Production `_ +* `What's New: Import and Export Select AI Agent Teams `_ +* `Example: Import and Export Agent Teams `_ +* `EXPORT_TEAM Function `_ +* `EXPORT_TEAM Procedure `_ +* `IMPORT_TEAM Procedure `_ diff --git a/pyagentspec/constraints/constraints.txt b/pyagentspec/constraints/constraints.txt index 23ab31e4..02838652 100644 --- a/pyagentspec/constraints/constraints.txt +++ b/pyagentspec/constraints/constraints.txt @@ -13,9 +13,9 @@ autogen-agentchat==0.7.4 crewai==1.6.1 # LangGraph adapter -langgraph==1.2.0 -langchain==1.3.1 -langchain-core==1.4.0 +langgraph==1.2.4 +langchain==1.3.9 +langchain-core==1.4.6 langchain-openai==1.2.1 langchain-ollama==1.0.1 langchain-oci==0.2.6 @@ -23,18 +23,19 @@ langchain-mcp-adapters==0.2.2 langgraph-swarm==0.1.0 # WayFlow adapter -wayflowcore==26.1.2 +wayflowcore==26.3.0 # AgentFramework adapter -agent-framework==1.0.0b260130 +agent-framework-core==1.13.0 +agent-framework-openai==1.12.0 # OpenAI Agents adapter -openai-agents==0.6.9 +openai-agents==0.19.4 libcst==1.8.5 # Evaluation pandas==2.3.3 -oci==2.174.0 +oci==2.184.2 litellm==1.84.0 numpy==2.2.6; python_version<"3.14" numpy==2.4.2; python_version>="3.14" diff --git a/pyagentspec/constraints/constraints_dev.txt b/pyagentspec/constraints/constraints_dev.txt index 23ab31e4..02838652 100644 --- a/pyagentspec/constraints/constraints_dev.txt +++ b/pyagentspec/constraints/constraints_dev.txt @@ -13,9 +13,9 @@ autogen-agentchat==0.7.4 crewai==1.6.1 # LangGraph adapter -langgraph==1.2.0 -langchain==1.3.1 -langchain-core==1.4.0 +langgraph==1.2.4 +langchain==1.3.9 +langchain-core==1.4.6 langchain-openai==1.2.1 langchain-ollama==1.0.1 langchain-oci==0.2.6 @@ -23,18 +23,19 @@ langchain-mcp-adapters==0.2.2 langgraph-swarm==0.1.0 # WayFlow adapter -wayflowcore==26.1.2 +wayflowcore==26.3.0 # AgentFramework adapter -agent-framework==1.0.0b260130 +agent-framework-core==1.13.0 +agent-framework-openai==1.12.0 # OpenAI Agents adapter -openai-agents==0.6.9 +openai-agents==0.19.4 libcst==1.8.5 # Evaluation pandas==2.3.3 -oci==2.174.0 +oci==2.184.2 litellm==1.84.0 numpy==2.2.6; python_version<"3.14" numpy==2.4.2; python_version>="3.14" diff --git a/pyagentspec/constraints/constraints_v26.3.0.txt b/pyagentspec/constraints/constraints_v26.3.0.txt new file mode 100644 index 00000000..64e5d8fb --- /dev/null +++ b/pyagentspec/constraints/constraints_v26.3.0.txt @@ -0,0 +1,38 @@ +jsonschema==4.23.0 +pydantic==2.12.5 +pyyaml==6.0.3 +httpx==0.28.1 +typing-extensions==4.15.0 +anyio==4.11.0 +# AutoGen adapter +autogen-core==0.7.4 +autogen-ext==0.7.4 +autogen-agentchat==0.7.4 + +# LangGraph adapter +langgraph==1.2.4 +langchain==1.3.9 +langchain-core==1.4.6 +langchain-openai==1.2.1 +langchain-ollama==1.0.1 +langchain-oci==0.2.6 +langchain-mcp-adapters==0.2.2 +langgraph-swarm==0.1.0 + +# WayFlow adapter +wayflowcore==26.3.0 + +# AgentFramework adapter +agent-framework-core==1.13.0 +agent-framework-openai==1.12.0 + +# OpenAI Agents adapter +openai-agents==0.19.4 +libcst==1.8.5 + +# Evaluation +pandas==2.3.3 +oci==2.184.2 +litellm==1.84.0 +numpy==2.2.6; python_version<"3.14" +numpy==2.4.2; python_version>="3.14" diff --git a/pyagentspec/pyproject.toml b/pyagentspec/pyproject.toml index 63e9d7f1..faa2b7e1 100644 --- a/pyagentspec/pyproject.toml +++ b/pyagentspec/pyproject.toml @@ -1,7 +1,7 @@ [build-system] requires = [ - # lower bound 78.1.1 to address CVE-2025-47273 - "setuptools>=78.1.1,<82.0.0", + # Keep setuptools on the patched 83.x release line. + "setuptools>=83.0.0,<90.0.0", "wheel", # setuptools-scm lets us include all necessary files (e.g. .pyx files) in the source # distribution (sdist) without needing a MANIFEST.in file. diff --git a/pyagentspec/requirements-dev-common.txt b/pyagentspec/requirements-dev-common.txt index 419145fe..3d8efdeb 100644 --- a/pyagentspec/requirements-dev-common.txt +++ b/pyagentspec/requirements-dev-common.txt @@ -13,7 +13,7 @@ flake8-copyright==0.2.4 pyflakes==2.5.0 pytest==9.0.3 pytest-asyncio==1.3.0 -setuptools==81.0.0 +setuptools==83.0.0 # stubs for mypy types-PyYAML diff --git a/pyagentspec/setup.py b/pyagentspec/setup.py index b35e807f..0a79644e 100644 --- a/pyagentspec/setup.py +++ b/pyagentspec/setup.py @@ -27,9 +27,9 @@ def read(file_name): LANGGRAPH_DEPS = [ # 3rd party dependencies (imported in code) - "langgraph>=1.2.0,<1.3.0", - "langchain-core>=1.4.0,<1.5.0", - "langchain>=1.3.1,<1.4.0", + "langgraph>=1.2.4,<1.3.0", + "langchain-core>=1.4.6,<2.0.0", + "langchain>=1.3.9,<2.0.0", "langchain-openai>=1.2.1,<1.3.0", "langchain-ollama>=1.0.1", "anyio>=4.10.0,<4.12.0", @@ -37,9 +37,10 @@ def read(file_name): "langgraph-swarm>=0.1.0", # 4rth party dependencies "certifi>=2025.1.31", # needed to avoid CVE present in earlier versions - "langgraph-checkpoint>=4.0.1,<5.0.0", # needed to avoid CVE present in earlier versions - "langsmith>=0.8.0,<1.0.0", # needed to avoid CVE present in earlier versions + "langgraph-checkpoint>=4.1.1,<5.0.0", # needed to avoid CVE present in earlier versions + "langsmith>=0.8.18,<1.0.0", # needed to avoid CVE present in earlier versions "urllib3>=2.7.0", # needed to avoid CVE present in earlier versions + "langgraph-sdk>=0.3.15", # needed to avoid CVE present in earlier versions ] LANGGRAPH_FULL_DEPS = LANGGRAPH_DEPS + [ @@ -47,7 +48,7 @@ def read(file_name): "langchain-mcp-adapters>=0.2.2", "langchain-oci>=0.2.6", # 4rth party dependencies - "cryptography>=46.0.7", # needed to avoid CVE present in earlier versions + "cryptography>=50.0.0", # needed to avoid CVE present in earlier versions "pyOpenSSL>=26.0.0,<27.0.0", # needed to avoid CVE present in earlier versions ] @@ -100,15 +101,16 @@ def read(file_name): # 4rth party dependencies "certifi>=2025.1.31; python_version < '3.13'", # needed to avoid CVE present in earlier versions "urllib3>=2.7.0; python_version < '3.13'", # needed to avoid CVE present in earlier versions + "pillow>=12.3.0; python_version < '3.13'", # needed to avoid CVE present in earlier versions ], "openai-agents": [ # 3rd party dependencies (imported in code) - "openai-agents>=0.6.9", + "openai-agents>=0.19.0", "libcst>=1.5,<2", "httpx>0.28.0", # 4rth party dependencies "certifi>=2025.1.31", # needed to avoid CVE present in earlier versions - "cryptography>=46.0.7", # needed to avoid CVE present in earlier versions + "cryptography>=50.0.0", # needed to avoid CVE present in earlier versions "urllib3>=2.7.0", # needed to avoid CVE present in earlier versions ], "crewai": [ @@ -117,64 +119,61 @@ def read(file_name): "httpx>0.28.0; python_version < '3.14'", # 4rth party dependencies "certifi>=2025.1.31; python_version < '3.14'", # needed to avoid CVE present in earlier versions - "cryptography>=46.0.7; python_version < '3.14'", # needed to avoid CVE present in earlier versions + "cryptography>=50.0.0; python_version < '3.14'", # needed to avoid CVE present in earlier versions # litellm is included to fix CVEs "litellm>=1.84.0,<2.0; python_version < '3.14'", "urllib3>=2.7.0; python_version < '3.14'", # needed to avoid CVE present in earlier versions + "chromadb>1.5.9; python_version < '3.14'", # # needed to avoid CVE present in earlier versions + "uv>=0.11.15; python_version < '3.14'", # needed to avoid CVE present in earlier versions ], "langgraph": LANGGRAPH_DEPS, "langgraph-full": LANGGRAPH_FULL_DEPS, "wayflow": [ # 3rd party dependencies (imported in code) - "wayflowcore>=26.1.2", + "wayflowcore>=26.3.0", # 4rth party dependencies "certifi>=2025.1.31; python_version < '3.14'", # needed to avoid CVE present in earlier versions - "cryptography>=46.0.7; python_version < '3.14'", # needed to avoid CVE present in earlier versions ], "wayflow_oci": [ # 3rd party dependencies (imported in code) - "wayflowcore[oci]>=26.1.2", + "wayflowcore[oci]>=26.3.0", # 4rth party dependencies "certifi>=2025.1.31; python_version < '3.14'", # needed to avoid CVE present in earlier versions - "cryptography>=46.0.7; python_version < '3.14'", # needed to avoid CVE present in earlier versions "pyOpenSSL>=26.0.0,<27.0.0; python_version < '3.14'", # needed to avoid CVE present in earlier versions "urllib3>=2.7.0; python_version < '3.14'", # needed to avoid CVE present in earlier versions ], "wayflow_a2a": [ # 3rd party dependencies (imported in code) - "wayflowcore[a2a]>=26.1.2", + "wayflowcore[a2a]>=26.3.0", # 4rth party dependencies "certifi>=2025.1.31; python_version < '3.14'", # needed to avoid CVE present in earlier versions - "cryptography>=46.0.7; python_version < '3.14'", # needed to avoid CVE present in earlier versions ], "wayflow_datastore": [ # 3rd party dependencies (imported in code) - "wayflowcore[datastore]>=26.1.2", + "wayflowcore[datastore]>=26.3.0", # 4rth party dependencies "certifi>=2025.1.31; python_version < '3.14'", # needed to avoid CVE present in earlier versions - "cryptography>=46.0.7; python_version < '3.14'", # needed to avoid CVE present in earlier versions ], "agent-framework": [ # 3rd party dependencies (imported in code) - "agent-framework>=1.0.0b260130; python_version < '3.14'", - "httpx>0.28.0; python_version < '3.14'", + "agent-framework-core>=1.10.0", + "agent-framework-openai>=1.10.0", + "httpx>0.28.0", # 4rth party dependencies - "certifi>=2025.1.31; python_version < '3.14'", # needed to avoid CVE present in earlier versions - "cryptography>=46.0.7; python_version < '3.14'", # needed to avoid CVE present in earlier versions - # including otel-semconv-ai to address internal agent-framework bug - "opentelemetry-semantic-conventions-ai<0.4.14", - "urllib3>=2.7.0; python_version < '3.14'", # needed to avoid CVE present in earlier versions + "certifi>=2025.1.31", # needed to avoid CVE present in earlier versions + "cryptography>=50.0.0", # needed to avoid CVE present in earlier versions + "urllib3>=2.7.0", # needed to avoid CVE present in earlier versions ], "evaluation": [ # 3rd party dependencies (imported in code) "anyio>=4.10.0,<4.12.0", "litellm>=1.84.0,<2.0; python_version < '3.14'", "pandas>=2.3.0,<3.0.0", - "oci>=2.158.2", + "oci>=2.184.2", "numpy>=2.2.6", # 4rth party dependencies "certifi>=2025.1.31", # needed to avoid CVE present in earlier versions - "cryptography>=46.0.7", # needed to avoid CVE present in earlier versions + "cryptography>=50.0.0", # needed to avoid CVE present in earlier versions "pyOpenSSL>=26.0.0,<27.0.0", # needed to avoid CVE present in earlier versions "urllib3>=2.7.0", # needed to avoid CVE present in earlier versions ], diff --git a/pyagentspec/src/pyagentspec/_component_registry.py b/pyagentspec/src/pyagentspec/_component_registry.py index be871a30..783191a4 100644 --- a/pyagentspec/src/pyagentspec/_component_registry.py +++ b/pyagentspec/src/pyagentspec/_component_registry.py @@ -44,6 +44,7 @@ ToolNode, ) from pyagentspec.llms import ( + DbmsVectorChainLlmConfig, GeminiConfig, OciGenAiConfig, OllamaConfig, @@ -105,6 +106,7 @@ "ControlFlowEdge": ControlFlowEdge, "DataFlowEdge": DataFlowEdge, "Datastore": Datastore, + "DbmsVectorChainLlmConfig": DbmsVectorChainLlmConfig, "EndNode": EndNode, "Flow": Flow, "FlowNode": FlowNode, diff --git a/pyagentspec/src/pyagentspec/adapters/agent_framework/_agentframeworkconverter.py b/pyagentspec/src/pyagentspec/adapters/agent_framework/_agentframeworkconverter.py index 5cf6fd21..7e827375 100644 --- a/pyagentspec/src/pyagentspec/adapters/agent_framework/_agentframeworkconverter.py +++ b/pyagentspec/src/pyagentspec/adapters/agent_framework/_agentframeworkconverter.py @@ -10,22 +10,23 @@ from pyagentspec.adapters._tools_common import _create_remote_tool_func from pyagentspec.adapters.agent_framework._types import ( + Agent, AgentFrameworkComponent, AgentFrameworkMCPTool, AgentFrameworkTool, BaseChatClient, - ChatAgent, FunctionTool, MCPStdioTool, MCPStreamableHTTPTool, OpenAIChatClient, + OpenAIChatCompletionClient, ) from pyagentspec.agent import Agent as AgentSpecAgent from pyagentspec.component import Component as AgentSpecComponent from pyagentspec.llms.llmconfig import LlmConfig as AgentSpecLlmConfig from pyagentspec.llms.llmgenerationconfig import LlmGenerationConfig from pyagentspec.llms.ollamaconfig import OllamaConfig -from pyagentspec.llms.openaicompatibleconfig import OpenAiCompatibleConfig +from pyagentspec.llms.openaicompatibleconfig import OpenAIAPIType, OpenAiCompatibleConfig from pyagentspec.llms.openaiconfig import OpenAiConfig from pyagentspec.mcp.tools import MCPTool as AgentSpecMCPTool from pyagentspec.property import Property as AgentSpecProperty @@ -234,6 +235,19 @@ def _llm_convert_to_agent_framework( tool_registry: dict[str, AgentFrameworkTool], converted_components: dict[str, AgentFrameworkComponent], ) -> BaseChatClient: + def openai_client(**kwargs: Any) -> BaseChatClient: + if llm_config.api_type in (OpenAIAPIType.RESPONSES, "responses"): + return OpenAIChatClient(**kwargs) + if llm_config.api_type in ( + None, + OpenAIAPIType.CHAT_COMPLETIONS, + "chat_completions", + ): + return OpenAIChatCompletionClient(**kwargs) + raise NotImplementedError( + f"LlmConfig with api_type='{llm_config.api_type}' is not supported in agent_framework" + ) + if isinstance(llm_config, OpenAiCompatibleConfig): from urllib.parse import urljoin @@ -242,30 +256,28 @@ def _llm_convert_to_agent_framework( base_url = f"http://{base_url}" if "/v1" not in base_url: base_url = urljoin(base_url + "/", "v1") - return OpenAIChatClient( + return openai_client( api_key="openai", base_url=base_url, - model_id=llm_config.model_id, + model=llm_config.model_id, ) elif isinstance(llm_config, OllamaConfig): - return OpenAIChatClient( + return openai_client( api_key="ollama", base_url=llm_config.url, - model_id=llm_config.model_id, + model=llm_config.model_id, ) elif isinstance(llm_config, OpenAiConfig): - return OpenAIChatClient( - model_id=llm_config.model_id, - ) + return openai_client(model=llm_config.model_id) else: # Bare LlmConfig — dispatch on api_provider string if llm_config.api_provider == "openai": - kwargs: dict[str, Any] = {"model_id": llm_config.model_id} + kwargs: dict[str, Any] = {"model": llm_config.model_id} if llm_config.url is not None: kwargs["base_url"] = llm_config.url if llm_config.api_key is not None: kwargs["api_key"] = llm_config.api_key - return OpenAIChatClient(**kwargs) + return openai_client(**kwargs) raise NotImplementedError( f"LlmConfig with api_provider='{llm_config.api_provider}' is not yet supported " f"in agent_framework. Consider using a specific LlmConfig subclass instead." @@ -283,14 +295,16 @@ def _agent_convert_to_agent_framework( chat_client = self.convert(agent.llm_config, tool_registry, converted_components) tools = [self.convert(tool, tool_registry, converted_components) for tool in agent.tools] prompt = agent.system_prompt - return ChatAgent( + return Agent( id=agent.id, name=agent.name, description=agent.description, - chat_client=cast(BaseChatClient, chat_client), + client=cast(BaseChatClient, chat_client), tools=cast(AgentFrameworkTool, tools), instructions=prompt, - temperature=generation_parameters.temperature, - top_p=generation_parameters.top_p, - max_tokens=generation_parameters.max_tokens, + additional_properties=dict( + temperature=generation_parameters.temperature, + top_p=generation_parameters.top_p, + max_tokens=generation_parameters.max_tokens, + ), ) diff --git a/pyagentspec/src/pyagentspec/adapters/agent_framework/_agentspecconverter.py b/pyagentspec/src/pyagentspec/adapters/agent_framework/_agentspecconverter.py index 22b41960..59dc56aa 100644 --- a/pyagentspec/src/pyagentspec/adapters/agent_framework/_agentspecconverter.py +++ b/pyagentspec/src/pyagentspec/adapters/agent_framework/_agentspecconverter.py @@ -9,20 +9,21 @@ from pyagentspec.adapters._utils import _get_obj_reference from pyagentspec.adapters.agent_framework._types import ( + Agent, AgentFrameworkLlmConfig, AgentFrameworkMCPTool, AgentFrameworkTool, - ChatAgent, FunctionTool, MCPStdioTool, MCPStreamableHTTPTool, OpenAIChatClient, + OpenAIChatCompletionClient, ) from pyagentspec.agent import Agent as AgentSpecAgent from pyagentspec.component import Component as AgentSpecComponent from pyagentspec.llms.llmconfig import LlmConfig from pyagentspec.llms.llmgenerationconfig import LlmGenerationConfig -from pyagentspec.llms.openaicompatibleconfig import OpenAiCompatibleConfig +from pyagentspec.llms.openaicompatibleconfig import OpenAIAPIType, OpenAiCompatibleConfig from pyagentspec.mcp.clienttransport import StdioTransport, StreamableHTTPTransport from pyagentspec.mcp.tools import MCPTool from pyagentspec.property import Property as AgentSpecProperty @@ -92,7 +93,7 @@ def convert( # If we did not find the object, we create it, and we record it in the referenced_objects registry agentspec_component: AgentSpecComponent - if isinstance(runtime_component, ChatAgent): + if isinstance(runtime_component, Agent): agentspec_component = self._agent_convert_to_agentspec( runtime_component, referenced_objects, @@ -187,22 +188,27 @@ def _llm_convert_to_agentspec( chat_client: AgentFrameworkLlmConfig, referenced_objects: dict[str, AgentSpecComponent], ) -> OpenAiCompatibleConfig: - if isinstance(chat_client, OpenAIChatClient): - if chat_client.model_id is None: + if isinstance(chat_client, (OpenAIChatClient, OpenAIChatCompletionClient)): + if chat_client.model is None: # Defensive check for None in some versions due to fast iteration # Once the framework stabilizes and the type is set in stone this check can be removed - raise ValueError(f"model_id for {type(chat_client)} is not set.") + raise ValueError(f"model for {type(chat_client)} is not set.") return OpenAiCompatibleConfig( - name=chat_client.model_id, - model_id=chat_client.model_id, + name=chat_client.model, + model_id=chat_client.model, url=chat_client.service_url(), + api_type=( + OpenAIAPIType.RESPONSES + if isinstance(chat_client, OpenAIChatClient) + else OpenAIAPIType.CHAT_COMPLETIONS + ), ) else: raise NotImplementedError(f"Chat client {type(chat_client)} not supported") def _agent_convert_to_agentspec( self, - chat_agent: ChatAgent, + chat_agent: Agent, referenced_objects: dict[str, AgentSpecComponent], ) -> AgentSpecComponent: generation_config = LlmGenerationConfig( @@ -213,7 +219,7 @@ def _agent_convert_to_agentspec( llm_config = cast( LlmConfig, self.convert( - chat_agent.chat_client, + chat_agent.client, referenced_objects, ), ) diff --git a/pyagentspec/src/pyagentspec/adapters/agent_framework/_types.py b/pyagentspec/src/pyagentspec/adapters/agent_framework/_types.py index cf0d4295..e000441c 100644 --- a/pyagentspec/src/pyagentspec/adapters/agent_framework/_types.py +++ b/pyagentspec/src/pyagentspec/adapters/agent_framework/_types.py @@ -10,49 +10,50 @@ if TYPE_CHECKING: from agent_framework import ( + Agent, BaseChatClient, - ChatAgent, - ChatClientProtocol, ChatOptions, FunctionTool, MCPStdioTool, MCPStreamableHTTPTool, MCPWebsocketTool, - ToolProtocol, ) - from agent_framework.openai import OpenAIChatClient + from agent_framework.openai import OpenAIChatClient, OpenAIChatCompletionClient else: ChatOptions = LazyLoader("agent_framework").ChatOptions FunctionTool = LazyLoader("agent_framework").FunctionTool BaseChatClient = LazyLoader("agent_framework").BaseChatClient - ChatAgent = LazyLoader("agent_framework").ChatAgent - ChatClientProtocol = LazyLoader("agent_framework").ChatClientProtocol + Agent = LazyLoader("agent_framework").Agent MCPStdioTool = LazyLoader("agent_framework").MCPStdioTool MCPStreamableHTTPTool = LazyLoader("agent_framework").MCPStreamableHTTPTool MCPWebsocketTool = LazyLoader("agent_framework").MCPWebsocketTool - ToolProtocol = LazyLoader("agent_framework").ToolProtocol - OpenAIChatClient = LazyLoader("agent_framework.openai").OpenAIChatClient + _openai = LazyLoader("agent_framework.openai") + OpenAIChatClient = _openai.OpenAIChatClient + OpenAIChatCompletionClient = _openai.OpenAIChatCompletionClient -AgentFrameworkComponent: TypeAlias = ChatAgent +AgentFrameworkMCPTool: TypeAlias = MCPStdioTool | MCPStreamableHTTPTool | MCPWebsocketTool AgentFrameworkTool: TypeAlias = ( - ToolProtocol - | FunctionTool[Any, Any] + FunctionTool | Callable[..., Any] | MutableMapping[str, Any] - | Sequence[ToolProtocol | Callable[..., Any] | MutableMapping[str, Any]] + | Sequence[Callable[..., Any] | MutableMapping[str, Any]] + | AgentFrameworkMCPTool ) -AgentFrameworkLlmConfig: TypeAlias = BaseChatClient | ChatClientProtocol -AgentFrameworkMCPTool: TypeAlias = MCPStdioTool | MCPStreamableHTTPTool | MCPWebsocketTool +AgentFrameworkLlmConfig: TypeAlias = BaseChatClient +AgentFrameworkComponent: TypeAlias = Agent | AgentFrameworkTool | AgentFrameworkLlmConfig __all__ = [ + "AgentFrameworkComponent", + "AgentFrameworkLlmConfig", + "AgentFrameworkMCPTool", + "AgentFrameworkTool", "BaseChatClient", "FunctionTool", - "ChatAgent", - "ChatClientProtocol", + "Agent", "MCPStdioTool", "MCPStreamableHTTPTool", "MCPWebsocketTool", - "ToolProtocol", "OpenAIChatClient", + "OpenAIChatCompletionClient", "ChatOptions", ] diff --git a/pyagentspec/src/pyagentspec/adapters/autogen/_autogenconverter.py b/pyagentspec/src/pyagentspec/adapters/autogen/_autogenconverter.py index ea46d27d..3e15b9f9 100644 --- a/pyagentspec/src/pyagentspec/adapters/autogen/_autogenconverter.py +++ b/pyagentspec/src/pyagentspec/adapters/autogen/_autogenconverter.py @@ -204,7 +204,8 @@ def _prepare_llm_args(agentspec_llm_: AgentSpecLlmConfig) -> Dict[str, Any]: if agentspec_llm_.url else None ), - api_key=agentspec_llm_.api_key if agentspec_llm_.api_key else None, + # Use a fake API key to avoid `openai` failing immediately at parsing if no key is needed + api_key=agentspec_llm_.api_key if agentspec_llm_.api_key else "EMPTY", model_info=_prepare_model_info(agentspec_llm_), ) diff --git a/pyagentspec/src/pyagentspec/adapters/langgraph/__init__.py b/pyagentspec/src/pyagentspec/adapters/langgraph/__init__.py index baeb855b..fb75ddeb 100644 --- a/pyagentspec/src/pyagentspec/adapters/langgraph/__init__.py +++ b/pyagentspec/src/pyagentspec/adapters/langgraph/__init__.py @@ -6,10 +6,13 @@ """Agent Spec adapter for the LangGraph agentic framework.""" +from ._managerworkers import DELEGATE_TOOL_PREFIX, is_delegation_tool_name from .agentspecexporter import AgentSpecExporter from .agentspecloader import AgentSpecLoader __all__ = [ "AgentSpecLoader", "AgentSpecExporter", + "DELEGATE_TOOL_PREFIX", + "is_delegation_tool_name", ] diff --git a/pyagentspec/src/pyagentspec/adapters/langgraph/_execution_span.py b/pyagentspec/src/pyagentspec/adapters/langgraph/_execution_span.py new file mode 100644 index 00000000..1fe7e0bb --- /dev/null +++ b/pyagentspec/src/pyagentspec/adapters/langgraph/_execution_span.py @@ -0,0 +1,87 @@ +# Copyright © 2025, 2026 Oracle and/or its affiliates. +# +# This software is under the Apache License 2.0 +# (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0) or Universal Permissive License +# (UPL) 1.0 (LICENSE-UPL or https://oss.oracle.com/licenses/upl), at your option. + +"""Wrap a compiled graph's ``stream``/``astream`` in an Agent Spec execution span. + +Agent, Flow and ManagerWorkers graphs all need the same wrapper: open a span, emit a +Start event carrying the invocation inputs, yield the chunks the underlying stream +produces while remembering the last state chunk, then emit an End event built from +that final state. Only the span class and the two event payloads differ, so they come +in as factories. ``invoke``/``ainvoke`` need no patch; they use ``stream``/``astream`` +internally. +""" + +from typing import Any, AsyncGenerator, Callable, Dict, Generator + +from pyagentspec.adapters.langgraph._types import CompiledStateGraph + + +def _invocation_inputs(kwargs: Dict[str, Any]) -> Dict[str, Any]: + """The ``input=`` argument of the patched call, or ``{}`` when it isn't a dict.""" + inputs = kwargs.get("input", {}) + return inputs if isinstance(inputs, dict) else {} + + +def _final_state(chunk: Any, so_far: Any) -> Any: + """Fold one streamed chunk into the running "last state seen". + + State arrives as ``(namespace, state)`` tuples; other chunk shapes aren't + something to build the End event from, so they leave the fold untouched. + """ + return chunk[1] if isinstance(chunk, tuple) else so_far + + +async def _async_or_sync( + async_call: Callable[..., Any], sync_call: Callable[..., Any], *args: Any +) -> None: + """Await ``async_call``, falling back to ``sync_call`` for spans that don't + implement the async half of the tracing protocol.""" + try: + await async_call(*args) + except NotImplementedError: + sync_call(*args) + + +def patch_with_execution_span( + compiled_graph: CompiledStateGraph[Any, Any, Any], + make_span: Callable[[], Any], + make_start_event: Callable[[Dict[str, Any]], Any], + make_end_event: Callable[[Dict[str, Any]], Any], +) -> None: + """Monkey-patch ``compiled_graph.stream`` / ``.astream`` to run inside a span. + + ``make_start_event`` receives the invocation inputs; ``make_end_event`` + receives the final state chunk (``{}`` when the run produced none). + """ + original_stream = compiled_graph.stream + original_astream = compiled_graph.astream + + def patched_stream(*args: Any, **kwargs: Any) -> Generator[Any, Any, None]: + with make_span() as span: + span.add_event(make_start_event(_invocation_inputs(kwargs))) + state: Any = {} + for chunk in original_stream(*args, **kwargs): + yield chunk + state = _final_state(chunk, state) + span.add_event(make_end_event(state if isinstance(state, dict) else {})) + + async def patched_astream(*args: Any, **kwargs: Any) -> AsyncGenerator[Any, Any]: + span = make_span() + await _async_or_sync(span.start_async, span.start) + try: + start_event = make_start_event(_invocation_inputs(kwargs)) + await _async_or_sync(span.add_event_async, span.add_event, start_event) + state: Any = {} + async for chunk in original_astream(*args, **kwargs): + yield chunk + state = _final_state(chunk, state) + end_event = make_end_event(state if isinstance(state, dict) else {}) + await _async_or_sync(span.add_event_async, span.add_event, end_event) + finally: + await _async_or_sync(span.end_async, span.end) + + compiled_graph.stream = patched_stream # type: ignore[method-assign] + compiled_graph.astream = patched_astream # type: ignore[method-assign] diff --git a/pyagentspec/src/pyagentspec/adapters/langgraph/_langgraphconverter.py b/pyagentspec/src/pyagentspec/adapters/langgraph/_langgraphconverter.py index 7393e9c8..0ca55cb0 100644 --- a/pyagentspec/src/pyagentspec/adapters/langgraph/_langgraphconverter.py +++ b/pyagentspec/src/pyagentspec/adapters/langgraph/_langgraphconverter.py @@ -7,15 +7,15 @@ import inspect import logging +import os import sys from typing import ( TYPE_CHECKING, Any, - AsyncGenerator, Awaitable, Callable, Dict, - Generator, + Hashable, List, Optional, Tuple, @@ -36,6 +36,16 @@ _build_type_from_schema, create_pydantic_model_from_properties, ) +from pyagentspec.adapters.langgraph._execution_span import patch_with_execution_span +from pyagentspec.adapters.langgraph._managerworkers import ( + _MANAGER_NODE_KEY, + _append_workers_roster, + _make_manager_router, + _make_worker_delegation_tool, + _patch_with_manager_workers_execution_span, + _safe_node_name, + _wrap_worker_for_subgraph, +) from pyagentspec.adapters.langgraph._node_execution import ( NodeExecutor, extract_outputs_from_invoke_result, @@ -69,6 +79,7 @@ AgentSpecToolCallbackHandler, ) from pyagentspec.agent import Agent as AgentSpecAgent +from pyagentspec.agenticcomponent import AgenticComponent as AgentSpecAgenticComponent from pyagentspec.flows.edges import ControlFlowEdge as AgentSpecControlFlowEdge from pyagentspec.flows.edges import DataFlowEdge as AgentSpecDataFlowEdge from pyagentspec.flows.flow import Flow as AgentSpecFlow @@ -102,6 +113,7 @@ ) from pyagentspec.llms.openaiconfig import OpenAiConfig from pyagentspec.llms.vllmconfig import VllmConfig +from pyagentspec.managerworkers import ManagerWorkers as AgentSpecManagerWorkers from pyagentspec.mcp.clienttransport import ClientTransport as AgentSpecClientTransport from pyagentspec.mcp.clienttransport import SSEmTLSTransport as AgentSpecSSEmTLSTransport from pyagentspec.mcp.clienttransport import SSETransport as AgentSpecSSETransport @@ -273,6 +285,15 @@ def _convert( config=config, middleware=middleware, ) + elif isinstance(agentspec_component, AgentSpecManagerWorkers): + return self._manager_workers_convert_to_langgraph( + agentspec_component, + tool_registry=tool_registry, + converted_components=converted_components, + checkpointer=checkpointer, + config=config, + middleware=middleware, + ) elif isinstance(agentspec_component, AgentSpecLlmConfig): return self._llm_convert_to_langgraph(agentspec_component, config=config) elif isinstance(agentspec_component, AgentSpecClientTransport): @@ -474,87 +495,18 @@ def _find_property(properties: List[AgentSpecProperty], name: str) -> AgentSpecP "Prefer invoke/stream or upgrade to Python 3.11+ for ainvoke/astream." ) - # To enable flow execution traces monkey patch all the functions that invoke the compiled graph - - original_stream = compiled_graph.stream - - def patch_with_flow_execution_span(*args: Any, **kwargs: Any) -> Generator[Any, Any, None]: - span_name = f"FlowExecution[{flow.name}]" - inputs = kwargs.get("input", {}) - if not isinstance(inputs, dict): - inputs = {} - with AgentSpecFlowExecutionSpan(name=span_name, flow=flow) as span: - span.add_event(AgentSpecFlowExecutionStart(flow=flow, inputs=inputs)) - original_result: dict[str, Any] | Any = {} - result: dict[str, Any] - # This is going to patch stream and astream, that return iterators and yield chunks - for chunk in original_stream(*args, **kwargs): - yield chunk - if isinstance(chunk, tuple): - original_result = chunk[1] - if not isinstance(original_result, dict): - result = {} - else: - result = original_result - span.add_event( - AgentSpecFlowExecutionEnd( - flow=flow, - outputs=result.get("outputs", {}), - branch_selected=result.get("node_execution_details", {}).get("branch", ""), - ) - ) - - original_astream = compiled_graph.astream - - async def patch_async_with_flow_execution_span( - *args: Any, **kwargs: Any - ) -> AsyncGenerator[Any, Any]: - span_name = f"FlowExecution[{flow.name}]" - inputs = kwargs.get("input", {}) - if not isinstance(inputs, dict): - inputs = {} - span = AgentSpecFlowExecutionSpan(name=span_name, flow=flow) - try: - await span.start_async() - except NotImplementedError: - span.start() - try: - try: - await span.add_event_async( - AgentSpecFlowExecutionStart(flow=flow, inputs=inputs) - ) - except NotImplementedError: - span.add_event(AgentSpecFlowExecutionStart(flow=flow, inputs=inputs)) - original_result: dict[str, Any] | Any = {} - result: dict[str, Any] - # This is going to patch stream and astream, that return iterators and yield chunks - async for chunk in original_astream(*args, **kwargs): - yield chunk - if isinstance(chunk, tuple): - original_result = chunk[1] - if not isinstance(original_result, dict): - result = {} - else: - result = original_result - span_end_event = AgentSpecFlowExecutionEnd( - flow=flow, - outputs=result.get("outputs", {}), - branch_selected=result.get("node_execution_details", {}).get("branch", ""), - ) - try: - await span.add_event_async(span_end_event) - except NotImplementedError: - span.add_event(span_end_event) - finally: - try: - await span.end_async() - except NotImplementedError: - span.end() - - # Monkey patch invocation functions to inject tracing - # No need to patch `(a)invoke` as the internally use `(a)stream` - compiled_graph.stream = patch_with_flow_execution_span # type: ignore - compiled_graph.astream = patch_async_with_flow_execution_span # type: ignore + patch_with_execution_span( + compiled_graph, + make_span=lambda: AgentSpecFlowExecutionSpan( + name=f"FlowExecution[{flow.name}]", flow=flow + ), + make_start_event=lambda inputs: AgentSpecFlowExecutionStart(flow=flow, inputs=inputs), + make_end_event=lambda result: AgentSpecFlowExecutionEnd( + flow=flow, + outputs=result.get("outputs", {}), + branch_selected=result.get("node_execution_details", {}).get("branch", ""), + ), + ) return compiled_graph def _node_convert_to_langgraph( @@ -761,9 +713,15 @@ def _agent_node_convert_to_langgraph( config: RunnableConfig, middleware: List[Any], ) -> "NodeExecutor": + from pyagentspec.adapters.langgraph._managerworkers_node import ManagerWorkersNodeExecutor from pyagentspec.adapters.langgraph._node_execution import AgentNodeExecutor - return AgentNodeExecutor( + executor_class = ( + ManagerWorkersNodeExecutor + if isinstance(agent_node.agent, AgentSpecManagerWorkers) + else AgentNodeExecutor + ) + return executor_class( agent_node, tool_registry=tool_registry, converted_components=converted_components, @@ -1122,6 +1080,108 @@ def _swarm_convert_to_langgraph( default_active_agent=agentspec_component.first_agent.name, ).compile(name=agentspec_component.name, checkpointer=checkpointer) + def _manager_workers_convert_to_langgraph( + self, + mw: AgentSpecManagerWorkers, + tool_registry: Dict[str, "LangGraphTool"], + converted_components: Dict[str, Any], + checkpointer: Optional[Checkpointer], + config: RunnableConfig, + middleware: List[Any], + ) -> CompiledStateGraph[Any, Any, Any]: + """Compile a ``ManagerWorkers`` into a hierarchical LangGraph. + + Topology:: + + ┌─ __delegate_to__w1 ─→ worker_1 ─┐ + START → manager ┤ ├→ manager (loop) + └─ __delegate_to__w2 ─→ worker_2 ─┘ + │ + └─ no tool_call ─→ END + + The manager is a react-agent holding one synthetic ``__delegate_to__`` + tool per worker. A conditional edge routes each delegation to its worker, + which runs in an isolated message context and answers with a ``ToolMessage`` + matched to the pending delegation id. Workers are converted recursively and + wired in as subgraph nodes, so ``astream_events`` still exposes the + parent/child boundary (``subgraph=True``) for tracing and streaming. + """ + if not isinstance(mw.group_manager, AgentSpecAgent): + # Delegation is routed off the manager's tool_calls, so the manager needs + # a chat-LLM; a Flow, Swarm or nested ManagerWorkers gives nothing to route on. + raise NotImplementedError( + f"ManagerWorkers.group_manager must be an Agent for LangGraph " + f"conversion; got {type(mw.group_manager).__name__}." + ) + + named_workers: List[Tuple[str, AgentSpecAgenticComponent]] = [ + (_safe_node_name(worker.name, fallback_id=worker.id), worker) for worker in mw.workers + ] + worker_node_names = [node_name for node_name, _ in named_workers] + if len(set(worker_node_names)) != len(worker_node_names): + raise ValueError( + "ManagerWorkers worker names collide after normalization: " + f"{worker_node_names}. Give each worker a unique name." + ) + + conversion_kwargs: Dict[str, Any] = { + "tool_registry": tool_registry, + "converted_components": converted_components, + "checkpointer": checkpointer, + "config": config, + "middleware": middleware, + } + + # The roster tells the LLM which delegation tool maps to which worker. + manager_agent = mw.group_manager + rendered_prompt = _append_workers_roster( + manager_agent.system_prompt, + [(node_name, worker.description or "") for node_name, worker in named_workers], + ) + + # The delegation tools execute inside the react loop: their Command(graph=PARENT) + # is how the call escapes the subgraph so the conditional edge below can route on it. + manager_graph = self._create_react_agent_with_given_info( + name=manager_agent.name, + system_prompt=rendered_prompt, + agent=manager_agent, + llm_config=manager_agent.llm_config, + tools=manager_agent.tools, + toolboxes=manager_agent.toolboxes, + inputs=manager_agent.inputs or [], + outputs=manager_agent.outputs or [], + additional_langgraph_tools=[ + _make_worker_delegation_tool(node_name) for node_name in worker_node_names + ], + **conversion_kwargs, + ) + + # Manager and workers all go in as compiled subgraph nodes, which is what + # makes LangGraph stream them with ``subgraph=True``. + builder = StateGraph(langgraph_graph.MessagesState) + builder.add_node(_MANAGER_NODE_KEY, manager_graph) + for node_name, worker in named_workers: + worker_graph = self.convert(worker, **conversion_kwargs) + builder.add_node(node_name, _wrap_worker_for_subgraph(worker_graph, node_name)) + builder.add_edge(node_name, _MANAGER_NODE_KEY) + + builder.add_edge(langgraph_graph.START, _MANAGER_NODE_KEY) + path_map: Dict[Hashable, str] = {} + for node_name in worker_node_names: + path_map[node_name] = node_name + path_map[langgraph_graph.END] = langgraph_graph.END + builder.add_conditional_edges( + _MANAGER_NODE_KEY, + _make_manager_router(worker_node_names), + # The path map covers every worker plus END, so langgraph can validate + # the routing statically. + path_map, + ) + + compiled_graph = builder.compile(checkpointer=checkpointer, name=mw.name) + _patch_with_manager_workers_execution_span(compiled_graph, mw) + return compiled_graph + def _create_react_agent_with_given_info( self, *, @@ -1173,10 +1233,23 @@ def _create_react_agent_with_given_info( ) output_model: Optional[type[BaseModel]] = None state_schema: Optional[Any] = None + response_format: Any = None # Build response (output) model (used for response_format) if outputs: output_model = create_pydantic_model_from_properties("AgentOutputModel", outputs) + # Explicitly use ToolStrategy instead of letting LangChain select a provider + # strategy. OpenAI-compatible models do not necessarily support provider-native + # structured output, and may otherwise return a plain AIMessage without the + # structured_response state entry after a client-tool resume. + from langchain.agents.structured_output import ToolStrategy + + response_format = ToolStrategy(output_model) + system_prompt = ( + f"{system_prompt}\n\n" + "After using the available tools, provide the final result by calling the " + "structured output tool. Do not respond with a plain-text final answer." + ) if inputs: state_schema = _create_agent_state_typed_dict( @@ -1190,88 +1263,28 @@ def _create_react_agent_with_given_info( tools=langgraph_tools, system_prompt=system_prompt, checkpointer=checkpointer, - response_format=output_model, + response_format=response_format, state_schema=state_schema, ) if middleware: create_agent_kwargs["middleware"] = middleware - compiled_graph = langchain_agents.create_agent(**create_agent_kwargs) - - # To enable flow execution traces monkey patch all the functions that invoke the compiled graph - - original_stream = compiled_graph.stream - - def patch_with_agent_execution_span(*args: Any, **kwargs: Any) -> Generator[Any, Any, Any]: - span_name = f"AgentExecution[{agent.name}]" - inputs = kwargs.get("input", {}) - if not isinstance(inputs, dict): - inputs = {} - with AgentSpecAgentExecutionSpan(name=span_name, agent=agent) as span: - span.add_event(AgentSpecAgentExecutionStart(agent=agent, inputs=inputs)) - original_result: dict[str, Any] | Any = {} - result: dict[str, Any] - # This is going to patch stream and astream, that return iterators and yield chunks - for chunk in original_stream(*args, **kwargs): - yield chunk - if isinstance(chunk, tuple): - original_result = chunk[1] - if not isinstance(original_result, dict): - result = {} - else: - result = original_result - outputs = extract_outputs_from_invoke_result(result, agent.outputs or []) - span.add_event(AgentSpecAgentExecutionEnd(agent=agent, outputs=outputs)) - - original_astream = compiled_graph.astream - - async def patch_async_with_agent_execution_span( - *args: Any, **kwargs: Any - ) -> AsyncGenerator[Any, Any]: - span_name = f"AgentExecution[{agent.name}]" - inputs = kwargs.get("input", {}) - if not isinstance(inputs, dict): - inputs = {} - span = AgentSpecAgentExecutionSpan(name=span_name, agent=agent) - try: - await span.start_async() - except NotImplementedError: - span.start() - try: - try: - await span.add_event_async( - AgentSpecAgentExecutionStart(agent=agent, inputs=inputs) - ) - except NotImplementedError: - span.add_event(AgentSpecAgentExecutionStart(agent=agent, inputs=inputs)) - original_result: dict[str, Any] | Any = {} - result: dict[str, Any] - # This is going to patch stream and astream, that return iterators and yield chunks - async for chunk in original_astream(*args, **kwargs): - yield chunk - if isinstance(chunk, tuple): - original_result = chunk[1] - if not isinstance(original_result, dict): - result = {} - else: - result = original_result - - outputs = extract_outputs_from_invoke_result(result, agent.outputs or []) - try: - await span.add_event_async( - AgentSpecAgentExecutionEnd(agent=agent, outputs=outputs) - ) - except NotImplementedError: - span.add_event(AgentSpecAgentExecutionEnd(agent=agent, outputs=outputs)) - finally: - try: - await span.end_async() - except NotImplementedError: - span.end() - - # Monkey patch invocation functions to inject tracing - # No need to patch `(a)invoke` as they internally use `(a)stream` - compiled_graph.stream = patch_with_agent_execution_span # type: ignore - compiled_graph.astream = patch_async_with_agent_execution_span # type: ignore + compiled_graph: CompiledStateGraph[Any, Any, Any] = langchain_agents.create_agent( + **create_agent_kwargs + ) + + patch_with_execution_span( + compiled_graph, + make_span=lambda: AgentSpecAgentExecutionSpan( + name=f"AgentExecution[{agent.name}]", agent=agent + ), + make_start_event=lambda inputs: AgentSpecAgentExecutionStart( + agent=agent, inputs=inputs + ), + make_end_event=lambda result: AgentSpecAgentExecutionEnd( + agent=agent, + outputs=extract_outputs_from_invoke_result(result, agent.outputs or []), + ), + ) return compiled_graph def _agent_convert_to_langgraph( @@ -1323,7 +1336,7 @@ def _llm_convert_to_langgraph( return _create_chat_openai_model( model_id=llm_config.model_id, base_url=_prepare_openai_compatible_url(llm_config.url), - api_key=llm_config.api_key if llm_config.api_key is not None else "EMPTY", + api_key=llm_config.api_key, use_responses_api=use_responses_api, callbacks=callbacks, generation_config=generation_config, @@ -1650,7 +1663,6 @@ def _generation_config_from_agentspec( def _create_chat_openai_model( - *, model_id: str, use_responses_api: bool, callbacks: List[BaseCallbackHandler], @@ -1661,12 +1673,15 @@ def _create_chat_openai_model( ) -> BaseChatModel: """Create a ChatOpenAI model without overriding env-based defaults. - Important: passing `api_key=None` disables LangChain's env-based default (`OPENAI_API_KEY`) - and results in a model without a sync client. Only pass `api_key` when it is explicitly - specified in the Agent Spec config. + Important: passing `api_key=None` fallbacks to env-based default (`OPENAI_API_KEY`) or a fake API key. """ from langchain_openai import ChatOpenAI + # If api_key is None (or not passed), and the environment variable is not set, openai raises an error + # Therefore, we set a fake API key to avoid raising an exception in case no key is required + if api_key is None: + api_key = os.getenv("OPENAI_API_KEY", "EMPTY") or "EMPTY" + optional_kwargs: _ChatOpenAIOptionalKwargs = {} max_retries = retry_config.get("max_retries") if max_retries is not None: @@ -1676,13 +1691,12 @@ def _create_chat_openai_model( optional_kwargs["timeout"] = timeout if base_url is not None: optional_kwargs["base_url"] = base_url - if api_key is not None: - optional_kwargs["api_key"] = SecretStr(api_key) return ChatOpenAI( model=model_id, use_responses_api=use_responses_api, callbacks=callbacks, + api_key=SecretStr(api_key), temperature=generation_config.get("temperature"), max_completion_tokens=generation_config.get("max_tokens"), top_p=generation_config.get("top_p"), @@ -1696,7 +1710,6 @@ class _ChatOpenAIOptionalKwargs(TypedDict): max_retries: NotRequired[int] timeout: NotRequired[float] base_url: NotRequired[str] - api_key: NotRequired[SecretStr] class _GenerationConfig(TypedDict): diff --git a/pyagentspec/src/pyagentspec/adapters/langgraph/_managerworkers.py b/pyagentspec/src/pyagentspec/adapters/langgraph/_managerworkers.py new file mode 100644 index 00000000..9d2298fb --- /dev/null +++ b/pyagentspec/src/pyagentspec/adapters/langgraph/_managerworkers.py @@ -0,0 +1,232 @@ +# Copyright © 2025, 2026 Oracle and/or its affiliates. +# +# This software is under the Apache License 2.0 +# (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0) or Universal Permissive License +# (UPL) 1.0 (LICENSE-UPL or https://oss.oracle.com/licenses/upl), at your option. + +"""Helpers for compiling a ``ManagerWorkers`` into LangGraph, orchestrated by +``AgentSpecToLangGraphConverter._manager_workers_convert_to_langgraph``. + +The delegation protocol is visible on purpose: ``__delegate_to__`` calls +stream like any other tool call. Consumers that would rather not render them can +filter on :func:`is_delegation_tool_name`. +""" + +import re +from typing import Annotated, Any, Dict, Iterable, List, Tuple + +from pyagentspec.adapters.langgraph._execution_span import patch_with_execution_span +from pyagentspec.adapters.langgraph._types import ( + CompiledStateGraph, + RunnableConfig, + langgraph_graph, +) +from pyagentspec.managerworkers import ManagerWorkers as AgentSpecManagerWorkers +from pyagentspec.tracing.events import ( + ManagerWorkersExecutionEnd as AgentSpecManagerWorkersExecutionEnd, +) +from pyagentspec.tracing.events import ( + ManagerWorkersExecutionStart as AgentSpecManagerWorkersExecutionStart, +) +from pyagentspec.tracing.spans import ( + ManagerWorkersExecutionSpan as AgentSpecManagerWorkersExecutionSpan, +) + +# Cannot collide with a worker node name: _normalize_identifier strips leading and +# trailing underscores, so no normalized name ever starts with one. +_MANAGER_NODE_KEY = "__manager__" + +#: Prefix of the synthetic ``__delegate_to__`` tool names the manager's LLM +#: uses to address a worker. The dunder prefix, like the delegation keys below, keeps +#: it from colliding with a real tool named ``delegate_to_``. Re-exported +#: from ``pyagentspec.adapters.langgraph`` so consumers can recognize the protocol. +DELEGATE_TOOL_PREFIX = "__delegate_to__" + +# Keys of the per-delegation ``Send`` payload: the task to run, and the tool_call_id +# the worker's reply must answer. Routing per delegation (instead of off shared state) +# lets one manager turn delegate to several workers at once. +_DELEGATE_TASK_KEY = "__delegate_task__" +_DELEGATE_CALL_ID_KEY = "__delegate_tool_call_id__" + +_WHITESPACE_RE = re.compile(r"\s+") + + +def is_delegation_tool_name(name: Any) -> bool: + """True for the synthetic ``__delegate_to__`` tool names a manager emits.""" + return isinstance(name, str) and name.startswith(DELEGATE_TOOL_PREFIX) + + +def _normalize_identifier(s: str) -> str: + """Lowercase, collapse non-alphanumerics to underscores, strip leading/trailing ones.""" + return re.sub(r"[^a-z0-9]+", "_", s.lower()).strip("_") + + +def _safe_node_name(name: str, fallback_id: str) -> str: + """Normalize a worker name into a LangGraph node identifier. + + The LLM has to emit ``__delegate_to__`` reliably as a tool name, so node + names stay ASCII identifiers. Falls back to the normalized component id when the + name slugifies to nothing. + """ + return _normalize_identifier(name) or _normalize_identifier(fallback_id) or "worker" + + +def _messages_of(state: Any) -> List[Any]: + """Read ``messages`` off a state, which langgraph injects as a dict or an object.""" + if isinstance(state, dict): + return list(state.get("messages") or []) + return list(getattr(state, "messages", []) or []) + + +def _append_workers_roster(system_prompt: str, entries: List[Tuple[str, str]]) -> str: + """Append an ``Available workers:`` block listing ``- : ``. + + Descriptions are flattened to one line each, since the LLM routes off the block's + one-line-per-worker shape. + """ + if not entries: + return system_prompt + lines = [ + f"- {name}: {_WHITESPACE_RE.sub(' ', description).strip()}" for name, description in entries + ] + roster = "Available workers:\n" + "\n".join(lines) + return f"{system_prompt}\n\n{roster}" if system_prompt else roster + + +def _make_worker_delegation_tool(worker_node_name: str) -> Any: + """Build the ``__delegate_to__`` tool the manager's LLM emits to route to + a worker. + + Executing the tool is only how the call escapes the react subgraph: its body + surfaces the subgraph messages to the parent with ``Command(graph=PARENT)`` and no + ``goto``. Routing stays in the edge built by :func:`_make_manager_router`; a ``goto`` here + would collapse several same-turn delegations into one parent Command and leave the + other ``tool_call_id``s unanswered. + """ + from langchain_core.tools import InjectedToolCallId, tool + from langgraph.prebuilt import InjectedState + from langgraph.types import Command + + tool_name = f"{DELEGATE_TOOL_PREFIX}{worker_node_name}" + description = ( + f"Delegate a task to the {worker_node_name} worker and receive its response. " + f"Use this when the task fits the worker's described capability." + ) + + @tool(tool_name, description=description) + def _delegate( + task: str, + state: Annotated[Any, InjectedState], + tool_call_id: Annotated[str, InjectedToolCallId], + ) -> Any: + # task and tool_call_id are declared for the LLM-facing schema; the routing + # edge recovers both off the surfaced AIMessage's tool_calls. The + # add_messages reducer dedupes by id, so re-surfacing messages is a no-op. + del task, tool_call_id + return Command(graph=Command.PARENT, update={"messages": _messages_of(state)}) + + return _delegate + + +def _make_manager_router(worker_node_names: Iterable[str]) -> Any: + """Build the conditional edge routing the parent graph off the manager's last + AIMessage: one ``Send`` per ``__delegate_to__`` tool call, or ``END`` + when it emitted none. + + Every delegation gets its own ``Send``, so each tool_call_id is answered + independently; an unanswered one breaks the manager's next-turn tool-call/result + sequence. Plain tool calls already ran inside the react loop; that includes a + real tool whose name merely starts with the prefix, which is why a suffix that + is not a worker node is not routed. + """ + known_workers = frozenset(worker_node_names) + + def _route_manager_to_worker_or_end(state: Dict[str, Any]) -> Any: + from langgraph.types import Send + + messages = state.get("messages") or [] + last = messages[-1] if messages else None + sends = [] + for tool_call in getattr(last, "tool_calls", None) or []: + name = tool_call.get("name") + if not is_delegation_tool_name(name): + continue + worker_node_name = name[len(DELEGATE_TOOL_PREFIX) :] + if worker_node_name not in known_workers: + continue + args = tool_call.get("args") or {} + sends.append( + Send( + worker_node_name, + { + _DELEGATE_TASK_KEY: args.get("task") or "", + _DELEGATE_CALL_ID_KEY: tool_call.get("id") or "", + }, + ) + ) + return sends or langgraph_graph.END + + return _route_manager_to_worker_or_end + + +def _worker_input(state: Dict[str, Any]) -> Dict[str, Any]: + """The single-message context a worker run starts from.""" + from langchain_core.messages import HumanMessage + + return {"messages": [HumanMessage(content=state.get(_DELEGATE_TASK_KEY) or "")]} + + +def _worker_reply(state: Dict[str, Any], result: Any) -> Dict[str, Any]: + """The worker's last message, as a ToolMessage answering this delegation.""" + from langchain_core.messages import ToolMessage + + messages = result.get("messages") if isinstance(result, dict) else None + content = (getattr(messages[-1], "content", "") if messages else "") or "" + return { + "messages": [ + ToolMessage(content=content, tool_call_id=state.get(_DELEGATE_CALL_ID_KEY) or "") + ] + } + + +def _wrap_worker_for_subgraph( + worker_graph: CompiledStateGraph[Any, Any, Any], + worker_node_name: str, +) -> Any: + """Wrap a worker subgraph as a node of the ManagerWorkers parent graph. + + Hierarchical rather than shared-state like a Swarm: each run is handed only the + manager's chosen task, and the worker's answer comes back as a ToolMessage so the + manager's react loop sees a well-formed tool response on its next turn. The worker + receives this node's ambient run config explicitly, which streams its token events + under the worker node's checkpoint namespace. Explicit propagation is necessary on + Python 3.10, where LangChain cannot preserve the callback context across tasks. + """ + from pyagentspec.adapters.langgraph._types import RunnableLambda + + def run(state: Dict[str, Any], config: RunnableConfig) -> Dict[str, Any]: + return _worker_reply(state, worker_graph.invoke(_worker_input(state), config=config)) + + async def arun(state: Dict[str, Any], config: RunnableConfig) -> Dict[str, Any]: + return _worker_reply(state, await worker_graph.ainvoke(_worker_input(state), config=config)) + + return RunnableLambda(func=run, afunc=arun, name=f"worker:{worker_node_name}") + + +def _patch_with_manager_workers_execution_span( + compiled_graph: CompiledStateGraph[Any, Any, Any], + mw: AgentSpecManagerWorkers, +) -> None: + """Wrap ``stream``/``astream`` so each run emits a ``ManagerWorkersExecutionSpan``.""" + patch_with_execution_span( + compiled_graph, + make_span=lambda: AgentSpecManagerWorkersExecutionSpan( + name=f"ManagerWorkersExecution[{mw.name}]", managerworkers=mw + ), + make_start_event=lambda inputs: AgentSpecManagerWorkersExecutionStart( + managerworkers=mw, inputs=inputs + ), + make_end_event=lambda result: AgentSpecManagerWorkersExecutionEnd( + managerworkers=mw, outputs={"messages": result.get("messages", [])} + ), + ) diff --git a/pyagentspec/src/pyagentspec/adapters/langgraph/_managerworkers_node.py b/pyagentspec/src/pyagentspec/adapters/langgraph/_managerworkers_node.py new file mode 100644 index 00000000..74a0e700 --- /dev/null +++ b/pyagentspec/src/pyagentspec/adapters/langgraph/_managerworkers_node.py @@ -0,0 +1,118 @@ +# Copyright © 2026 Oracle and/or its affiliates. +# +# This software is under the Apache License 2.0 +# (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0) or Universal Permissive License +# (UPL) 1.0 (LICENSE-UPL or https://oss.oracle.com/licenses/upl), at your option. + +"""Runs a ``ManagerWorkers`` as a flow step. + +``AgentSpecToLangGraphConverter._agent_node_convert_to_langgraph`` selects +`ManagerWorkersNodeExecutor` when the node's agent is a ``ManagerWorkers``, +so `AgentNodeExecutor` keeps the plain-Agent behavior only. +""" + +from typing import Any, Dict, List, Optional, Tuple + +from pyagentspec.adapters._utils import render_template +from pyagentspec.adapters.langgraph._node_execution import AgentNodeExecutor +from pyagentspec.adapters.langgraph._types import ( + Checkpointer, + CompiledStateGraph, + ExecuteOutput, + LangGraphTool, + Messages, + NodeExecutionDetails, + RunnableConfig, +) +from pyagentspec.agent import Agent as AgentSpecAgent +from pyagentspec.flows.nodes import AgentNode as AgentSpecAgentNode +from pyagentspec.managerworkers import ManagerWorkers as AgentSpecManagerWorkers + + +class ManagerWorkersNodeExecutor(AgentNodeExecutor): + """Executes an ``AgentNode`` whose agent is a ``ManagerWorkers``. + + The hierarchical graph runs over ``MessagesState``, which can carry neither + structured inputs inward nor a ``structured_response`` outward. Inputs are + therefore rendered into the group-manager's system prompt before compiling, and + the manager's final message is the node's single string output. + """ + + def __init__( + self, + node: AgentSpecAgentNode, + tool_registry: Dict[str, "LangGraphTool"], + converted_components: Dict[str, Any], + checkpointer: Optional[Checkpointer], + config: RunnableConfig, + middleware: Optional[List[Any]] = None, + ) -> None: + super().__init__( + node, tool_registry, converted_components, checkpointer, config, middleware + ) + if not isinstance(node.agent, AgentSpecManagerWorkers): + raise TypeError( + "ManagerWorkersNodeExecutor requires an AgentNode holding a ManagerWorkers" + ) + self._manager_workers: AgentSpecManagerWorkers = node.agent + # Anything but a single string output cannot be honored (see class docstring); + # raising here fails at conversion time rather than mid-run. + outputs = node.outputs or [] + if outputs and (len(outputs) != 1 or outputs[0].type != "string"): + raise NotImplementedError( + "A ManagerWorkers flow step supports a single string output; " + f"node `{node.name}` declares {[o.title for o in outputs]}." + ) + + def _create_manager_workers_with_given_input_values( + self, inputs: Dict[str, Any] + ) -> CompiledStateGraph[Any, Any]: + """Compile the ``ManagerWorkers`` with the node inputs rendered into the + group-manager's ``system_prompt`` and the satisfied ports dropped. + + Cached by rendered prompt, the same key + :meth:`AgentNodeExecutor._create_react_agent_with_given_input_values` uses. + Calling the private converter entry point is deliberate, mirroring the react + path: the public ``convert`` caches by component id, which would collapse the + differently-rendered copies (all sharing the original's id) into one graph. + """ + from pyagentspec.adapters.langgraph._langgraphconverter import AgentSpecToLangGraphConverter + + converter = AgentSpecToLangGraphConverter() + component = self._manager_workers + entry_agent = component.group_manager + if not isinstance(entry_agent, AgentSpecAgent): + # Nothing to render or cache; the converter owns the error for this case. + return converter._manager_workers_convert_to_langgraph( + component, **self._conversion_kwargs() + ) + + system_prompt = render_template(entry_agent.system_prompt, inputs) + if system_prompt not in self._agents_cache: + rendered = component.model_copy( + update={ + "group_manager": entry_agent.model_copy( + update={"system_prompt": system_prompt, "inputs": []} + ), + "inputs": [], + } + ) + self._agents_cache[system_prompt] = converter._manager_workers_convert_to_langgraph( + rendered, **self._conversion_kwargs() + ) + return self._agents_cache[system_prompt] + + def _prepare_agent_and_inputs( + self, inputs: Dict[str, Any], messages: Messages + ) -> Tuple[CompiledStateGraph[Any, Any], Dict[str, Any]]: + # Inputs were baked into the group-manager's prompt, so this graph runs on + # messages alone rather than the react-agent's remaining_steps state. + graph = self._create_manager_workers_with_given_input_values(inputs) + return graph, {"messages": self._with_driving_message(messages)} + + def _format_agent_result(self, result: Dict[str, Any]) -> ExecuteOutput: + node_outputs = self.node.outputs + if not node_outputs: + return super()._format_agent_result(result) + # __init__ already rejected any shape but a single string output. + return {node_outputs[0].title: result["messages"][-1].content}, NodeExecutionDetails() diff --git a/pyagentspec/src/pyagentspec/adapters/langgraph/_node_execution.py b/pyagentspec/src/pyagentspec/adapters/langgraph/_node_execution.py index f1999aef..a1398df9 100644 --- a/pyagentspec/src/pyagentspec/adapters/langgraph/_node_execution.py +++ b/pyagentspec/src/pyagentspec/adapters/langgraph/_node_execution.py @@ -499,6 +499,16 @@ def __init__( self._middleware: List[Any] = list(middleware or []) self._agents_cache: Dict[str, CompiledStateGraph[Any, Any]] = {} + def _conversion_kwargs(self) -> Dict[str, Any]: + """The converter arguments every compile from this executor passes through.""" + return { + "tool_registry": self.tool_registry, + "converted_components": self.converted_components, + "checkpointer": self.checkpointer, + "config": self.config, + "middleware": self._middleware, + } + def _create_react_agent_with_given_input_values( self, inputs: Dict[str, Any] ) -> CompiledStateGraph[Any, Any]: @@ -521,27 +531,25 @@ def _create_react_agent_with_given_input_values( toolboxes=agentspec_component.toolboxes, inputs=agentspec_component.inputs or [], outputs=agentspec_component.outputs or [], - tool_registry=self.tool_registry, - converted_components=self.converted_components, - checkpointer=self.checkpointer, - config=self.config, - middleware=self._middleware, + **self._conversion_kwargs(), ) return self._agents_cache[system_prompt] + @staticmethod + def _with_driving_message(messages: Messages) -> Messages: + # LangGraph's agent expects at least one user message to drive execution. + # When an AgentNode is used with a templated system prompt and no messages are + # provided by the flow, the agent can crash. To avoid this, we artificially + # insert an empty user message when the message list is empty. + return messages if messages else cast(Messages, [{"role": "user", "content": ""}]) + def _prepare_agent_and_inputs( self, inputs: Dict[str, Any], messages: Messages ) -> Tuple[CompiledStateGraph[Any, Any], Dict[str, Any]]: agent = self._create_react_agent_with_given_input_values(inputs) - # LangGraph's agent expects at least one user message to drive execution. - # When an AgentNode is used with a templated system prompt and no messages are provided - # by the flow, the agent can crash. To avoid this, we artificially insert an empty - # user message when the message list is empty. - if not messages: - messages = cast(Messages, [{"role": "user", "content": ""}]) inputs |= { "remaining_steps": 20, # Get the right number of steps left - "messages": messages, + "messages": self._with_driving_message(messages), "structured_response": {}, } return agent, inputs diff --git a/pyagentspec/src/pyagentspec/adapters/langgraph/tracing.py b/pyagentspec/src/pyagentspec/adapters/langgraph/tracing.py index fb8bff97..04eaf9d9 100644 --- a/pyagentspec/src/pyagentspec/adapters/langgraph/tracing.py +++ b/pyagentspec/src/pyagentspec/adapters/langgraph/tracing.py @@ -305,7 +305,7 @@ def on_chat_model_start( def on_llm_new_token( self, - token: str, + token: Union[str, List[Union[str, Dict[str, Any]]]], *, chunk: Optional[Union[ChatGenerationChunk, GenerationChunk]] = None, run_id: UUID, @@ -461,7 +461,7 @@ async def on_chat_model_start_async( async def on_llm_new_token_async( self, - token: str, + token: Union[str, List[Union[str, Dict[str, Any]]]], *, chunk: Optional[Union[ChatGenerationChunk, GenerationChunk]] = None, run_id: UUID, diff --git a/pyagentspec/src/pyagentspec/llms/__init__.py b/pyagentspec/src/pyagentspec/llms/__init__.py index c7170151..d7aba5a4 100644 --- a/pyagentspec/src/pyagentspec/llms/__init__.py +++ b/pyagentspec/src/pyagentspec/llms/__init__.py @@ -6,6 +6,7 @@ """Define LLM configuration abstractions and provider-specific implementations.""" +from .dbmsvectorchainllmconfig import DbmsVectorChainLlmConfig from .geminiconfig import GeminiConfig from .llmconfig import LlmConfig from .llmgenerationconfig import LlmGenerationConfig @@ -16,6 +17,7 @@ from .vllmconfig import VllmConfig __all__ = [ + "DbmsVectorChainLlmConfig", "LlmConfig", "LlmGenerationConfig", "GeminiConfig", diff --git a/pyagentspec/src/pyagentspec/llms/dbmsvectorchainllmconfig.py b/pyagentspec/src/pyagentspec/llms/dbmsvectorchainllmconfig.py new file mode 100644 index 00000000..d334481f --- /dev/null +++ b/pyagentspec/src/pyagentspec/llms/dbmsvectorchainllmconfig.py @@ -0,0 +1,90 @@ +# Copyright © 2026 Oracle and/or its affiliates. +# +# This software is under the Apache License 2.0 +# (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0) or Universal Permissive License +# (UPL) 1.0 (LICENSE-UPL or https://oss.oracle.com/licenses/upl), at your option. + +"""Define the LLM configuration used by Oracle Database DBMS_VECTOR_CHAIN.""" + +from typing import Literal, Optional + +from pydantic import Field +from pydantic.json_schema import SkipJsonSchema +from typing_extensions import Self + +from pyagentspec.datastores.oracle import OracleDatabaseConnectionConfig +from pyagentspec.llms.llmconfig import LlmConfig +from pyagentspec.sensitive_field import SensitiveField +from pyagentspec.validation_helpers import model_validator_with_error_accumulation +from pyagentspec.versioning import AgentSpecVersionEnum + + +class DbmsVectorChainLlmConfig(LlmConfig): + """Configure an LLM executed by Oracle Database through DBMS_VECTOR_CHAIN.""" + + min_agentspec_version: SkipJsonSchema[AgentSpecVersionEnum] = Field( + default=AgentSpecVersionEnum.v26_3_0, + init=False, + exclude=True, + ) + + model_id: str = Field(min_length=1) + """Model identifier used by Oracle Database for the provider request.""" + + provider: str = Field(min_length=1) + """Provider of the model, such as ``openai`` or ``cohere``.""" + + url: str = Field(min_length=1) + """Provider API endpoint used by Oracle Database.""" + + host: Optional[Literal["local"]] = None + """Set to ``local`` for a local OpenAI or Ollama provider without a database credential.""" + + credential_name: Optional[str] = Field(default=None, min_length=1) + """Name of a credential managed by Oracle Database.""" + + transfer_timeout: Optional[int] = Field(default=None, ge=0) + """Maximum transfer time in seconds for the provider request.""" + + connection_config: Optional[OracleDatabaseConnectionConfig] = None + """Optional Oracle Database connection configuration.""" + + # Authentication is configured through the database credential referenced by + # ``credential_name``. + api_provider: SkipJsonSchema[Optional[str]] = Field(default=None, exclude=True) + api_type: SkipJsonSchema[Optional[str]] = Field(default=None, exclude=True) + api_key: SkipJsonSchema[SensitiveField[Optional[str]]] = Field(default=None, exclude=True) + + @model_validator_with_error_accumulation + def _validate_credential_name(self) -> Self: + if self.host is not None and self.credential_name is not None: + raise ValueError("`host` and `credential_name` cannot both be specified.") + if self.host is None and self.credential_name is None: + raise ValueError("`credential_name` is required unless `host` is 'local'.") + if self.host is not None and self.provider not in {"openai", "ollama"}: + raise ValueError("`host` is supported only for the `openai` and `ollama` providers.") + return self + + @model_validator_with_error_accumulation + def _validate_unsupported_inherited_fields(self) -> Self: + unsupported_fields = { + "api_provider": self.api_provider, + "api_type": self.api_type, + "api_key": self.api_key, + } + configured_fields = [ + field_name for field_name, value in unsupported_fields.items() if value is not None + ] + if configured_fields: + raise ValueError( + "DbmsVectorChainLlmConfig does not support inherited field(s): " + + ", ".join(configured_fields) + ) + return self + + def _versioned_model_fields_to_exclude( + self, agentspec_version: AgentSpecVersionEnum + ) -> set[str]: + fields_to_exclude = super()._versioned_model_fields_to_exclude(agentspec_version) + fields_to_exclude.update({"api_provider", "api_type", "api_key"}) + return fields_to_exclude diff --git a/pyagentspec/src/pyagentspec/managerworkers.py b/pyagentspec/src/pyagentspec/managerworkers.py index 26c2bdeb..11c9088c 100644 --- a/pyagentspec/src/pyagentspec/managerworkers.py +++ b/pyagentspec/src/pyagentspec/managerworkers.py @@ -13,6 +13,7 @@ from typing_extensions import Self from pyagentspec.agenticcomponent import AgenticComponent +from pyagentspec.property import Property, properties_have_same_type from pyagentspec.validation_helpers import model_validator_with_error_accumulation from pyagentspec.versioning import AgentSpecVersionEnum @@ -65,6 +66,61 @@ class ManagerWorkers(AgenticComponent): default=AgentSpecVersionEnum.v25_4_2, init=False, exclude=True ) + def _get_inferred_inputs(self) -> List[Property]: + # Per the language spec, the inputs of a ManagerWorkers are the inputs of its + # group manager (same name and type): the manager drives the conversation. + return ( + self.group_manager.inputs or [] + if getattr(self, "group_manager", None) + and self.min_agentspec_version >= AgentSpecVersionEnum.v26_4_0 + else [] + ) + + def _get_inferred_outputs(self) -> List[Property]: + # Symmetric with the inferred inputs: the group manager's outputs. + return ( + self.group_manager.outputs or [] + if getattr(self, "group_manager", None) + and self.min_agentspec_version >= AgentSpecVersionEnum.v26_4_0 + else [] + ) + + def _infer_min_agentspec_version_from_configuration(self) -> AgentSpecVersionEnum: + min_version = super()._infer_min_agentspec_version_from_configuration() + # ManagerWorkers I/O matching was introduced in 26.4.0. Omitted I/O + # inherits from the group manager; explicitly empty I/O is legacy-only. + manager = getattr(self, "group_manager", None) + inherits_manager_io = bool( + manager + and ( + ("inputs" not in self.model_fields_set and manager.inputs) + or ("outputs" not in self.model_fields_set and manager.outputs) + ) + ) + if inherits_manager_io or getattr(self, "inputs", []) or getattr(self, "outputs", []): + min_version = max(min_version, AgentSpecVersionEnum.v26_4_0) + return min_version + + def _infer_max_agentspec_version_from_configuration(self) -> AgentSpecVersionEnum: + max_version = super()._infer_max_agentspec_version_from_configuration() + # Before 26.4.0 a ManagerWorkers did not inherit its manager's I/O. + manager = getattr(self, "group_manager", None) + inherits_manager_io = bool( + manager + and ( + ("inputs" not in self.model_fields_set and manager.inputs) + or ("outputs" not in self.model_fields_set and manager.outputs) + ) + ) + if ( + manager + and (manager.inputs or manager.outputs) + and not inherits_manager_io + and not (self.inputs or self.outputs) + ): + max_version = min(max_version, AgentSpecVersionEnum.v26_3_0) + return max_version + @model_validator_with_error_accumulation def _validate_one_or_more_workers(self) -> Self: if len(self.workers) == 0: @@ -79,3 +135,28 @@ def _validate_group_manager_is_not_included_as_a_worker(self) -> Self: if any(self.group_manager is agent for agent in self.workers): raise ValueError("Group manager cannot be a worker.") return self + + @model_validator_with_error_accumulation + def _validate_ios_match_group_manager_ios(self) -> Self: + # Per the language spec, the I/Os of a ManagerWorkers must be the I/Os of its + # group manager, same name and type. The base ComponentWithIO validators + # already enforce matching titles; use the shared property helper here so + # nested JSON Schema types are compared correctly as well. + for kind, own_properties, manager_properties, explicitly_provided in ( + ("input", self.inputs or [], self.group_manager.inputs or [], "inputs"), + ("output", self.outputs or [], self.group_manager.outputs or [], "outputs"), + ): + if explicitly_provided not in self.model_fields_set: + continue + manager_property_by_title = {p.title: p for p in manager_properties} + for own_property in own_properties: + manager_property = manager_property_by_title.get(own_property.title) + if manager_property is not None and not properties_have_same_type( + own_property, manager_property + ): + raise ValueError( + f"The {kind}s of a `ManagerWorkers` must match the {kind}s of its " + f"group manager (same name and type), but {kind} " + f"`{own_property.title}` has a different type from the group manager." + ) + return self diff --git a/pyagentspec/src/pyagentspec/mcp/tools.py b/pyagentspec/src/pyagentspec/mcp/tools.py index 3fee83d3..a6efd165 100644 --- a/pyagentspec/src/pyagentspec/mcp/tools.py +++ b/pyagentspec/src/pyagentspec/mcp/tools.py @@ -38,14 +38,14 @@ def _versioned_model_fields_to_exclude( self, agentspec_version: AgentSpecVersionEnum ) -> set[str]: fields_to_exclude = super()._versioned_model_fields_to_exclude(agentspec_version) - if agentspec_version < AgentSpecVersionEnum.v26_2_0: + if agentspec_version < AgentSpecVersionEnum.v26_3_0: fields_to_exclude.add("retry_policy") return fields_to_exclude def _infer_min_agentspec_version_from_configuration(self) -> AgentSpecVersionEnum: min_version = super()._infer_min_agentspec_version_from_configuration() if self.retry_policy is not None: - min_version = max(min_version, AgentSpecVersionEnum.v26_2_0) + min_version = max(min_version, AgentSpecVersionEnum.v26_3_0) return min_version @@ -107,12 +107,12 @@ def _versioned_model_fields_to_exclude( self, agentspec_version: AgentSpecVersionEnum ) -> set[str]: fields_to_exclude = super()._versioned_model_fields_to_exclude(agentspec_version) - if agentspec_version < AgentSpecVersionEnum.v26_2_0: + if agentspec_version < AgentSpecVersionEnum.v26_3_0: fields_to_exclude.add("retry_policy") return fields_to_exclude def _infer_min_agentspec_version_from_configuration(self) -> AgentSpecVersionEnum: min_version = super()._infer_min_agentspec_version_from_configuration() if self.retry_policy is not None: - min_version = max(min_version, AgentSpecVersionEnum.v26_2_0) + min_version = max(min_version, AgentSpecVersionEnum.v26_3_0) return min_version diff --git a/pyagentspec/src/pyagentspec/versioning.py b/pyagentspec/src/pyagentspec/versioning.py index d0dacda5..fa465793 100644 --- a/pyagentspec/src/pyagentspec/versioning.py +++ b/pyagentspec/src/pyagentspec/versioning.py @@ -37,9 +37,10 @@ class AgentSpecVersionEnum(Enum): v25_4_2 = "25.4.2" v26_1_0 = "26.1.0" v26_1_2 = "26.1.2" - v26_2_0 = "26.2.0" - current_version = "26.2.0" - latest_supported_version = "26.2.0" + v26_3_0 = "26.3.0" + v26_4_0 = "26.4.0" + current_version = "26.4.0" + latest_supported_version = "26.4.0" def __lt__(self, other: "AgentSpecVersionEnum") -> bool: return _version_lt(self.value, other.value) diff --git a/pyagentspec/tests/adapters/agent_framework/test_agentframework_to_agentspec.py b/pyagentspec/tests/adapters/agent_framework/test_agentframework_to_agentspec.py index 87bb7900..b379d944 100644 --- a/pyagentspec/tests/adapters/agent_framework/test_agentframework_to_agentspec.py +++ b/pyagentspec/tests/adapters/agent_framework/test_agentframework_to_agentspec.py @@ -6,14 +6,14 @@ from typing import cast -from pyagentspec.agent import Agent -from pyagentspec.llms.openaicompatibleconfig import OpenAiCompatibleConfig +from pyagentspec.agent import Agent as AgentSpecAgent +from pyagentspec.llms.openaicompatibleconfig import OpenAIAPIType, OpenAiCompatibleConfig def test_agent_framework_converts_to_agent_spec_with_server_tool() -> None: - from agent_framework import ChatAgent, tool - from agent_framework.openai import OpenAIChatClient + from agent_framework import Agent, tool + from agent_framework.openai import OpenAIChatCompletionClient from pyagentspec.adapters.agent_framework import AgentSpecExporter @@ -21,31 +21,34 @@ def test_agent_framework_converts_to_agent_spec_with_server_tool() -> None: def add_tool(a: int, b: int) -> int: return a + b - agent = ChatAgent( - chat_client=OpenAIChatClient( + agent = Agent( + client=OpenAIChatCompletionClient( api_key="ollama", base_url="url.to.agi.model", - model_id="agi_ollama_model", + model="agi_ollama_model", ), name="MathAgent", instructions="You are a helpful math agent", tools=add_tool, - temperature=0.2, - top_p=0.5, - max_tokens=10000, + additional_properties=dict( + temperature=0.2, + top_p=0.5, + max_tokens=10000, + ), ) exporter = AgentSpecExporter() - agent_component = cast(Agent, exporter.to_component(agent)) + agent_component = cast(AgentSpecAgent, exporter.to_component(agent)) # Agent config assert agent_component.name == agent.name assert agent_component.description == agent.description assert isinstance(agent_component.llm_config, OpenAiCompatibleConfig) - assert isinstance(agent.chat_client, OpenAIChatClient) + assert agent_component.llm_config.api_type == OpenAIAPIType.CHAT_COMPLETIONS + assert isinstance(agent.client, OpenAIChatCompletionClient) assert agent_component.system_prompt == agent.default_options["instructions"] # Llm Config - assert agent_component.llm_config.url == agent.chat_client.service_url() - assert agent_component.llm_config.model_id == agent.chat_client.model_id + assert agent_component.llm_config.url == agent.client.service_url() + assert agent_component.llm_config.model_id == agent.client.model default_generation_parameters = agent_component.llm_config.default_generation_parameters assert default_generation_parameters is not None assert default_generation_parameters.temperature == agent.additional_properties["temperature"] @@ -65,3 +68,26 @@ def add_tool(a: int, b: int) -> int: assert "b" in (schema["title"] for schema in input_json_schemas) assert all(schema["type"] == "integer" for schema in input_json_schemas) assert output_json_schema["title"] == "result" and output_json_schema["type"] == "integer" + + +def test_agent_framework_responses_client_converts_to_agent_spec() -> None: + from agent_framework import Agent + from agent_framework.openai import OpenAIChatClient + + from pyagentspec.adapters.agent_framework import AgentSpecExporter + + agent = Agent( + client=OpenAIChatClient( + api_key="test-key", + base_url="https://api.example.com/v1", + model="gpt-test", + ), + name="ResponsesAgent", + instructions="Be helpful.", + ) + + agent_component = cast(AgentSpecAgent, AgentSpecExporter().to_component(agent)) + + assert isinstance(agent_component.llm_config, OpenAiCompatibleConfig) + assert agent_component.llm_config.api_type == OpenAIAPIType.RESPONSES + assert agent_component.llm_config.model_id == agent.client.model diff --git a/pyagentspec/tests/adapters/agent_framework/test_agentspec_to_agentframework.py b/pyagentspec/tests/adapters/agent_framework/test_agentspec_to_agentframework.py index b173d700..368f29bf 100644 --- a/pyagentspec/tests/adapters/agent_framework/test_agentspec_to_agentframework.py +++ b/pyagentspec/tests/adapters/agent_framework/test_agentspec_to_agentframework.py @@ -5,28 +5,92 @@ # (UPL) 1.0 (LICENSE-UPL or https://oss.oracle.com/licenses/upl), at your option. import anyio +import pytest -from pyagentspec.agent import Agent +from pyagentspec.agent import Agent as AgentSpecAgent +from pyagentspec.llms import LlmConfig +from pyagentspec.llms.openaicompatibleconfig import OpenAIAPIType, OpenAiCompatibleConfig from pyagentspec.llms.vllmconfig import VllmConfig from pyagentspec.serialization import AgentSpecDeserializer from .conftest import get_weather +@pytest.mark.parametrize( + ("api_type", "expected_client_name"), + [ + (OpenAIAPIType.CHAT_COMPLETIONS, "OpenAIChatCompletionClient"), + (OpenAIAPIType.RESPONSES, "OpenAIChatClient"), + ], +) +def test_agentspec_api_type_selects_agent_framework_client( + api_type: OpenAIAPIType, expected_client_name: str +) -> None: + from agent_framework.openai import OpenAIChatClient, OpenAIChatCompletionClient + + from pyagentspec.adapters.agent_framework import AgentSpecLoader + + client = AgentSpecLoader().load_component( + OpenAiCompatibleConfig( + name="test-model", + model_id="test-model", + url="http://test.example/v1", + api_type=api_type, + ) + ) + + expected_client = { + "OpenAIChatClient": OpenAIChatClient, + "OpenAIChatCompletionClient": OpenAIChatCompletionClient, + }[expected_client_name] + assert isinstance(client, expected_client) + + +@pytest.mark.parametrize( + ("api_type", "expected_client_name"), + [ + ("chat_completions", "OpenAIChatCompletionClient"), + ("responses", "OpenAIChatClient"), + ], +) +def test_bare_llmconfig_api_type_selects_agent_framework_client( + api_type: str, expected_client_name: str +) -> None: + from agent_framework.openai import OpenAIChatClient, OpenAIChatCompletionClient + + from pyagentspec.adapters.agent_framework import AgentSpecLoader + + client = AgentSpecLoader().load_component( + LlmConfig( + name="test-model", + model_id="test-model", + api_provider="openai", + api_type=api_type, + url="http://test.example/v1", + ) + ) + + expected_client = { + "OpenAIChatClient": OpenAIChatClient, + "OpenAIChatCompletionClient": OpenAIChatCompletionClient, + }[expected_client_name] + assert isinstance(client, expected_client) + + def test_agentspec_converts_to_agent_framework_with_server_tool( weather_agent_server_tool: str, ) -> None: - from agent_framework import ChatAgent, FunctionTool - from agent_framework.openai import OpenAIChatClient + from agent_framework import Agent, FunctionTool + from agent_framework.openai import OpenAIChatCompletionClient from pyagentspec.adapters.agent_framework import AgentSpecLoader agent_component = AgentSpecDeserializer().from_yaml(weather_agent_server_tool) - assert isinstance(agent_component, Agent) + assert isinstance(agent_component, AgentSpecAgent) loader = AgentSpecLoader(tool_registry={"get_weather": get_weather}) agent = loader.load_yaml(weather_agent_server_tool) - assert isinstance(agent, ChatAgent) + assert isinstance(agent, Agent) # Agent Config assert agent_component.name == agent.name @@ -35,9 +99,9 @@ def test_agentspec_converts_to_agent_framework_with_server_tool( # Llm Config assert isinstance(agent_component.llm_config, VllmConfig) - assert isinstance(agent.chat_client, OpenAIChatClient) - assert agent_component.llm_config.url in agent.chat_client.service_url() - assert agent_component.llm_config.model_id == agent.chat_client.model_id + assert isinstance(agent.client, OpenAIChatCompletionClient) + assert agent_component.llm_config.url in agent.client.service_url() + assert agent_component.llm_config.model_id == agent.client.model default_generation_parameters = agent_component.llm_config.default_generation_parameters assert default_generation_parameters is not None assert default_generation_parameters.temperature == agent.additional_properties["temperature"] @@ -56,27 +120,26 @@ def test_agentspec_converts_to_agent_framework_with_server_tool( def test_agent_with_server_tool_runs_after_config_load(weather_agent_server_tool: str) -> None: - from agent_framework import ChatAgent + from agent_framework import Agent from pyagentspec.adapters.agent_framework import AgentSpecLoader loader = AgentSpecLoader(tool_registry={"get_weather": get_weather}) agent = loader.load_yaml(weather_agent_server_tool) - assert isinstance(agent, ChatAgent) + assert isinstance(agent, Agent) result = anyio.run(agent.run, "What is the weather like in Agadir?") contents = result.messages[-1].contents assert len(contents) > 0 - last_content = contents[-1] def test_remote_tool_with_agent(weather_agent_remote_tool: str) -> None: - from agent_framework import ChatAgent + from agent_framework import Agent from pyagentspec.adapters.agent_framework import AgentSpecLoader agent = AgentSpecLoader().load_yaml(weather_agent_remote_tool) - assert isinstance(agent, ChatAgent) + assert isinstance(agent, Agent) result = anyio.run(agent.run, "What is the weather like in Agadir?") contents = result.messages[-1].contents @@ -89,13 +152,13 @@ def test_server_tool_requires_confirmation_with_agent( weather_agent_server_tool_confirmation: str, ) -> None: - from agent_framework import ChatAgent, ChatMessage, FunctionTool, Role + from agent_framework import Agent, FunctionTool, Message from pyagentspec.adapters.agent_framework import AgentSpecLoader loader = AgentSpecLoader(tool_registry={"get_weather": get_weather}) agent = loader.load_yaml(weather_agent_server_tool_confirmation) - assert isinstance(agent, ChatAgent) + assert isinstance(agent, Agent) # Ensure approval mode is set to always_require for the converted tool tools = agent.default_options["tools"] or [] @@ -118,8 +181,8 @@ async def run_agent_with_confirmation(agent): final_result = await agent.run( [ "What is the weather like in Agadir?", - ChatMessage(role=Role.ASSISTANT, contents=[req]), - ChatMessage(role=Role.USER, contents=[req.to_function_approval_response(True)]), + Message(role="assistant", contents=[req]), + Message(role="user", contents=[req.to_function_approval_response(True)]), ], ) contents = final_result.messages[-1].contents diff --git a/pyagentspec/tests/adapters/langgraph/test_agentspec_to_langraph.py b/pyagentspec/tests/adapters/langgraph/test_agentspec_to_langraph.py index c1b35287..7c077651 100644 --- a/pyagentspec/tests/adapters/langgraph/test_agentspec_to_langraph.py +++ b/pyagentspec/tests/adapters/langgraph/test_agentspec_to_langraph.py @@ -220,29 +220,6 @@ def weather_agent_server_tool_openaicompatible_yaml(weather_agent_server_tool_ya ) -def test_weather_agent_with_server_tool_with_openaicompatible_llm_raises_without_api_key( - weather_agent_server_tool_openaicompatible_yaml: str, -) -> None: - """ - This test is checking the case of OpenAiCompatibleConfig. - The VllmConfig is already tested in all the tests above - """ - import openai - - from pyagentspec.adapters.langgraph import AgentSpecLoader - - old_value = os.environ.pop("OPENAI_API_KEY", None) - - try: - with pytest.raises(openai.OpenAIError, match="api_key"): - AgentSpecLoader(tool_registry={"get_weather": get_weather}).load_yaml( - weather_agent_server_tool_openaicompatible_yaml - ) - finally: - if old_value is not None: - os.environ["OPENAI_API_KEY"] = old_value - - @mock.patch.dict(os.environ, {"OPENAI_API_KEY": "MOCKED_KEY"}) @retry_test(max_attempts=3, wait_between_tries=2) def test_execute_weather_agent_with_server_tool_with_openaicompatible_llm( diff --git a/pyagentspec/tests/adapters/langgraph/test_managerworkers.py b/pyagentspec/tests/adapters/langgraph/test_managerworkers.py new file mode 100644 index 00000000..01a14389 --- /dev/null +++ b/pyagentspec/tests/adapters/langgraph/test_managerworkers.py @@ -0,0 +1,624 @@ +# Copyright © 2026 Oracle and/or its affiliates. +# +# This software is under the Apache License 2.0 +# (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0) or Universal Permissive License +# (UPL) 1.0 (LICENSE-UPL or https://oss.oracle.com/licenses/upl), at your option. + +from typing import Any, List, Optional +from unittest.mock import patch + +import pytest + +from pyagentspec.agent import Agent +from pyagentspec.llms import OpenAiCompatibleConfig +from pyagentspec.managerworkers import ManagerWorkers +from pyagentspec.property import Property + + +def _agent( + name: str, + llm_name: str, + description: str = "", + system_prompt: str = ".", + outputs: Optional[List[Property]] = None, +) -> Agent: + return Agent( + name=name, + description=description, + system_prompt=system_prompt, + llm_config=OpenAiCompatibleConfig(name=llm_name, model_id="fake", url="null"), + outputs=outputs, + ) + + +def _fake_llm(*ai_responses: Any) -> Any: + """A FakeMessagesListChatModel subclassed under ChatOpenAI so the react-agent + treats it as an OpenAI-style chat model.""" + from langchain_core.language_models.fake_chat_models import FakeMessagesListChatModel + from langchain_openai import ChatOpenAI + + class _FakeModel(FakeMessagesListChatModel, ChatOpenAI): + pass + + return _FakeModel(responses=list(ai_responses)) + + +def _load_with_fake_llms(mw: Any, default: Any = None, **fakes_by_llm_name: Any) -> Any: + """Compile ``mw`` offline, answering each LLM config (keyed by ``llm_config.name``) + with a queued fake; ``default`` answers any config not named. + + ``bind_tools`` is stubbed to return the same fake because + ``FakeMessagesListChatModel`` inherits it from ``ChatOpenAI``, which calls OpenAI. + """ + from langchain_core.language_models.fake_chat_models import FakeMessagesListChatModel + from langgraph.checkpoint.memory import MemorySaver + + from pyagentspec.adapters.langgraph import AgentSpecLoader + from pyagentspec.adapters.langgraph._langgraphconverter import AgentSpecToLangGraphConverter + + def _dispatch(_self: Any, llm_config: Any, *args: Any, **kwargs: Any) -> Any: + fake = fakes_by_llm_name.get(llm_config.name, default) + if fake is None: + raise AssertionError(f"unexpected llm_config: {llm_config.name}") + return fake + + loader = AgentSpecLoader(tool_registry={}, checkpointer=MemorySaver()) + with patch.object( + AgentSpecToLangGraphConverter, + "_llm_convert_to_langgraph", + autospec=True, + side_effect=_dispatch, + ), patch.object( + FakeMessagesListChatModel, "bind_tools", new=lambda self_obj, *a, **kw: self_obj + ): + return loader.load_component(mw) + + +def test_safe_node_name_normalizes_and_falls_back() -> None: + from pyagentspec.adapters.langgraph._managerworkers import _safe_node_name + + assert _safe_node_name("Research Helper", "id-1") == "research_helper" + assert _safe_node_name("My-Worker!! v2", "id-1") == "my_worker_v2" + # Name slugifies to empty → normalized id; both empty → constant fallback. + assert _safe_node_name("!!!", "sub-1") == "sub_1" + assert _safe_node_name("", "") == "worker" + + +def test_append_workers_roster_renders_one_line_per_worker() -> None: + from pyagentspec.adapters.langgraph._managerworkers import _append_workers_roster + + out = _append_workers_roster( + "Coordinate the team.", + [("research_helper", "Handles research"), ("drafter", "Drafts text")], + ) + assert out == ( + "Coordinate the team.\n\n" + "Available workers:\n" + "- research_helper: Handles research\n" + "- drafter: Drafts text" + ) + # Multiline descriptions are flattened so the one-line-per-worker shape survives. + out = _append_workers_roster("", [("helper", "First line\nsecond line\n third line ")]) + assert out == "Available workers:\n- helper: First line second line third line" + + +def test_route_manager_to_worker_or_end_returns_end_when_no_delegation() -> None: + from langchain_core.messages import AIMessage + from langgraph.graph import END + + from pyagentspec.adapters.langgraph._managerworkers import _make_manager_router + + route = _make_manager_router(["drafter"]) + not_delegating = AIMessage(content="Done.", tool_calls=[]) + assert route({"messages": [not_delegating]}) == END + assert route({"messages": []}) == END + + +def test_route_manager_to_worker_or_end_fans_out_one_send_per_delegation() -> None: + from langchain_core.messages import AIMessage + from langgraph.types import Send + + from pyagentspec.adapters.langgraph._managerworkers import ( + _DELEGATE_CALL_ID_KEY, + _DELEGATE_TASK_KEY, + _make_manager_router, + ) + + route = _make_manager_router(["drafter", "research_helper"]) + msg = AIMessage( + content="", + tool_calls=[ + {"name": "some_other_tool", "args": {}, "id": "c0"}, + {"name": "__delegate_to__drafter", "args": {"task": "x"}, "id": "c1"}, + {"name": "__delegate_to__research_helper", "args": {"task": "y"}, "id": "c2"}, + ], + ) + sends = route({"messages": [msg]}) + # Every delegation gets its own Send carrying the task and the tool_call_id its + # reply must answer. The non-delegation tool call already ran inside the manager's + # react loop and is ignored by routing. + assert all(isinstance(s, Send) for s in sends) + assert [s.node for s in sends] == ["drafter", "research_helper"] + assert [s.arg[_DELEGATE_TASK_KEY] for s in sends] == ["x", "y"] + assert [s.arg[_DELEGATE_CALL_ID_KEY] for s in sends] == ["c1", "c2"] + + +def test_route_manager_ignores_prefixed_tool_whose_suffix_is_not_a_worker() -> None: + """A tool call that merely looks like a delegation must not be routed: its suffix + is not a worker node, so a Send would target a non-existing node. It already ran + as a plain tool inside the react loop.""" + from langchain_core.messages import AIMessage + from langgraph.graph import END + + from pyagentspec.adapters.langgraph._managerworkers import _make_manager_router + + route = _make_manager_router(["drafter"]) + msg = AIMessage( + content="", + tool_calls=[{"name": "__delegate_to__nobody", "args": {"task": "x"}, "id": "c1"}], + ) + assert route({"messages": [msg]}) == END + + +def test_manager_workers_compiles_to_hierarchical_graph_topology() -> None: + from langchain_core.messages import AIMessage + from langgraph.graph import START + + from pyagentspec.adapters.langgraph._managerworkers import _MANAGER_NODE_KEY + + mw = ManagerWorkers( + name="ResearchTeam", + group_manager=_agent("Coordinator", "manager_llm", system_prompt="Coordinate the team."), + workers=[ + _agent("Research Helper", "worker_a_llm", description="Handles research"), + _agent("Drafter", "worker_b_llm", description="Drafts text"), + ], + ) + + compiled = _load_with_fake_llms(mw, default=_fake_llm(AIMessage(content="Done."))) + + builder = compiled.builder + assert _MANAGER_NODE_KEY in builder.nodes + assert "research_helper" in builder.nodes + assert "drafter" in builder.nodes + + # START → manager; every worker → manager (loop). + edge_pairs = {(src, dst) for src, dst in builder.edges} + assert (START, _MANAGER_NODE_KEY) in edge_pairs + assert ("research_helper", _MANAGER_NODE_KEY) in edge_pairs + assert ("drafter", _MANAGER_NODE_KEY) in edge_pairs + + # Manager → worker is a conditional edge. + assert builder.branches.get(_MANAGER_NODE_KEY) + + +def test_manager_workers_registers_a_delegation_tool_per_worker() -> None: + from langchain_core.messages import AIMessage + + from pyagentspec.adapters.langgraph._managerworkers import _MANAGER_NODE_KEY + + mw = ManagerWorkers( + name="Team", + group_manager=_agent("Coordinator", "manager_llm"), + workers=[_agent("Research Helper", "worker_llm", description="Handles research tasks")], + ) + + compiled = _load_with_fake_llms(mw, default=_fake_llm(AIMessage(content="Done."))) + + # The delegation tool the roster advertises is registered on the manager + # react-agent's tools node, so the LLM has the matching contract. + manager_subgraph = compiled.builder.nodes[_MANAGER_NODE_KEY].runnable + tools_node = manager_subgraph.builder.nodes["tools"].runnable + assert "__delegate_to__research_helper" in tools_node.tools_by_name + + +def test_manager_workers_delegates_and_routes_back_with_tool_message() -> None: + """The manager delegates, the worker runs in an isolated message context and its + answer comes back as a ToolMessage matched to the pending tool_call_id, and the + manager's next turn terminates the graph.""" + from langchain_core.messages import AIMessage, HumanMessage + + mw = ManagerWorkers( + name="Team", + group_manager=_agent("Coordinator", "manager_llm", system_prompt="You coordinate."), + workers=[_agent("Research Helper", "worker_llm", description="Handles research")], + ) + + # Manager turn 1: delegate. Manager turn 2: final answer (no tool call → END). + manager_responses = [ + AIMessage( + content="", + tool_calls=[ + { + "name": "__delegate_to__research_helper", + "args": {"task": "Look up Saturn"}, + "id": "call_1", + } + ], + ), + AIMessage(content="The worker reports: Saturn has rings."), + ] + worker_responses = [AIMessage(content="Saturn has rings.")] + + compiled = _load_with_fake_llms( + mw, + manager_llm=_fake_llm(*manager_responses), + worker_llm=_fake_llm(*worker_responses), + ) + + # Sync invocation only: FakeMessagesListChatModel overrides ``_generate`` but not + # ``_agenerate``, so the async path would resolve to ``ChatOpenAI._agenerate`` + # and call OpenAI. + result = compiled.invoke( + {"messages": [HumanMessage(content="Tell me about Saturn.")]}, + {"configurable": {"thread_id": "mw-1"}}, + ) + messages = result["messages"] + + assert isinstance(messages[-1], AIMessage) + assert "Saturn has rings" in messages[-1].content + tool_msgs = [m for m in messages if type(m).__name__ == "ToolMessage"] + assert tool_msgs and tool_msgs[0].tool_call_id == "call_1" + assert "Saturn has rings" in tool_msgs[0].content + + +def test_manager_workers_answers_every_delegation_in_a_single_turn() -> None: + """When one manager turn emits several delegations, each must be answered by its + own ToolMessage matched to the originating tool_call_id; an unanswered one is an + invalid tool-call/result sequence the manager would hallucinate around.""" + from langchain_core.messages import AIMessage, HumanMessage + + mw = ManagerWorkers( + name="Team", + group_manager=_agent("Coordinator", "manager_llm", system_prompt="You coordinate."), + workers=[_agent("Sub Agent", "worker_llm", description="Writes poems")], + ) + + # Turn 1: three delegations to the same worker in one AIMessage. Turn 2: terminate. + manager_responses = [ + AIMessage( + content="", + tool_calls=[ + { + "name": "__delegate_to__sub_agent", + "args": {"task": "Spanish poem"}, + "id": "call_1", + }, + { + "name": "__delegate_to__sub_agent", + "args": {"task": "French poem"}, + "id": "call_2", + }, + { + "name": "__delegate_to__sub_agent", + "args": {"task": "German poem"}, + "id": "call_3", + }, + ], + ), + AIMessage(content="Here are your three poems."), + ] + worker_responses = [AIMessage(content=f"poem #{i}") for i in range(1, 6)] + + compiled = _load_with_fake_llms( + mw, + manager_llm=_fake_llm(*manager_responses), + worker_llm=_fake_llm(*worker_responses), + ) + + result = compiled.invoke( + {"messages": [HumanMessage(content="Write 3 poems via sub-agents.")]}, + {"configurable": {"thread_id": "mw-multi"}}, + ) + messages = result["messages"] + + tool_msgs = [m for m in messages if type(m).__name__ == "ToolMessage"] + answered = sorted(m.tool_call_id for m in tool_msgs) + assert answered == ["call_1", "call_2", "call_3"] + assert all(m.content.startswith("poem #") for m in tool_msgs) + + +def test_nested_manager_workers_compiles_recursively() -> None: + """A worker that is itself a ManagerWorkers compiles through the same dispatch and + is wired in as a subgraph node of the outer parent graph.""" + from langchain_core.messages import AIMessage + + inner_mw = ManagerWorkers( + name="Inner", + group_manager=_agent("InnerManager", "inner_llm", system_prompt="Manage leaves."), + workers=[_agent("Leaf", "leaf_llm", description="Leaf task")], + ) + outer_mw = ManagerWorkers( + name="Outer", + group_manager=_agent("OuterManager", "outer_llm", system_prompt="Manage subteams."), + workers=[inner_mw], + ) + + compiled = _load_with_fake_llms(outer_mw, default=_fake_llm(AIMessage(content="Done."))) + + assert "inner" in compiled.builder.nodes + + +def test_rejects_non_agent_group_manager() -> None: + """A nested ManagerWorkers as group_manager is valid per the pyagentspec + validators, but the adapter needs a chat-LLM emitting tool_calls to route on.""" + from langgraph.checkpoint.memory import MemorySaver + + from pyagentspec.adapters.langgraph import AgentSpecLoader + + inner_mw = ManagerWorkers( + name="Inner", + group_manager=_agent("Inner", "i"), + workers=[_agent("Leaf", "l")], + ) + outer_mw = ManagerWorkers( + name="Outer", + group_manager=inner_mw, + workers=[_agent("Other", "o")], + ) + + loader = AgentSpecLoader(tool_registry={}, checkpointer=MemorySaver()) + with pytest.raises(NotImplementedError, match="group_manager must be an Agent"): + loader.load_component(outer_mw) + + +def test_workers_with_name_slug_collision_are_rejected() -> None: + """Two workers whose names normalize to the same node identifier would silently + overwrite each other in the parent graph; raise at load time instead.""" + from langgraph.checkpoint.memory import MemorySaver + + from pyagentspec.adapters.langgraph import AgentSpecLoader + + # Both worker names normalize to "helper_a". + mw = ManagerWorkers( + name="T", + group_manager=_agent("M", "m"), + workers=[_agent("Helper A", "a"), _agent("helper-a", "b")], + ) + + loader = AgentSpecLoader(tool_registry={}, checkpointer=MemorySaver()) + with pytest.raises(ValueError, match="collide after normalization"): + loader.load_component(mw) + + +def test_worker_events_stream_natively_namespaced_under_worker_node() -> None: + """Regression: a worker's token events must stream under the worker node's + checkpoint namespace so a consumer can attribute them to the sub-agent. The + wrapper must inherit the ambient run config; a fresh thread_id would detach the + worker into an unattributable top-level ``agent:`` run.""" + import asyncio + + from langchain_core.language_models.fake_chat_models import GenericFakeChatModel + from langchain_core.messages import AIMessage, HumanMessage + from langchain_core.runnables import RunnableConfig + from langgraph.graph import END, START, MessagesState, StateGraph + + from pyagentspec.adapters.langgraph._managerworkers import _wrap_worker_for_subgraph + + # A minimal worker graph that streams some content. + wmodel = GenericFakeChatModel(messages=iter([AIMessage(content="Saturn has rings")] * 9)) + wb = StateGraph(MessagesState) + + async def _wagent(state: Any, config: RunnableConfig) -> Any: + return {"messages": [await wmodel.ainvoke(state["messages"], config=config)]} + + wb.add_node("agent", _wagent) + wb.add_edge(START, "agent") + wb.add_edge("agent", END) + worker_graph = wb.compile() + + # Parent: a plain manager node emits the delegate tool call, then routes to the + # wrapped worker node. + pb = StateGraph(MessagesState) + + def _manager(state: Any) -> Any: + return { + "messages": [ + AIMessage( + content="", + tool_calls=[ + { + "name": "__delegate_to__research_helper", + "args": {"task": "Saturn"}, + "id": "c1", + } + ], + ) + ] + } + + pb.add_node("__manager__", _manager) + pb.add_node("research_helper", _wrap_worker_for_subgraph(worker_graph, "research_helper")) + pb.add_edge(START, "__manager__") + pb.add_edge("__manager__", "research_helper") + pb.add_edge("research_helper", END) + parent = pb.compile() + + async def _collect() -> Any: + namespaces = [] + async for ev in parent.astream_events( + {"messages": [HumanMessage(content="hi")]}, + {"configurable": {"thread_id": "t"}}, + version="v2", + ): + if ev["event"] == "on_chat_model_stream": + ns = (ev.get("metadata") or {}).get("langgraph_checkpoint_ns", "") + namespaces.append(ns) + return namespaces + + namespaces = asyncio.run(_collect()) + assert namespaces, "expected the worker to emit token-stream events" + assert all(ns.startswith("research_helper:") for ns in namespaces), namespaces + + +def test_is_delegation_tool_name_matches_only_the_synthetic_prefix() -> None: + from pyagentspec.adapters.langgraph._managerworkers import ( + DELEGATE_TOOL_PREFIX, + is_delegation_tool_name, + ) + + assert DELEGATE_TOOL_PREFIX == "__delegate_to__" + assert is_delegation_tool_name("__delegate_to__research_helper") + assert not is_delegation_tool_name("get_weather") + # A real tool plausibly named delegate_to_ is not a delegation. + assert not is_delegation_tool_name("delegate_to_someone") + # Nor is one merely *containing* the prefix mid-name. + assert not is_delegation_tool_name("please__delegate_to__someone") + assert not is_delegation_tool_name(None) + assert not is_delegation_tool_name(123) + + +def test_manager_workers_leaves_astream_events_unwrapped() -> None: + """The delegation protocol is deliberately visible: only stream/astream are + patched (for the ManagerWorkersExecutionSpan); nothing wraps ``astream_events`` + to scrub the delegation tool calls.""" + mw = ManagerWorkers( + name="Team", + group_manager=_agent("Coordinator", "manager_llm"), + workers=[_agent("Research Helper", "worker_llm")], + ) + + compiled = _load_with_fake_llms(mw, default=_fake_llm()) + + assert "stream" in compiled.__dict__ and "astream" in compiled.__dict__ + assert "astream_events" not in compiled.__dict__ + + +def test_managerworkers_infers_inputs_from_group_manager_prompt() -> None: + """A ManagerWorkers exposes the group manager's prompt placeholders as inputs, so + a flow AgentNode wrapping it declares input ports a DataFlowEdge can resolve.""" + manager = _agent( + "manager", + "manager_llm", + system_prompt="Translate the following to Arabic:\n\n{{joke}}\n\nMake {{count}} variants.", + ) + worker = _agent("worker", "worker_llm", system_prompt="You translate.") + mw = ManagerWorkers(name="mw", group_manager=manager, workers=[worker]) + + assert sorted(p.title for p in (mw.inputs or [])) == ["count", "joke"] + + +def test_managerworkers_infers_outputs_from_group_manager() -> None: + """Symmetric with inputs: a ManagerWorkers exposes the group manager's outputs.""" + from pyagentspec.property import StringProperty + + manager = _agent( + "manager", + "manager_llm", + system_prompt="Answer the question.", + outputs=[StringProperty(title="answer")], + ) + worker = _agent("worker", "worker_llm", system_prompt="You help.") + mw = ManagerWorkers(name="mw", group_manager=manager, workers=[worker]) + + assert [p.title for p in (mw.outputs or [])] == ["answer"] + + +def _flow_with_manager_workers_step(outputs: List[Property]) -> Any: + """A start → AgentNode(ManagerWorkers) → end flow whose end node exposes + ``outputs``, with the data edges resolving the manager's ``joke`` input and + every output.""" + from pyagentspec.flows.edges import ControlFlowEdge, DataFlowEdge + from pyagentspec.flows.flow import Flow + from pyagentspec.flows.nodes import AgentNode, EndNode, StartNode + from pyagentspec.property import StringProperty + + joke = StringProperty(title="joke") + manager = _agent( + "manager", + "manager_llm", + system_prompt="Translate the following to Arabic:\n\n{{joke}}", + outputs=outputs, + ) + worker = _agent("worker", "worker_llm", system_prompt="You translate.") + mw = ManagerWorkers(name="translator", group_manager=manager, workers=[worker]) + assert [p.title for p in (mw.inputs or [])] == ["joke"] + + manager_node = AgentNode(name="manager_node", agent=mw) + start_node = StartNode(name="start", inputs=[joke]) + end_node = EndNode(name="end", outputs=outputs) + return Flow( + name="flow", + start_node=start_node, + nodes=[start_node, manager_node, end_node], + control_flow_connections=[ + ControlFlowEdge(name="start_to_node", from_node=start_node, to_node=manager_node), + ControlFlowEdge(name="node_to_end", from_node=manager_node, to_node=end_node), + ], + data_flow_connections=[ + DataFlowEdge( + name="joke_edge", + source_node=start_node, + source_output=joke.title, + destination_node=manager_node, + destination_input=joke.title, + ), + ] + + [ + DataFlowEdge( + name=f"{output.title}_edge", + source_node=manager_node, + source_output=output.title, + destination_node=end_node, + destination_input=output.title, + ) + for output in outputs + ], + outputs=outputs, + ) + + +def test_managerworkers_runs_as_a_flow_step_with_data_edge_inputs() -> None: + """A ManagerWorkers flow step loads with its data edge resolved and executes: + loading proves the node exposes the ``joke`` input the edge targets, running + proves the manager's answer comes back as the node's single string output.""" + from langchain_core.language_models.fake_chat_models import FakeMessagesListChatModel + from langchain_core.messages import AIMessage + from langgraph.checkpoint.memory import MemorySaver + + from pyagentspec.adapters.langgraph import AgentSpecLoader + from pyagentspec.adapters.langgraph._langgraphconverter import AgentSpecToLangGraphConverter + from pyagentspec.property import StringProperty + + # The final message has no tool_calls → the manager routes to END without delegating. + fake_llm = _fake_llm(AIMessage(content="لماذا...")) + flow = _flow_with_manager_workers_step(outputs=[StringProperty(title="translated")]) + + loader = AgentSpecLoader(tool_registry={}, checkpointer=MemorySaver()) + with patch.object( + AgentSpecToLangGraphConverter, + "_llm_convert_to_langgraph", + autospec=True, + side_effect=lambda self_obj, llm_config, *a, **k: fake_llm, + ), patch.object( + FakeMessagesListChatModel, + "bind_tools", + new=lambda self_obj, *a, **k: self_obj, + ): + compiled = loader.load_component(flow) + result = compiled.invoke( + { + "inputs": {"joke": "Why did the car..."}, + "messages": [{"role": "user", "content": ""}], + }, + {"configurable": {"thread_id": "managerworkers-node"}}, + ) + + assert result["outputs"]["translated"] == "لماذا..." + + +def test_managerworkers_flow_step_with_unsupported_outputs_fails_at_conversion() -> None: + """A ManagerWorkers flow step supports a single string output only; any other + shape must be rejected when the flow is converted, not once the step runs.""" + from langgraph.checkpoint.memory import MemorySaver + + from pyagentspec.adapters.langgraph import AgentSpecLoader + from pyagentspec.property import StringProperty + + flow = _flow_with_manager_workers_step( + outputs=[StringProperty(title="translated"), StringProperty(title="notes")] + ) + + loader = AgentSpecLoader(tool_registry={}, checkpointer=MemorySaver()) + with pytest.raises(NotImplementedError, match="single string output"): + loader.load_component(flow) diff --git a/pyagentspec/tests/serialization/test_dbms_vector_chain_llm_config.py b/pyagentspec/tests/serialization/test_dbms_vector_chain_llm_config.py new file mode 100644 index 00000000..26926bc1 --- /dev/null +++ b/pyagentspec/tests/serialization/test_dbms_vector_chain_llm_config.py @@ -0,0 +1,84 @@ +# Copyright © 2026 Oracle and/or its affiliates. +# +# This software is under the Apache License 2.0 +# (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0) or Universal Permissive License +# (UPL) 1.0 (LICENSE-UPL or https://oss.oracle.com/licenses/upl), at your option. + +import json + +import pytest + +from pyagentspec.llms import DbmsVectorChainLlmConfig, LlmGenerationConfig +from pyagentspec.retrypolicy import RetryPolicy +from pyagentspec.serialization import AgentSpecDeserializer, AgentSpecSerializer +from pyagentspec.versioning import AgentSpecVersionEnum + + +def test_dbms_vector_chain_llm_config_serializes() -> None: + config = DbmsVectorChainLlmConfig( + id="database-llm", + name="database-openai", + model_id="gpt-4o", + provider="openai", + url="https://api.openai.com/v1/chat/completions", + credential_name="MY_OPENAI_CREDENTIAL", + transfer_timeout=120, + default_generation_parameters=LlmGenerationConfig(temperature=0.3), + retry_policy=RetryPolicy(max_attempts=3), + ) + + serialized = json.loads(AgentSpecSerializer().to_json(config)) + + assert serialized["component_type"] == "DbmsVectorChainLlmConfig" + assert serialized["model_id"] == "gpt-4o" + assert "model" not in serialized + assert serialized["provider"] == "openai" + assert serialized["url"] == "https://api.openai.com/v1/chat/completions" + assert serialized["host"] is None + assert serialized["credential_name"] == "MY_OPENAI_CREDENTIAL" + assert serialized["transfer_timeout"] == 120 + assert serialized["connection_config"] is None + assert serialized["default_generation_parameters"]["temperature"] == 0.3 + assert "api_key" not in serialized + assert "api_provider" not in serialized + assert "api_type" not in serialized + assert serialized["retry_policy"]["max_attempts"] == 3 + + +def test_dbms_vector_chain_llm_config_round_trips() -> None: + config = DbmsVectorChainLlmConfig( + id="database-llm", + name="database-openai", + model_id="gpt-4o", + provider="openai", + url="https://api.openai.com/v1/chat/completions", + credential_name="MY_OPENAI_CREDENTIAL", + ) + serializer = AgentSpecSerializer() + + serialized = serializer.to_yaml(config) + deserialized = AgentSpecDeserializer().from_yaml(serialized) + + assert "model_id: gpt-4o" in serialized + assert "model:" not in serialized + assert deserialized == config + assert deserialized.model_id == "gpt-4o" + + +def test_dbms_vector_chain_llm_config_requires_v26_2() -> None: + config = DbmsVectorChainLlmConfig( + id="database-llm", + name="database-openai", + model_id="gpt-4o", + provider="openai", + url="https://api.openai.com/v1/chat/completions", + credential_name="MY_OPENAI_CREDENTIAL", + ) + + with pytest.raises(ValueError, match="Invalid agentspec_version"): + AgentSpecSerializer().to_dict(config, agentspec_version=AgentSpecVersionEnum.v26_1_2) + + dumped = AgentSpecSerializer().to_dict(config) + dumped["agentspec_version"] = AgentSpecVersionEnum.v26_1_2.value + with pytest.raises(ValueError, match="Invalid agentspec_version"): + AgentSpecDeserializer().from_dict(dumped) diff --git a/pyagentspec/tests/serialization/test_managerworkers.py b/pyagentspec/tests/serialization/test_managerworkers.py index fefb4271..dba17837 100644 --- a/pyagentspec/tests/serialization/test_managerworkers.py +++ b/pyagentspec/tests/serialization/test_managerworkers.py @@ -9,6 +9,7 @@ from pyagentspec.agent import Agent from pyagentspec.llms import VllmConfig from pyagentspec.managerworkers import ManagerWorkers +from pyagentspec.property import StringProperty from pyagentspec.serialization import AgentSpecDeserializer, AgentSpecSerializer from pyagentspec.versioning import AgentSpecVersionEnum @@ -109,3 +110,60 @@ def test_deserializing_managerworkers_with_unsupported_version_raises_error( with pytest.raises(ValueError, match="Invalid agentspec_version"): AgentSpecDeserializer().from_yaml(serialized_managerworkers) + + +def test_managerworkers_infers_manager_ios_in_current_version() -> None: + llm_config = VllmConfig(name="model", model_id="model_id", url="https://example.com") + manager = Agent( + name="manager", + llm_config=llm_config, + system_prompt="Manage the team.", + outputs=[StringProperty(title="answer")], + ) + worker = Agent(name="worker", llm_config=llm_config, system_prompt="Help the manager.") + manager_workers = ManagerWorkers( + name="team", + group_manager=manager, + workers=[worker], + outputs=[StringProperty(title="answer")], + ) + + assert manager_workers.outputs == manager.outputs + assert manager_workers.min_agentspec_version == AgentSpecVersionEnum.current_version + with pytest.raises(ValueError, match="Invalid agentspec_version"): + AgentSpecSerializer().to_dict( + manager_workers, agentspec_version=AgentSpecVersionEnum.v26_3_0 + ) + + +def test_managerworkers_without_ios_with_manager_ios_is_legacy_compatible() -> None: + llm_config = VllmConfig(name="model", model_id="model_id", url="https://example.com") + manager = Agent( + name="manager", + llm_config=llm_config, + system_prompt="Manage {{question}}.", + inputs=[StringProperty(title="question")], + outputs=[StringProperty(title="answer")], + ) + worker = Agent(name="worker", llm_config=llm_config, system_prompt="Help the manager.") + manager_workers = ManagerWorkers( + name="team", + group_manager=manager, + workers=[worker], + inputs=[], + outputs=[], + ) + + assert manager_workers.inputs == [] + assert manager_workers.outputs == [] + assert manager_workers.min_agentspec_version == AgentSpecVersionEnum.v25_4_2 + assert manager_workers.max_agentspec_version == AgentSpecVersionEnum.v26_3_0 + + serialized = AgentSpecSerializer().to_dict( + manager_workers, agentspec_version=AgentSpecVersionEnum.v26_3_0 + ) + deserialized = AgentSpecDeserializer().from_dict(serialized) + + assert isinstance(deserialized, ManagerWorkers) + assert deserialized.inputs == [] + assert deserialized.outputs == [] diff --git a/pyagentspec/tests/serialization/test_mcp_tools.py b/pyagentspec/tests/serialization/test_mcp_tools.py index f49072a5..8a494e36 100644 --- a/pyagentspec/tests/serialization/test_mcp_tools.py +++ b/pyagentspec/tests/serialization/test_mcp_tools.py @@ -138,7 +138,7 @@ def test_mcp_tool_with_retry_policy_can_be_serialized_then_deserialized() -> Non dumped_tool = AgentSpecSerializer().to_dict(mcp_tool) loaded_tool = AgentSpecDeserializer().from_dict(dumped_tool) - assert dumped_tool["agentspec_version"] == AgentSpecVersionEnum.v26_2_0.value + assert dumped_tool["agentspec_version"] == AgentSpecVersionEnum.v26_3_0.value assert dumped_tool["retry_policy"]["max_attempts"] == 3 assert AgentSpecSerializer().to_dict(loaded_tool) == dumped_tool @@ -159,7 +159,7 @@ def test_mcp_toolbox_with_retry_policy_can_be_serialized_then_deserialized() -> dumped_toolbox = AgentSpecSerializer().to_dict(mcp_toolbox) loaded_toolbox = AgentSpecDeserializer().from_dict(dumped_toolbox) - assert dumped_toolbox["agentspec_version"] == AgentSpecVersionEnum.v26_2_0.value + assert dumped_toolbox["agentspec_version"] == AgentSpecVersionEnum.v26_3_0.value assert dumped_toolbox["retry_policy"]["max_attempts"] == 4 assert AgentSpecSerializer().to_dict(loaded_toolbox) == dumped_toolbox diff --git a/pyagentspec/tests/serialization/test_serialization.py b/pyagentspec/tests/serialization/test_serialization.py index f65d7af5..29d0a7f5 100644 --- a/pyagentspec/tests/serialization/test_serialization.py +++ b/pyagentspec/tests/serialization/test_serialization.py @@ -323,15 +323,15 @@ def test_deserialization_and_serialization_preserves_min_version(simplest_flow: ) -def test_deserialization_accepts_v26_2_0_configs(simplest_flow: Flow) -> None: +def test_deserialization_accepts_v26_3_0_configs(simplest_flow: Flow) -> None: serialized_flow = AgentSpecSerializer().to_dict( - simplest_flow, agentspec_version=AgentSpecVersionEnum.v26_2_0 + simplest_flow, agentspec_version=AgentSpecVersionEnum.v26_3_0 ) deserialized_flow = AgentSpecDeserializer().from_dict(serialized_flow) - assert serialized_flow[AGENTSPEC_VERSION_FIELD_NAME] == AgentSpecVersionEnum.v26_2_0.value - assert deserialized_flow.min_agentspec_version == AgentSpecVersionEnum.v26_2_0 + assert serialized_flow[AGENTSPEC_VERSION_FIELD_NAME] == AgentSpecVersionEnum.v26_3_0.value + assert deserialized_flow.min_agentspec_version == AgentSpecVersionEnum.v26_3_0 def test_dict_serialization_and_deserialization(simplest_flow: Flow) -> None: diff --git a/pyagentspec/tests/test_schema_generation.py b/pyagentspec/tests/test_schema_generation.py index 05bb0ce9..4ddb9340 100644 --- a/pyagentspec/tests/test_schema_generation.py +++ b/pyagentspec/tests/test_schema_generation.py @@ -19,6 +19,7 @@ from pyagentspec.flows.flow import Flow from pyagentspec.flows.node import Node from pyagentspec.flows.nodes import LlmNode +from pyagentspec.llms import DbmsVectorChainLlmConfig from pyagentspec.llms.llmconfig import LlmConfig from pyagentspec.llms.vllmconfig import VllmConfig from pyagentspec.serialization import AgentSpecSerializer @@ -43,6 +44,24 @@ def test_llmconfig_schema_contains_all_concrete_llmconfig_types() -> None: assert len(schema["anyOf"]) == len(llm_config_subtypes) + 2 +def test_dbms_vector_chain_llm_config_is_registered_in_schema() -> None: + assert BUILTIN_CLASS_MAP["DbmsVectorChainLlmConfig"] is DbmsVectorChainLlmConfig + + schema = DbmsVectorChainLlmConfig.model_json_schema(only_core_components=True) + config_schema = schema["$defs"]["BaseDbmsVectorChainLlmConfig"] + + assert "model_id" in config_schema["properties"] + assert "model" not in config_schema["properties"] + assert "api_provider" not in config_schema["properties"] + assert "api_type" not in config_schema["properties"] + assert "api_key" not in config_schema["properties"] + assert "retry_policy" in config_schema["properties"] + assert {"$ref": "#/$defs/OracleDatabaseConnectionConfig"} in config_schema["properties"][ + "connection_config" + ]["anyOf"] + assert config_schema["properties"]["host"]["anyOf"][0]["const"] == "local" + + def test_llmnode_schema_contains_all_concrete_llmconfig_types() -> None: schema = LlmNode.model_json_schema(only_core_components=True) # because all concrete components can be either serialized as a reference or as their properties diff --git a/pyagentspec/tests/test_versioning.py b/pyagentspec/tests/test_versioning.py index 45a3aa9a..0d680310 100644 --- a/pyagentspec/tests/test_versioning.py +++ b/pyagentspec/tests/test_versioning.py @@ -30,10 +30,10 @@ from .serialization.conftest import simplest_flow # noqa: F401 -def test_current_version_is_26_2_0() -> None: - assert AgentSpecVersionEnum.current_version == AgentSpecVersionEnum.v26_2_0 - assert AgentSpecVersionEnum.latest_supported_version == AgentSpecVersionEnum.v26_2_0 - assert AgentSpecVersionEnum("26.2.0") == AgentSpecVersionEnum.v26_2_0 +def test_current_version_is_26_4_0() -> None: + assert AgentSpecVersionEnum.current_version == AgentSpecVersionEnum.v26_4_0 + assert AgentSpecVersionEnum.latest_supported_version == AgentSpecVersionEnum.v26_4_0 + assert AgentSpecVersionEnum("26.4.0") == AgentSpecVersionEnum.v26_4_0 def test_flow_exports_with_agentspec_version(simplest_flow: Flow) -> None: diff --git a/pyagentspec/tests/validation/test_agentic_patterns_validation.py b/pyagentspec/tests/validation/test_agentic_patterns_validation.py index 81a13af8..2eeab694 100644 --- a/pyagentspec/tests/validation/test_agentic_patterns_validation.py +++ b/pyagentspec/tests/validation/test_agentic_patterns_validation.py @@ -14,6 +14,7 @@ from pyagentspec.flows.nodes.startnode import StartNode from pyagentspec.llms import OpenAiConfig from pyagentspec.managerworkers import ManagerWorkers +from pyagentspec.property import FloatProperty, ListProperty, StringProperty from pyagentspec.swarm import Swarm @@ -79,6 +80,85 @@ def test_managerworkers_with_different_agentic_components_can_be_validated() -> ) +def test_managerworkers_with_ios_matching_the_group_manager_can_be_validated() -> None: + manager_agent = Agent( + name="manager_agent", + system_prompt="Answer about {{topic}}.", + llm_config=OpenAiConfig(name="default", model_id="test_model"), + outputs=[StringProperty(title="answer")], + ) + worker_agent = Agent( + name="worker_agent", + system_prompt="You help.", + llm_config=OpenAiConfig(name="default", model_id="test_model"), + ) + + # The I/Os of a ManagerWorkers must be the I/Os of its group manager (same name + # and type); redeclaring them explicitly is valid. + _ = ManagerWorkers( + name="managerworkers", + group_manager=manager_agent, + workers=[worker_agent], + inputs=[StringProperty(title="topic")], + outputs=[StringProperty(title="answer")], + ) + + +def test_managerworkers_with_ios_not_matching_the_group_manager_raises_errors() -> None: + manager_agent = Agent( + name="manager_agent", + system_prompt="Answer about {{topic}}.", + llm_config=OpenAiConfig(name="default", model_id="test_model"), + outputs=[StringProperty(title="answer")], + ) + worker_agent = Agent( + name="worker_agent", + system_prompt="You help.", + llm_config=OpenAiConfig(name="default", model_id="test_model"), + ) + + # Same title as a group manager input, but a different type. + with pytest.raises(ValueError, match="must match the inputs of its group manager"): + ManagerWorkers( + name="managerworkers", + group_manager=manager_agent, + workers=[worker_agent], + inputs=[FloatProperty(title="topic")], + ) + + # Same title as a group manager output, but a different type. + with pytest.raises(ValueError, match="must match the outputs of its group manager"): + ManagerWorkers( + name="managerworkers", + group_manager=manager_agent, + workers=[worker_agent], + outputs=[FloatProperty(title="answer")], + ) + + # The comparison must also inspect nested schema types, rather than only the + # top-level ``array`` type. + list_output_manager = manager_agent.model_copy( + update={"outputs": [ListProperty(title="answer", item_type=StringProperty())]} + ) + with pytest.raises(ValueError, match="must match the outputs of its group manager"): + ManagerWorkers( + name="managerworkers", + group_manager=list_output_manager, + workers=[worker_agent], + outputs=[ListProperty(title="answer", item_type=FloatProperty())], + ) + + # A title the group manager does not declare is rejected by the base + # ComponentWithIO validation. + with pytest.raises(ValueError, match="expected only properties with the titles"): + ManagerWorkers( + name="managerworkers", + group_manager=manager_agent, + workers=[worker_agent], + outputs=[StringProperty(title="answer"), StringProperty(title="extra")], + ) + + def test_swarm_with_empty_relationships_raises_errors() -> None: first_agent = Agent( name="first_agent", diff --git a/pyagentspec/tests/validation/test_dbms_vector_chain_llm_config.py b/pyagentspec/tests/validation/test_dbms_vector_chain_llm_config.py new file mode 100644 index 00000000..4925fc7a --- /dev/null +++ b/pyagentspec/tests/validation/test_dbms_vector_chain_llm_config.py @@ -0,0 +1,133 @@ +# Copyright © 2026 Oracle and/or its affiliates. +# +# This software is under the Apache License 2.0 +# (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0) or Universal Permissive License +# (UPL) 1.0 (LICENSE-UPL or https://oss.oracle.com/licenses/upl), at your option. + +from typing import Any + +import pytest +from pydantic import ValidationError + +from pyagentspec.datastores.oracle import TlsOracleDatabaseConnectionConfig +from pyagentspec.llms import DbmsVectorChainLlmConfig + + +def valid_config_kwargs() -> dict[str, Any]: + return { + "name": "database-openai", + "model_id": "gpt-4o", + "provider": "openai", + "url": "https://api.openai.com/v1/chat/completions", + "credential_name": "MY_OPENAI_CREDENTIAL", + } + + +@pytest.mark.parametrize( + "field_name", + ["model_id", "provider", "url"], +) +def test_dbms_vector_chain_llm_config_rejects_empty_required_fields(field_name: str) -> None: + kwargs = valid_config_kwargs() + kwargs[field_name] = "" + + with pytest.raises(ValidationError, match=field_name): + DbmsVectorChainLlmConfig(**kwargs) + + +def test_dbms_vector_chain_llm_config_rejects_model_alias() -> None: + kwargs = valid_config_kwargs() + kwargs["model"] = kwargs.pop("model_id") + + with pytest.raises(ValidationError, match="model_id"): + DbmsVectorChainLlmConfig(**kwargs) + + +def test_dbms_vector_chain_llm_config_requires_credential_without_host() -> None: + kwargs = valid_config_kwargs() + kwargs.pop("credential_name") + + with pytest.raises(ValidationError, match="credential_name"): + DbmsVectorChainLlmConfig(**kwargs) + + +@pytest.mark.parametrize("provider", ["openai", "ollama"]) +def test_dbms_vector_chain_llm_config_allows_local_host_without_credential( + provider: str, +) -> None: + kwargs = valid_config_kwargs() + kwargs["provider"] = provider + kwargs["host"] = "local" + kwargs.pop("credential_name") + + config = DbmsVectorChainLlmConfig(**kwargs) + + assert config.credential_name is None + + +def test_dbms_vector_chain_llm_config_rejects_host_with_credential() -> None: + kwargs = valid_config_kwargs() + kwargs["host"] = "local" + + with pytest.raises(ValidationError, match="host.*credential_name"): + DbmsVectorChainLlmConfig(**kwargs) + + +def test_dbms_vector_chain_llm_config_rejects_nonlocal_host() -> None: + kwargs = valid_config_kwargs() + kwargs.pop("credential_name") + kwargs["host"] = "public" + + with pytest.raises(ValidationError, match="host"): + DbmsVectorChainLlmConfig(**kwargs) + + +def test_dbms_vector_chain_llm_config_rejects_local_host_for_unsupported_provider() -> None: + kwargs = valid_config_kwargs() + kwargs["provider"] = "cohere" + kwargs["host"] = "local" + kwargs.pop("credential_name") + + with pytest.raises(ValidationError, match="openai.*ollama"): + DbmsVectorChainLlmConfig(**kwargs) + + +def test_dbms_vector_chain_llm_config_rejects_negative_transfer_timeout() -> None: + kwargs = valid_config_kwargs() + kwargs["transfer_timeout"] = -1 + + with pytest.raises(ValidationError, match="transfer_timeout"): + DbmsVectorChainLlmConfig(**kwargs) + + +def test_dbms_vector_chain_llm_config_accepts_optional_connection_config() -> None: + kwargs = valid_config_kwargs() + connection_config = TlsOracleDatabaseConnectionConfig( + name="database-connection", + user="test-user", + password="test-password", # nosec B106 + dsn="test-dsn", + ) + kwargs["connection_config"] = connection_config + + config = DbmsVectorChainLlmConfig(**kwargs) + + assert config.connection_config is connection_config + + +@pytest.mark.parametrize( + "field_name,value", + [ + ("api_provider", "openai"), + ("api_type", "chat_completions"), + ("api_key", "not-a-real-key"), + ], +) +def test_dbms_vector_chain_llm_config_rejects_unsupported_inherited_fields( + field_name: str, value: Any +) -> None: + kwargs = valid_config_kwargs() + kwargs[field_name] = value + + with pytest.raises(ValidationError, match=field_name): + DbmsVectorChainLlmConfig(**kwargs) diff --git a/tsagentspec/src/agents/manager-workers.ts b/tsagentspec/src/agents/manager-workers.ts index 5e32e4e1..03aaea42 100644 --- a/tsagentspec/src/agents/manager-workers.ts +++ b/tsagentspec/src/agents/manager-workers.ts @@ -3,7 +3,7 @@ */ import { z } from "zod"; import { ComponentWithIOSchema } from "../component.js"; -import type { Property } from "../property.js"; +import { propertiesHaveSameType, type Property } from "../property.js"; // z.record(z.unknown()) is used instead of AgenticComponentUnion to break a circular // dependency (ManagerWorkers -> AgenticComponentUnion -> ManagerWorkers). Validation of @@ -18,6 +18,43 @@ export const ManagerWorkersSchema = ComponentWithIOSchema.extend({ export type ManagerWorkers = z.infer; +function getComponentProperties( + component: Record, + field: "inputs" | "outputs", +): Property[] { + const properties = component[field]; + return Array.isArray(properties) ? (properties as Property[]) : []; +} + +function validatePropertiesMatchManager( + properties: Property[], + managerProperties: Property[], + kind: "inputs" | "outputs", +): void { + const managerPropertiesByTitle = new Map( + managerProperties.map((property) => [property.title, property]), + ); + const propertiesByTitle = new Map( + properties.map((property) => [property.title, property]), + ); + if ( + propertiesByTitle.size !== properties.length || + propertiesByTitle.size !== managerPropertiesByTitle.size + ) { + throw new Error( + `The ${kind} of a ManagerWorkers must match the ${kind} of its group manager.`, + ); + } + for (const property of properties) { + const managerProperty = managerPropertiesByTitle.get(property.title); + if (!managerProperty || !propertiesHaveSameType(property, managerProperty)) { + throw new Error( + `The ${kind} of a ManagerWorkers must match the ${kind} of its group manager.`, + ); + } + } +} + export function createManagerWorkers(opts: { name: string; groupManager: Record; @@ -31,9 +68,19 @@ export function createManagerWorkers(opts: { if (opts.workers.some(w => w === opts.groupManager)) { throw new Error("Group manager cannot be a worker."); } + const managerInputs = getComponentProperties(opts.groupManager, "inputs"); + const managerOutputs = getComponentProperties(opts.groupManager, "outputs"); + if (opts.inputs !== undefined) { + validatePropertiesMatchManager(opts.inputs, managerInputs, "inputs"); + } + if (opts.outputs !== undefined) { + validatePropertiesMatchManager(opts.outputs, managerOutputs, "outputs"); + } return Object.freeze( ManagerWorkersSchema.parse({ ...opts, + inputs: opts.inputs ?? managerInputs, + outputs: opts.outputs ?? managerOutputs, componentType: "ManagerWorkers" as const, }), ); diff --git a/tsagentspec/src/versioning.ts b/tsagentspec/src/versioning.ts index 94df5c04..fa566920 100644 --- a/tsagentspec/src/versioning.ts +++ b/tsagentspec/src/versioning.ts @@ -10,14 +10,17 @@ export const AgentSpecVersion = { V25_4_1: "25.4.1", V25_4_2: "25.4.2", V26_1_0: "26.1.0", + V26_1_2: "26.1.2", V26_2_0: "26.2.0", + V26_3_0: "26.3.0", + V26_4_0: "26.4.0", } as const; export type AgentSpecVersion = (typeof AgentSpecVersion)[keyof typeof AgentSpecVersion]; /** The current (latest) agent spec version */ -export const CURRENT_VERSION: AgentSpecVersion = AgentSpecVersion.V26_2_0; +export const CURRENT_VERSION: AgentSpecVersion = AgentSpecVersion.V26_4_0; /** Field name for the agentspec version in serialized JSON/YAML */ export const AGENTSPEC_VERSION_FIELD_NAME = "agentspec_version"; diff --git a/tsagentspec/tests/agents/manager-workers.test.ts b/tsagentspec/tests/agents/manager-workers.test.ts index 3f58b895..7daac563 100644 --- a/tsagentspec/tests/agents/manager-workers.test.ts +++ b/tsagentspec/tests/agents/manager-workers.test.ts @@ -3,6 +3,8 @@ import { createManagerWorkers, createAgent, createOpenAiCompatibleConfig, + numberProperty, + stringProperty, } from "../../src/index.js"; function makeLlmConfig() { @@ -118,4 +120,42 @@ describe("ManagerWorkers", () => { }), ).toThrow("Group manager cannot be a worker."); }); + + it("should infer the group manager's inputs and outputs", () => { + const topic = stringProperty({ title: "topic" }); + const answer = stringProperty({ title: "answer" }); + const manager = createAgent({ + name: "manager", + llmConfig: makeLlmConfig(), + systemPrompt: "Manage the team.", + inputs: [topic], + outputs: [answer], + }); + const mw = createManagerWorkers({ + name: "test-mw", + groupManager: manager, + workers: [makeAgent("worker")], + }); + + expect(mw.inputs).toEqual([topic]); + expect(mw.outputs).toEqual([answer]); + }); + + it("should reject explicit I/O that differs from the group manager", () => { + const manager = createAgent({ + name: "manager", + llmConfig: makeLlmConfig(), + systemPrompt: "Manage the team.", + outputs: [stringProperty({ title: "answer" })], + }); + + expect(() => + createManagerWorkers({ + name: "test-mw", + groupManager: manager, + workers: [makeAgent("worker")], + outputs: [numberProperty({ title: "answer" })], + }), + ).toThrow("outputs of a ManagerWorkers must match"); + }); }); diff --git a/tsagentspec/tests/versioning.test.ts b/tsagentspec/tests/versioning.test.ts index d8b63f9c..06d13be4 100644 --- a/tsagentspec/tests/versioning.test.ts +++ b/tsagentspec/tests/versioning.test.ts @@ -17,12 +17,15 @@ describe("AgentSpecVersion", () => { expect(AgentSpecVersion.V25_4_1).toBe("25.4.1"); expect(AgentSpecVersion.V25_4_2).toBe("25.4.2"); expect(AgentSpecVersion.V26_1_0).toBe("26.1.0"); + expect(AgentSpecVersion.V26_1_2).toBe("26.1.2"); expect(AgentSpecVersion.V26_2_0).toBe("26.2.0"); + expect(AgentSpecVersion.V26_3_0).toBe("26.3.0"); + expect(AgentSpecVersion.V26_4_0).toBe("26.4.0"); }); it("should set CURRENT_VERSION to the latest version", () => { - expect(CURRENT_VERSION).toBe("26.2.0"); - expect(CURRENT_VERSION).toBe(AgentSpecVersion.V26_2_0); + expect(CURRENT_VERSION).toBe("26.4.0"); + expect(CURRENT_VERSION).toBe(AgentSpecVersion.V26_4_0); }); it("should define the version field name", () => {