From 23f1aa00c1a2b6966ffaa322ab64acef092ed05e Mon Sep 17 00:00:00 2001 From: WenjinXie Date: Mon, 8 Jun 2026 16:02:53 +0800 Subject: [PATCH] [fix] Make agents block optional so infra-only YAML files load (#775) The "split topology from infra" pattern documented in yaml.md fails because YamlAgentsDocument declares agents as required, so an infrastructure-only file (shared chat-model connections, vector stores, ...) is rejected. Make agents default to empty on both Python and Java sides, re-export yaml-schema.json, add infra-only regression tests, and note the optional block in the docs. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../flink/agents/api/yaml/spec/YamlAgentsDocument.java | 4 ++-- .../agents/api/yaml/spec/YamlAgentsDocumentTest.java | 10 ++++++++++ docs/content/docs/development/yaml.md | 2 +- docs/yaml-schema.json | 5 +---- python/flink_agents/api/yaml/specs.py | 9 ++++++--- python/flink_agents/api/yaml/tests/test_specs.py | 9 ++++++--- 6 files changed, 26 insertions(+), 13 deletions(-) diff --git a/api/src/main/java/org/apache/flink/agents/api/yaml/spec/YamlAgentsDocument.java b/api/src/main/java/org/apache/flink/agents/api/yaml/spec/YamlAgentsDocument.java index cc0874d05..1b0fbfa99 100644 --- a/api/src/main/java/org/apache/flink/agents/api/yaml/spec/YamlAgentsDocument.java +++ b/api/src/main/java/org/apache/flink/agents/api/yaml/spec/YamlAgentsDocument.java @@ -42,7 +42,7 @@ public final class YamlAgentsDocument { @JsonCreator public YamlAgentsDocument( - @JsonProperty(value = "agents", required = true) List agents, + @JsonProperty("agents") List agents, @JsonProperty("prompts") List prompts, @JsonProperty("tools") List tools, @JsonProperty("skills") List skills, @@ -54,7 +54,7 @@ public YamlAgentsDocument( @JsonProperty("embedding_model_setups") List embeddingModelSetups, @JsonProperty("vector_stores") List vectorStores, @JsonProperty("mcp_servers") List mcpServers) { - this.agents = agents; + this.agents = orEmpty(agents); this.prompts = orEmpty(prompts); this.tools = orEmpty(tools); this.skills = orEmpty(skills); diff --git a/api/src/test/java/org/apache/flink/agents/api/yaml/spec/YamlAgentsDocumentTest.java b/api/src/test/java/org/apache/flink/agents/api/yaml/spec/YamlAgentsDocumentTest.java index e5326826b..d7e7604e5 100644 --- a/api/src/test/java/org/apache/flink/agents/api/yaml/spec/YamlAgentsDocumentTest.java +++ b/api/src/test/java/org/apache/flink/agents/api/yaml/spec/YamlAgentsDocumentTest.java @@ -46,4 +46,14 @@ void sharedSectionsAtFileLevel() throws Exception { assertThat(doc.getActions()).hasSize(1); assertThat(doc.getActions().get(0).getName()).isEqualTo("shared_a"); } + + @Test + void infraOnlyFile() throws Exception { + YamlAgentsDocument doc = + M.readValue( + "chat_model_connections:\n - name: x\n clazz: ollama\n", + YamlAgentsDocument.class); + assertThat(doc.getAgents()).isEmpty(); + assertThat(doc.getChatModelConnections()).hasSize(1); + } } diff --git a/docs/content/docs/development/yaml.md b/docs/content/docs/development/yaml.md index 30760d803..a6054fa32 100644 --- a/docs/content/docs/development/yaml.md +++ b/docs/content/docs/development/yaml.md @@ -382,7 +382,7 @@ agentsEnv.loadYaml(Paths.get("./shared.yaml")); {{< /tabs >}} -A common pattern is to split a topology file (the agents themselves) from an infrastructure file (chat-model connections, vector stores, ...). The infrastructure file can be swapped per environment (dev / staging / prod) without touching the agent definitions. +A common pattern is to split a topology file (the agents themselves) from an infrastructure file (chat-model connections, vector stores, ...). The infrastructure file can be swapped per environment (dev / staging / prod) without touching the agent definitions. The `agents:` block is optional, so an infrastructure-only file (no `agents:` block) is loaded as shared resources. For an end-to-end runnable walkthrough that loads a YAML-declared agent and runs it on Flink, see [YAML Agent Quickstart]({{< ref "docs/get-started/quickstart/yaml_agent" >}}). diff --git a/docs/yaml-schema.json b/docs/yaml-schema.json index ac62bb17e..7c4b35fa7 100644 --- a/docs/yaml-schema.json +++ b/docs/yaml-schema.json @@ -400,7 +400,7 @@ } }, "additionalProperties": false, - "description": "Top-level YAML document.\n\nAlways wraps one or more agents under ``agents:``. Resources and\nactions declared at the same level as ``agents:`` are shared:\nresources are registered on the environment; actions can be\nreferenced from any agent by name string.", + "description": "Top-level YAML document.\n\nAgents are declared under ``agents:``. The block is optional, so a\nfile may carry only shared infrastructure (chat-model connections,\nvector stores, ...) \u2014 useful for splitting a topology file from an\ninfrastructure file that can be swapped per environment. Resources\nand actions declared at the same level as ``agents:`` are shared:\nresources are registered on the environment; actions can be\nreferenced from any agent by name string.", "properties": { "actions": { "items": { @@ -480,9 +480,6 @@ "type": "array" } }, - "required": [ - "agents" - ], "title": "YamlAgentsDocument", "type": "object" } diff --git a/python/flink_agents/api/yaml/specs.py b/python/flink_agents/api/yaml/specs.py index 220eccf35..0c68772af 100644 --- a/python/flink_agents/api/yaml/specs.py +++ b/python/flink_agents/api/yaml/specs.py @@ -220,15 +220,18 @@ class AgentSpec(BaseModel): class YamlAgentsDocument(BaseModel): """Top-level YAML document. - Always wraps one or more agents under ``agents:``. Resources and - actions declared at the same level as ``agents:`` are shared: + Agents are declared under ``agents:``. The block is optional, so a + file may carry only shared infrastructure (chat-model connections, + vector stores, ...) — useful for splitting a topology file from an + infrastructure file that can be swapped per environment. Resources + and actions declared at the same level as ``agents:`` are shared: resources are registered on the environment; actions can be referenced from any agent by name string. """ model_config = ConfigDict(extra="forbid") - agents: List[AgentSpec] + agents: List[AgentSpec] = Field(default_factory=list) prompts: List[PromptSpec] = Field(default_factory=list) tools: List[ToolSpec] = Field(default_factory=list) diff --git a/python/flink_agents/api/yaml/tests/test_specs.py b/python/flink_agents/api/yaml/tests/test_specs.py index e70285881..1c849f0fc 100644 --- a/python/flink_agents/api/yaml/tests/test_specs.py +++ b/python/flink_agents/api/yaml/tests/test_specs.py @@ -224,9 +224,12 @@ def test_agent_spec_action_can_be_string_reference() -> None: assert isinstance(spec.actions[1], ActionSpec) -def test_yaml_document_requires_agents() -> None: - with pytest.raises(ValidationError): - YamlAgentsDocument.model_validate({}) +def test_yaml_document_allows_infra_only_file() -> None: + doc = YamlAgentsDocument.model_validate( + {"chat_model_connections": [{"name": "x", "clazz": "ollama"}]} + ) + assert doc.agents == [] + assert doc.chat_model_connections[0].name == "x" def test_yaml_document_minimal() -> None: