Skip to content

Commit e4886af

Browse files
[hotfix] Register all resource types added via Agent.addResource (#671)
1 parent e0a6d51 commit e4886af

2 files changed

Lines changed: 106 additions & 19 deletions

File tree

plan/src/main/java/org/apache/flink/agents/plan/AgentPlan.java

Lines changed: 37 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -359,6 +359,18 @@ private void extractActionsFromAgent(Agent agent) throws Exception {
359359
}
360360
}
361361

362+
private static ResourceDescriptor requireResourceDescriptor(
363+
String name, ResourceType type, Object value) {
364+
if (!(value instanceof ResourceDescriptor)) {
365+
throw new IllegalStateException(
366+
String.format(
367+
"Resource '%s' of type %s must be a ResourceDescriptor when added via"
368+
+ " Agent.addResource, but got %s",
369+
name, type, value == null ? "null" : value.getClass().getName()));
370+
}
371+
return (ResourceDescriptor) value;
372+
}
373+
362374
private void extractResource(ResourceType type, Method method) throws Exception {
363375
extractResource(type, method, null);
364376
}
@@ -561,25 +573,7 @@ private void extractResourceProvidersFromAgent(Agent agent) throws Exception {
561573

562574
for (Map.Entry<ResourceType, Map<String, Object>> entry : agent.getResources().entrySet()) {
563575
ResourceType type = entry.getKey();
564-
if (type == ResourceType.CHAT_MODEL || type == ResourceType.CHAT_MODEL_CONNECTION) {
565-
for (Map.Entry<String, Object> kv : entry.getValue().entrySet()) {
566-
ResourceProvider provider;
567-
if (PythonResourceWrapper.class.isAssignableFrom(
568-
Class.forName(
569-
((ResourceDescriptor) kv.getValue()).getClazz(),
570-
true,
571-
Thread.currentThread().getContextClassLoader()))) {
572-
provider =
573-
new PythonResourceProvider(
574-
kv.getKey(), type, (ResourceDescriptor) kv.getValue());
575-
} else {
576-
provider =
577-
new JavaResourceProvider(
578-
kv.getKey(), type, (ResourceDescriptor) kv.getValue());
579-
}
580-
addResourceProvider(provider);
581-
}
582-
} else if (type == PROMPT) {
576+
if (type == PROMPT) {
583577
for (Map.Entry<String, Object> kv : entry.getValue().entrySet()) {
584578
JavaSerializableResourceProvider provider =
585579
JavaSerializableResourceProvider.createResourceProvider(
@@ -593,6 +587,30 @@ private void extractResourceProvidersFromAgent(Agent agent) throws Exception {
593587
((org.apache.flink.agents.api.tools.FunctionTool) kv.getValue())
594588
.getMethod());
595589
}
590+
} else if (type == MCP_SERVER) {
591+
if (!entry.getValue().isEmpty()) {
592+
throw new UnsupportedOperationException(
593+
"Adding an MCP server via Agent.addResource is not supported."
594+
+ " Declare the MCP server with a @MCPServer-annotated static"
595+
+ " method on your Agent class so its tools and prompts can be"
596+
+ " discovered.");
597+
}
598+
} else {
599+
for (Map.Entry<String, Object> kv : entry.getValue().entrySet()) {
600+
ResourceDescriptor descriptor =
601+
requireResourceDescriptor(kv.getKey(), type, kv.getValue());
602+
ResourceProvider provider;
603+
if (PythonResourceWrapper.class.isAssignableFrom(
604+
Class.forName(
605+
descriptor.getClazz(),
606+
true,
607+
Thread.currentThread().getContextClassLoader()))) {
608+
provider = new PythonResourceProvider(kv.getKey(), type, descriptor);
609+
} else {
610+
provider = new JavaResourceProvider(kv.getKey(), type, descriptor);
611+
}
612+
addResourceProvider(provider);
613+
}
596614
}
597615
}
598616
}

plan/src/test/java/org/apache/flink/agents/plan/AgentPlanTest.java

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import org.apache.flink.agents.api.vectorstores.VectorStoreQuery;
3939
import org.apache.flink.agents.api.vectorstores.VectorStoreQueryResult;
4040
import org.apache.flink.agents.plan.actions.Action;
41+
import org.apache.flink.agents.plan.resourceprovider.JavaResourceProvider;
4142
import org.apache.flink.agents.plan.resourceprovider.JavaSerializableResourceProvider;
4243
import org.apache.flink.agents.plan.resourceprovider.PythonResourceProvider;
4344
import org.apache.flink.agents.plan.resourceprovider.ResourceProvider;
@@ -478,4 +479,72 @@ public void testGetResourceFromResourceProvider() throws Exception {
478479
Resource myToolAgain = agentPlan.getResource("myTool", ResourceType.TOOL);
479480
assertThat(myTool).isSameAs(myToolAgain);
480481
}
482+
483+
@Test
484+
public void testAddResourceRegistersEmbeddingModelProvider() throws Exception {
485+
Agent agent = new Agent();
486+
ResourceDescriptor descriptor =
487+
ResourceDescriptor.Builder.newBuilder(TestPythonResource.class.getName())
488+
.addInitialArgument("pythonClazz", "test.module.EmbeddingClazz")
489+
.build();
490+
agent.addResource("myEmbedding", ResourceType.EMBEDDING_MODEL, descriptor);
491+
492+
AgentPlan plan = new AgentPlan(agent);
493+
494+
Map<String, ResourceProvider> providers =
495+
plan.getResourceProviders().get(ResourceType.EMBEDDING_MODEL);
496+
assertThat(providers).isNotNull();
497+
assertThat(providers).containsKey("myEmbedding");
498+
499+
ResourceProvider provider = providers.get("myEmbedding");
500+
// TestPythonResource implements PythonResourceWrapper, so a Python provider is expected.
501+
assertThat(provider).isInstanceOf(PythonResourceProvider.class);
502+
assertThat(provider.getName()).isEqualTo("myEmbedding");
503+
assertThat(provider.getType()).isEqualTo(ResourceType.EMBEDDING_MODEL);
504+
}
505+
506+
@Test
507+
public void testAddResourceRegistersVectorStoreJavaProvider() throws Exception {
508+
Agent agent = new Agent();
509+
// A non-Python-wrapper class triggers JavaResourceProvider — String is fine for this
510+
// structural check; we never call provide() here.
511+
ResourceDescriptor descriptor =
512+
ResourceDescriptor.Builder.newBuilder(String.class.getName()).build();
513+
agent.addResource("myVectorStore", ResourceType.VECTOR_STORE, descriptor);
514+
515+
AgentPlan plan = new AgentPlan(agent);
516+
517+
ResourceProvider provider =
518+
plan.getResourceProviders().get(ResourceType.VECTOR_STORE).get("myVectorStore");
519+
assertThat(provider).isInstanceOf(JavaResourceProvider.class);
520+
assertThat(provider.getType()).isEqualTo(ResourceType.VECTOR_STORE);
521+
}
522+
523+
@Test
524+
public void testAddResourceRejectsNonDescriptorForUnsupportedType() {
525+
Agent agent = new Agent();
526+
// SerializableResource for EMBEDDING_MODEL is illegal — only PROMPT / TOOL accept
527+
// non-descriptors. The new code path must reject it with a clear message instead of CCE.
528+
agent.addResource(
529+
"badEmbedding",
530+
ResourceType.EMBEDDING_MODEL,
531+
new TestSerializableChatModel("badEmbedding"));
532+
533+
Assertions.assertThrows(IllegalStateException.class, () -> new AgentPlan(agent));
534+
}
535+
536+
@Test
537+
public void testAddResourceMCPServerRejectedWithGuidance() {
538+
Agent agent = new Agent();
539+
ResourceDescriptor descriptor =
540+
ResourceDescriptor.Builder.newBuilder("dummy.MCPServer")
541+
.addInitialArgument("endpoint", "http://127.0.0.1:0/mcp")
542+
.build();
543+
agent.addResource("addedMcpServer", ResourceType.MCP_SERVER, descriptor);
544+
545+
UnsupportedOperationException ex =
546+
Assertions.assertThrows(
547+
UnsupportedOperationException.class, () -> new AgentPlan(agent));
548+
assertThat(ex.getMessage()).contains("@MCPServer");
549+
}
481550
}

0 commit comments

Comments
 (0)