Skip to content

Commit cc4362d

Browse files
wenjin272claude
andcommitted
[api][java] Introduce YAML API for declaring agents
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 4e4689f commit cc4362d

51 files changed

Lines changed: 3818 additions & 4 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

api/pom.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@ under the License.
3838
<groupId>com.fasterxml.jackson.core</groupId>
3939
<artifactId>jackson-databind</artifactId>
4040
</dependency>
41+
<dependency>
42+
<groupId>com.fasterxml.jackson.dataformat</groupId>
43+
<artifactId>jackson-dataformat-yaml</artifactId>
44+
</dependency>
4145
<dependency>
4246
<groupId>org.apache.flink</groupId>
4347
<artifactId>flink-streaming-java</artifactId>

api/src/main/java/org/apache/flink/agents/api/AgentBuilder.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,21 @@ public interface AgentBuilder {
4242
*/
4343
AgentBuilder apply(Agent agent);
4444

45+
/**
46+
* Apply an agent previously registered on the environment (typically via {@code
47+
* env.loadYaml(...)}) by name.
48+
*
49+
* <p>Default implementation throws — concrete builders that have access to the environment
50+
* override this to look up the named agent and delegate to {@link #apply(Agent)}.
51+
*
52+
* @param agentName the name under which the agent was registered on the environment.
53+
* @return a configured AgentBuilder for method chaining.
54+
*/
55+
default AgentBuilder apply(String agentName) {
56+
throw new UnsupportedOperationException(
57+
"apply(String) is not supported by this AgentBuilder; only Agent instances accepted.");
58+
}
59+
4560
/**
4661
* Get output list of agent execution.
4762
*

api/src/main/java/org/apache/flink/agents/api/AgentsExecutionEnvironment.java

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
package org.apache.flink.agents.api;
2020

21+
import org.apache.flink.agents.api.agents.Agent;
2122
import org.apache.flink.agents.api.configuration.Configuration;
2223
import org.apache.flink.agents.api.resource.ResourceDescriptor;
2324
import org.apache.flink.agents.api.resource.ResourceType;
@@ -42,6 +43,7 @@
4243
*/
4344
public abstract class AgentsExecutionEnvironment {
4445
protected final Map<ResourceType, Map<String, Object>> resources;
46+
protected final Map<String, Agent> agents = new HashMap<>();
4547

4648
protected AgentsExecutionEnvironment() {
4749
this.resources = new HashMap<>();
@@ -50,6 +52,25 @@ protected AgentsExecutionEnvironment() {
5052
}
5153
}
5254

55+
/**
56+
* Returns the agents registered on this environment, keyed by name.
57+
*
58+
* <p>Populated by {@link #loadYaml(java.nio.file.Path...)} and friends.
59+
*/
60+
public Map<String, Agent> getAgents() {
61+
return agents;
62+
}
63+
64+
/**
65+
* Returns the resources registered on this environment, grouped by {@link ResourceType}.
66+
*
67+
* <p>Exposed primarily so YAML loading code (in a sibling package) and tests can inspect
68+
* registered shared resources without subclassing.
69+
*/
70+
public Map<ResourceType, Map<String, Object>> getResources() {
71+
return resources;
72+
}
73+
5374
/**
5475
* Get agents execution environment.
5576
*
@@ -229,4 +250,20 @@ public AgentsExecutionEnvironment addResource(String name, ResourceType type, Ob
229250
}
230251
return this;
231252
}
253+
254+
/**
255+
* Load one or more YAML files and register their agents and shared resources on this
256+
* environment. Duplicate names — both within a single file and across the current environment —
257+
* raise {@link IllegalArgumentException}.
258+
*/
259+
public void loadYaml(java.nio.file.Path... paths) {
260+
org.apache.flink.agents.api.yaml.YamlLoader.loadYaml(this, java.util.Arrays.asList(paths));
261+
}
262+
263+
/**
264+
* Load multiple YAML files and register their agents and shared resources on this environment.
265+
*/
266+
public void loadYaml(java.util.List<java.nio.file.Path> paths) {
267+
org.apache.flink.agents.api.yaml.YamlLoader.loadYaml(this, paths);
268+
}
232269
}
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
19+
package org.apache.flink.agents.api.yaml;
20+
21+
import org.apache.flink.agents.api.InputEvent;
22+
import org.apache.flink.agents.api.OutputEvent;
23+
import org.apache.flink.agents.api.event.ChatRequestEvent;
24+
import org.apache.flink.agents.api.event.ChatResponseEvent;
25+
import org.apache.flink.agents.api.event.ContextRetrievalRequestEvent;
26+
import org.apache.flink.agents.api.event.ContextRetrievalResponseEvent;
27+
import org.apache.flink.agents.api.event.ToolRequestEvent;
28+
import org.apache.flink.agents.api.event.ToolResponseEvent;
29+
import org.apache.flink.agents.api.resource.ResourceName;
30+
import org.apache.flink.agents.api.resource.ResourceType;
31+
32+
import java.util.Collections;
33+
import java.util.EnumMap;
34+
import java.util.HashMap;
35+
import java.util.Map;
36+
37+
/**
38+
* Static alias tables for the YAML loader.
39+
*
40+
* <p>Two tables:
41+
*
42+
* <ul>
43+
* <li>{@link #EVENT_ALIASES} maps short event names to {@code EVENT_TYPE} constants.
44+
* <li>{@link #CLAZZ_ALIASES} maps short provider names to fully-qualified class paths, keyed on
45+
* resource type <em>and</em> implementation language so the same alias (e.g. {@code ollama})
46+
* can refer to different classes across sections and languages.
47+
* </ul>
48+
*
49+
* <p>For Python resources, the loader resolves the alias to the Python FQN and wraps it in a
50+
* Java-side wrapper class (see {@link #PYTHON_WRAPPER_CLAZZ}).
51+
*/
52+
public final class Aliases {
53+
54+
/** Short event alias to fully-qualified {@code EVENT_TYPE} string. */
55+
public static final Map<String, String> EVENT_ALIASES;
56+
57+
/** ResourceType to Language to alias to fully-qualified class path. */
58+
public static final Map<ResourceType, Map<Language, Map<String, String>>> CLAZZ_ALIASES;
59+
60+
/**
61+
* ResourceType to Java-side wrapper FQN that embeds a Python implementation. Used when a YAML
62+
* resource declares {@code type: python} so the Java host wraps the Python class through an
63+
* existing PythonResourceWrapper implementation.
64+
*/
65+
public static final Map<ResourceType, String> PYTHON_WRAPPER_CLAZZ;
66+
67+
static {
68+
Map<String, String> ev = new HashMap<>();
69+
ev.put("input", InputEvent.EVENT_TYPE);
70+
ev.put("output", OutputEvent.EVENT_TYPE);
71+
ev.put("chat_request", ChatRequestEvent.EVENT_TYPE);
72+
ev.put("chat_response", ChatResponseEvent.EVENT_TYPE);
73+
ev.put("tool_request", ToolRequestEvent.EVENT_TYPE);
74+
ev.put("tool_response", ToolResponseEvent.EVENT_TYPE);
75+
ev.put("context_retrieval_request", ContextRetrievalRequestEvent.EVENT_TYPE);
76+
ev.put("context_retrieval_response", ContextRetrievalResponseEvent.EVENT_TYPE);
77+
EVENT_ALIASES = Collections.unmodifiableMap(ev);
78+
79+
Map<ResourceType, Map<Language, Map<String, String>>> ca =
80+
new EnumMap<>(ResourceType.class);
81+
82+
// CHAT_MODEL_CONNECTION
83+
Map<String, String> chatConnJava = new HashMap<>();
84+
chatConnJava.put("ollama", ResourceName.ChatModel.OLLAMA_CONNECTION);
85+
chatConnJava.put(
86+
"openai_completions", ResourceName.ChatModel.OPENAI_COMPLETIONS_CONNECTION);
87+
chatConnJava.put("openai_responses", ResourceName.ChatModel.OPENAI_RESPONSES_CONNECTION);
88+
chatConnJava.put("anthropic", ResourceName.ChatModel.ANTHROPIC_CONNECTION);
89+
chatConnJava.put("azure", ResourceName.ChatModel.AZURE_CONNECTION);
90+
Map<String, String> chatConnPython = new HashMap<>();
91+
chatConnPython.put("ollama", ResourceName.ChatModel.Python.OLLAMA_CONNECTION);
92+
chatConnPython.put("openai", ResourceName.ChatModel.Python.OPENAI_COMPLETIONS_CONNECTION);
93+
chatConnPython.put("anthropic", ResourceName.ChatModel.Python.ANTHROPIC_CONNECTION);
94+
chatConnPython.put("tongyi", ResourceName.ChatModel.Python.TONGYI_CONNECTION);
95+
chatConnPython.put("azure_openai", ResourceName.ChatModel.Python.AZURE_OPENAI_CONNECTION);
96+
ca.put(ResourceType.CHAT_MODEL_CONNECTION, buildLangBuckets(chatConnJava, chatConnPython));
97+
98+
// CHAT_MODEL
99+
Map<String, String> chatJava = new HashMap<>();
100+
chatJava.put("ollama", ResourceName.ChatModel.OLLAMA_SETUP);
101+
chatJava.put("openai_completions", ResourceName.ChatModel.OPENAI_COMPLETIONS_SETUP);
102+
chatJava.put("openai_responses", ResourceName.ChatModel.OPENAI_RESPONSES_SETUP);
103+
chatJava.put("anthropic", ResourceName.ChatModel.ANTHROPIC_SETUP);
104+
chatJava.put("azure", ResourceName.ChatModel.AZURE_SETUP);
105+
Map<String, String> chatPython = new HashMap<>();
106+
chatPython.put("ollama", ResourceName.ChatModel.Python.OLLAMA_SETUP);
107+
chatPython.put("openai", ResourceName.ChatModel.Python.OPENAI_COMPLETIONS_SETUP);
108+
chatPython.put("anthropic", ResourceName.ChatModel.Python.ANTHROPIC_SETUP);
109+
chatPython.put("tongyi", ResourceName.ChatModel.Python.TONGYI_SETUP);
110+
chatPython.put("azure_openai", ResourceName.ChatModel.Python.AZURE_OPENAI_SETUP);
111+
ca.put(ResourceType.CHAT_MODEL, buildLangBuckets(chatJava, chatPython));
112+
113+
// EMBEDDING_MODEL_CONNECTION
114+
Map<String, String> embConnJava = new HashMap<>();
115+
embConnJava.put("ollama", ResourceName.EmbeddingModel.OLLAMA_CONNECTION);
116+
Map<String, String> embConnPython = new HashMap<>();
117+
embConnPython.put("ollama", ResourceName.EmbeddingModel.Python.OLLAMA_CONNECTION);
118+
embConnPython.put("openai", ResourceName.EmbeddingModel.Python.OPENAI_CONNECTION);
119+
embConnPython.put("tongyi", ResourceName.EmbeddingModel.Python.TONGYI_CONNECTION);
120+
ca.put(
121+
ResourceType.EMBEDDING_MODEL_CONNECTION,
122+
buildLangBuckets(embConnJava, embConnPython));
123+
124+
// EMBEDDING_MODEL
125+
Map<String, String> embJava = new HashMap<>();
126+
embJava.put("ollama", ResourceName.EmbeddingModel.OLLAMA_SETUP);
127+
Map<String, String> embPython = new HashMap<>();
128+
embPython.put("ollama", ResourceName.EmbeddingModel.Python.OLLAMA_SETUP);
129+
embPython.put("openai", ResourceName.EmbeddingModel.Python.OPENAI_SETUP);
130+
embPython.put("tongyi", ResourceName.EmbeddingModel.Python.TONGYI_SETUP);
131+
ca.put(ResourceType.EMBEDDING_MODEL, buildLangBuckets(embJava, embPython));
132+
133+
// VECTOR_STORE
134+
Map<String, String> vsJava = new HashMap<>();
135+
vsJava.put("elasticsearch", ResourceName.VectorStore.ELASTICSEARCH_VECTOR_STORE);
136+
Map<String, String> vsPython = new HashMap<>();
137+
vsPython.put("chroma", ResourceName.VectorStore.Python.CHROMA_VECTOR_STORE);
138+
ca.put(ResourceType.VECTOR_STORE, buildLangBuckets(vsJava, vsPython));
139+
140+
CLAZZ_ALIASES = Collections.unmodifiableMap(ca);
141+
142+
Map<ResourceType, String> wrap = new EnumMap<>(ResourceType.class);
143+
wrap.put(
144+
ResourceType.CHAT_MODEL_CONNECTION,
145+
ResourceName.ChatModel.PYTHON_WRAPPER_CONNECTION);
146+
wrap.put(ResourceType.CHAT_MODEL, ResourceName.ChatModel.PYTHON_WRAPPER_SETUP);
147+
wrap.put(
148+
ResourceType.EMBEDDING_MODEL_CONNECTION,
149+
ResourceName.EmbeddingModel.PYTHON_WRAPPER_CONNECTION);
150+
wrap.put(ResourceType.EMBEDDING_MODEL, ResourceName.EmbeddingModel.PYTHON_WRAPPER_SETUP);
151+
wrap.put(ResourceType.VECTOR_STORE, ResourceName.VectorStore.PYTHON_WRAPPER_VECTOR_STORE);
152+
PYTHON_WRAPPER_CLAZZ = Collections.unmodifiableMap(wrap);
153+
}
154+
155+
private Aliases() {}
156+
157+
private static Map<Language, Map<String, String>> buildLangBuckets(
158+
Map<String, String> javaBucket, Map<String, String> pythonBucket) {
159+
Map<Language, Map<String, String>> out = new EnumMap<>(Language.class);
160+
out.put(Language.JAVA, Collections.unmodifiableMap(new HashMap<>(javaBucket)));
161+
out.put(Language.PYTHON, Collections.unmodifiableMap(new HashMap<>(pythonBucket)));
162+
return Collections.unmodifiableMap(out);
163+
}
164+
165+
/** Look up an event alias; return {@code name} unchanged on miss. */
166+
public static String resolveEventType(String name) {
167+
return EVENT_ALIASES.getOrDefault(name, name);
168+
}
169+
170+
/**
171+
* Look up a class alias for {@code (resourceType, language)}; return {@code name} unchanged on
172+
* miss.
173+
*/
174+
public static String resolveClazz(String name, ResourceType resourceType, Language language) {
175+
Map<Language, Map<String, String>> byLang = CLAZZ_ALIASES.get(resourceType);
176+
if (byLang == null) {
177+
return name;
178+
}
179+
Map<String, String> bucket = byLang.get(language);
180+
if (bucket == null) {
181+
return name;
182+
}
183+
return bucket.getOrDefault(name, name);
184+
}
185+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
19+
package org.apache.flink.agents.api.yaml;
20+
21+
import com.fasterxml.jackson.annotation.JsonCreator;
22+
import com.fasterxml.jackson.annotation.JsonValue;
23+
24+
/**
25+
* Implementation language of a YAML-declared resource, action, or tool.
26+
*
27+
* <p>The JSON/YAML wire form is the lowercase string ({@code "python"} or {@code "java"}); the
28+
* loader supplies the host-default when the field is omitted.
29+
*/
30+
public enum Language {
31+
PYTHON("python"),
32+
JAVA("java");
33+
34+
private final String value;
35+
36+
Language(String value) {
37+
this.value = value;
38+
}
39+
40+
@JsonValue
41+
public String getValue() {
42+
return value;
43+
}
44+
45+
@JsonCreator
46+
public static Language fromValue(String value) {
47+
for (Language l : values()) {
48+
if (l.value.equals(value)) return l;
49+
}
50+
throw new IllegalArgumentException("Unknown language: " + value);
51+
}
52+
}

0 commit comments

Comments
 (0)