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();

Reply via email to