This is an automated email from the ASF dual-hosted git repository.
wenjin272 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/flink-agents.git
The following commit(s) were added to refs/heads/main by this push:
new c6c43f7b [Tech Debt] Avoid using reflection to load implementation
classes of resources during the compilation phase (#879)
c6c43f7b is described below
commit c6c43f7b03f5bd4a743466db469c90f958bf388b
Author: daken <[email protected]>
AuthorDate: Mon Jul 13 11:52:56 2026 +0800
[Tech Debt] Avoid using reflection to load implementation classes of
resources during the compilation phase (#879)
Co-authored-by: daken <[email protected]>
---
.../agents/api/resource/ResourceDescriptor.java | 9 ++-
.../org/apache/flink/agents/plan/AgentPlan.java | 50 ++++++++---------
.../apache/flink/agents/plan/AgentPlanTest.java | 64 ++++++++++++++++++++++
3 files changed, 95 insertions(+), 28 deletions(-)
diff --git
a/api/src/main/java/org/apache/flink/agents/api/resource/ResourceDescriptor.java
b/api/src/main/java/org/apache/flink/agents/api/resource/ResourceDescriptor.java
index 140fee4a..6e9365f3 100644
---
a/api/src/main/java/org/apache/flink/agents/api/resource/ResourceDescriptor.java
+++
b/api/src/main/java/org/apache/flink/agents/api/resource/ResourceDescriptor.java
@@ -51,15 +51,18 @@ public class ResourceDescriptor {
* <ul>
* <li><b>For Java resources:</b> The fully qualified Java class
name (e.g.,
* "com.example.YourJavaClass"). The {@code module} parameter
should be empty or null.
- * <li><b>For Python resources (when declaring from Java):</b> The
Python class name
- * (simple name, not module path, e.g., "YourPythonClass"). The
Python module path
- * must be specified in the {@code module} parameter (e.g.,
"your_module.submodule").
* </ul>
*
* @param module The Python module path for cross-platform compatibility.
Defaults to empty
* string for Java resources. Example: "your_module.submodule"
* @param initialArguments Additional arguments for resource
initialization. Can be null or
* empty map if no initial arguments are needed.
+ * <ul>
+ * <li><b>For Python resources (when declaring from Java): put the
fully-qualified Python
+ * class under the pythonClazz key in initialArguments (e.g.
("pythonClazz",
+ * "your_module.submodule.YourPythonClass")). The clazz argument
is unused in this
+ * case.
+ * </ul>
*/
@JsonCreator
public ResourceDescriptor(
diff --git a/plan/src/main/java/org/apache/flink/agents/plan/AgentPlan.java
b/plan/src/main/java/org/apache/flink/agents/plan/AgentPlan.java
index 714ac4a3..3b08bf2c 100644
--- a/plan/src/main/java/org/apache/flink/agents/plan/AgentPlan.java
+++ b/plan/src/main/java/org/apache/flink/agents/plan/AgentPlan.java
@@ -35,7 +35,6 @@ import org.apache.flink.agents.api.resource.Resource;
import org.apache.flink.agents.api.resource.ResourceDescriptor;
import org.apache.flink.agents.api.resource.ResourceType;
import org.apache.flink.agents.api.resource.SerializableResource;
-import org.apache.flink.agents.api.resource.python.PythonResourceWrapper;
import org.apache.flink.agents.api.skills.SkillSourceSpec;
import org.apache.flink.agents.api.skills.Skills;
import org.apache.flink.agents.api.tools.ToolMetadata;
@@ -316,31 +315,40 @@ public class AgentPlan implements Serializable {
}
private void extractResource(ResourceType type, Method method) throws
Exception {
- extractResource(type, method, null);
+ extractResource(type, method, null, false);
}
private void extractResource(
ResourceType type,
Method method,
- Function<ResourceDescriptor, ResourceDescriptor>
descriptorDecorator)
+ Function<ResourceDescriptor, ResourceDescriptor>
descriptorDecorator,
+ boolean isPython)
throws Exception {
String name = method.getName();
- ResourceProvider provider;
ResourceDescriptor descriptor = (ResourceDescriptor)
method.invoke(null);
descriptor =
descriptorDecorator != null ?
descriptorDecorator.apply(descriptor) : descriptor;
- if (PythonResourceWrapper.class.isAssignableFrom(
- Class.forName(
- descriptor.getClazz(),
- true,
- Thread.currentThread().getContextClassLoader()))) {
- provider = new PythonResourceProvider(name, type, descriptor);
- } else {
- provider = new JavaResourceProvider(name, type, descriptor);
+ addResourceProvider(createDescriptorResourceProvider(name, type,
descriptor, isPython));
+ }
+
+ private ResourceProvider createDescriptorResourceProvider(
+ String name, ResourceType type, ResourceDescriptor descriptor) {
+ return createDescriptorResourceProvider(name, type, descriptor, false);
+ }
+
+ private ResourceProvider createDescriptorResourceProvider(
+ String name, ResourceType type, ResourceDescriptor descriptor,
boolean isPython) {
+ if (isPython || isPythonResource(descriptor)) {
+ return new PythonResourceProvider(name, type, descriptor);
}
- addResourceProvider(provider);
+ return new JavaResourceProvider(name, type, descriptor);
+ }
+
+ private boolean isPythonResource(ResourceDescriptor descriptor) {
+ String pythonClazz = descriptor.getArgument("pythonClazz");
+ return pythonClazz != null && !pythonClazz.isEmpty();
}
private void extractTool(Method method) throws Exception {
@@ -525,7 +533,8 @@ public class AgentPlan implements Serializable {
new ResourceDescriptor(
desc.getModule(),
PythonMCPServer.class.getName(),
- new
HashMap<>(desc.getInitialArguments())));
+ new
HashMap<>(desc.getInitialArguments())),
+ true);
}
}
}
@@ -576,17 +585,8 @@ public class AgentPlan implements Serializable {
for (Map.Entry<String, Object> kv :
entry.getValue().entrySet()) {
ResourceDescriptor descriptor =
requireResourceDescriptor(kv.getKey(), type,
kv.getValue());
- ResourceProvider provider;
- if (PythonResourceWrapper.class.isAssignableFrom(
- Class.forName(
- descriptor.getClazz(),
- true,
-
Thread.currentThread().getContextClassLoader()))) {
- provider = new PythonResourceProvider(kv.getKey(),
type, descriptor);
- } else {
- provider = new JavaResourceProvider(kv.getKey(), type,
descriptor);
- }
- addResourceProvider(provider);
+ addResourceProvider(
+ createDescriptorResourceProvider(kv.getKey(),
type, descriptor));
}
}
}
diff --git a/plan/src/test/java/org/apache/flink/agents/plan/AgentPlanTest.java
b/plan/src/test/java/org/apache/flink/agents/plan/AgentPlanTest.java
index 230eb75f..9e355feb 100644
--- a/plan/src/test/java/org/apache/flink/agents/plan/AgentPlanTest.java
+++ b/plan/src/test/java/org/apache/flink/agents/plan/AgentPlanTest.java
@@ -24,11 +24,13 @@ import org.apache.flink.agents.api.InputEvent;
import org.apache.flink.agents.api.OutputEvent;
import org.apache.flink.agents.api.agents.Agent;
import org.apache.flink.agents.api.annotation.ChatModelSetup;
+import org.apache.flink.agents.api.annotation.MCPServer;
import org.apache.flink.agents.api.annotation.Tool;
import org.apache.flink.agents.api.context.RunnerContext;
import org.apache.flink.agents.api.resource.Resource;
import org.apache.flink.agents.api.resource.ResourceContext;
import org.apache.flink.agents.api.resource.ResourceDescriptor;
+import org.apache.flink.agents.api.resource.ResourceName;
import org.apache.flink.agents.api.resource.ResourceType;
import org.apache.flink.agents.api.resource.SerializableResource;
import org.apache.flink.agents.api.resource.python.PythonResourceAdapter;
@@ -505,6 +507,25 @@ public class AgentPlanTest {
assertThat(provider.getType()).isEqualTo(ResourceType.EMBEDDING_MODEL);
}
+ @Test
+ public void
testAddResourceDescriptorWithPythonClazzUsesPythonResourceProvider()
+ throws Exception {
+ Agent agent = new Agent();
+ ResourceDescriptor descriptor =
+ ResourceDescriptor.Builder.newBuilder(String.class.getName())
+ .addInitialArgument("pythonClazz",
"test.module.EmbeddingClazz")
+ .build();
+ agent.addResource("myEmbedding", ResourceType.EMBEDDING_MODEL,
descriptor);
+
+ AgentPlan plan = new AgentPlan(agent);
+
+ ResourceProvider provider =
+
plan.getResourceProviders().get(ResourceType.EMBEDDING_MODEL).get("myEmbedding");
+ assertThat(provider).isInstanceOf(PythonResourceProvider.class);
+ assertThat(((PythonResourceProvider)
provider).getDescriptor().getClazz())
+ .isEqualTo(String.class.getName());
+ }
+
@Test
public void testAddResourceRegistersVectorStoreJavaProvider() throws
Exception {
Agent agent = new Agent();
@@ -535,6 +556,49 @@ public class AgentPlanTest {
Assertions.assertThrows(IllegalStateException.class, () -> new
AgentPlan(agent));
}
+ /** Test agent with descriptor-based Python resource. */
+ public static class TestAgentWithDescriptorPythonResource extends Agent {
+ @ChatModelSetup
+ public static ResourceDescriptor pythonChatModelByDescriptor() {
+ return
ResourceDescriptor.Builder.newBuilder(String.class.getName())
+ .addInitialArgument("pythonClazz", "test.module.TestClazz")
+ .build();
+ }
+ }
+
+ @Test
+ public void testDescriptorWithPythonClazzUsesPythonResourceProvider()
throws Exception {
+ AgentPlan plan = new AgentPlan(new
TestAgentWithDescriptorPythonResource());
+
+ ResourceProvider provider =
+ plan.getResourceProviders()
+ .get(ResourceType.CHAT_MODEL)
+ .get("pythonChatModelByDescriptor");
+ assertThat(provider).isInstanceOf(PythonResourceProvider.class);
+ assertThat(((PythonResourceProvider)
provider).getDescriptor().getClazz())
+ .isEqualTo(String.class.getName());
+ }
+
+ /** Test agent with explicit Python MCP server declaration. */
+ public static class TestAgentWithPythonMCPServer extends Agent {
+ @MCPServer(lang = "python")
+ public static ResourceDescriptor testMcpServer() {
+ return
ResourceDescriptor.Builder.newBuilder(ResourceName.MCP_SERVER)
+ .addInitialArgument("endpoint",
"http://127.0.0.1:8000/mcp")
+ .addInitialArgument("timeout", 30)
+ .build();
+ }
+ }
+
+ @Test
+ public void testPythonMCPServerUsesPythonResourceProviderAtCompileTime()
throws Exception {
+ AgentPlan plan = new AgentPlan(new TestAgentWithPythonMCPServer());
+
+ ResourceProvider provider =
+
plan.getResourceProviders().get(ResourceType.MCP_SERVER).get("testMcpServer");
+ assertThat(provider).isInstanceOf(PythonResourceProvider.class);
+ }
+
@Test
public void testAddResourceMCPServerRejectedWithGuidance() {
Agent agent = new Agent();