Skip to content

Commit 127b53e

Browse files
KomachiSionco63oc
authored andcommitted
feat(a2a): support subscribe a2a agentCard from nacos. (alibaba#2446)
1 parent f10542f commit 127b53e

File tree

10 files changed

+240
-36
lines changed

10 files changed

+240
-36
lines changed

auto-configurations/spring-ai-alibaba-autoconfigure-a2a-client/src/main/java/com/alibaba/cloud/ai/autoconfigure/a2a/client/A2aClientAgentCardProviderAutoConfiguration.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import com.alibaba.cloud.ai.a2a.A2aClientAgentCardProperties;
2020
import com.alibaba.cloud.ai.autoconfigure.a2a.client.condition.A2aClientAgentCardWellKnownCondition;
2121
import com.alibaba.cloud.ai.graph.agent.a2a.AgentCardProvider;
22+
import com.alibaba.cloud.ai.graph.agent.a2a.AgentCardWrapper;
2223
import com.alibaba.cloud.ai.graph.agent.a2a.RemoteAgentCardProvider;
2324
import io.a2a.spec.AgentCard;
2425

@@ -67,7 +68,7 @@ public AgentCardProvider localAgentCardProvider(A2aClientAgentCardProperties a2a
6768
.protocolVersion(a2aClientAgentCardProperties.getProtocolVersion())
6869
.preferredTransport(a2aClientAgentCardProperties.getPreferredTransport())
6970
.build();
70-
return () -> agentCard;
71+
return () -> new AgentCardWrapper(agentCard);
7172
}
7273

7374
}

auto-configurations/spring-ai-alibaba-autoconfigure-a2a-server/src/main/java/com/alibaba/cloud/ai/autoconfigure/a2a/server/A2aServerAgentCardAutoConfiguration.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ private List<AgentInterface> getAdditionalInterfaces(A2aServerAgentCardPropertie
113113
if (null != a2AServerAgentCardProperties.getAdditionalInterfaces()) {
114114
return a2AServerAgentCardProperties.getAdditionalInterfaces();
115115
}
116-
return List.of(new AgentInterface(a2aServerProperties.getType(), buildUrl(a2aServerProperties)));
116+
return List.of(new AgentInterface(a2aServerProperties.getType(), getUrl(a2aServerProperties, a2AServerAgentCardProperties)));
117117
}
118118

119119
private String buildUrl(A2aServerProperties a2aServerProperties) {

spring-ai-alibaba-a2a/spring-ai-alibaba-a2a-registry/src/main/java/com/alibaba/cloud/ai/a2a/registry/nacos/discovery/NacosAgentCardProvider.java

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,16 @@
1818

1919
import com.alibaba.cloud.ai.a2a.registry.nacos.utils.AgentCardConverterUtil;
2020
import com.alibaba.cloud.ai.graph.agent.a2a.AgentCardProvider;
21+
import com.alibaba.cloud.ai.graph.agent.a2a.AgentCardWrapper;
2122
import com.alibaba.nacos.api.ai.A2aService;
23+
import com.alibaba.nacos.api.ai.listener.AbstractNacosAgentCardListener;
24+
import com.alibaba.nacos.api.ai.listener.NacosAgentCardEvent;
25+
import com.alibaba.nacos.api.ai.model.a2a.AgentCard;
2226
import com.alibaba.nacos.api.exception.NacosException;
2327
import com.alibaba.nacos.api.exception.runtime.NacosRuntimeException;
24-
import io.a2a.spec.AgentCard;
28+
import com.alibaba.nacos.common.utils.JacksonUtils;
29+
import org.slf4j.Logger;
30+
import org.slf4j.LoggerFactory;
2531

2632
/**
2733
* Implementation of {@link AgentCardProvider} for getting agent card from nacos a2a
@@ -31,27 +37,40 @@
3137
*/
3238
public class NacosAgentCardProvider implements AgentCardProvider {
3339

40+
private static final Logger LOGGER = LoggerFactory.getLogger(NacosAgentCardProvider.class);
41+
3442
private final A2aService a2aService;
3543

36-
private com.alibaba.nacos.api.ai.model.a2a.AgentCard nacosAgentCard;
44+
private AgentCardWrapper agentCard;
3745

3846
public NacosAgentCardProvider(A2aService a2aService) {
3947
this.a2aService = a2aService;
4048
}
4149

4250
@Override
43-
public AgentCard getAgentCard() {
44-
if (null == nacosAgentCard) {
45-
throw new IllegalStateException("Please use getAgentCard(agentName) first");
51+
public AgentCardWrapper getAgentCard() {
52+
if (null == agentCard) {
53+
throw new IllegalStateException("Please use getAgentCard(agentName) first.");
4654
}
47-
return AgentCardConverterUtil.convertToA2aAgentCard(nacosAgentCard);
55+
return agentCard;
4856
}
4957

5058
@Override
51-
public AgentCard getAgentCard(String agentName) {
59+
public AgentCardWrapper getAgentCard(String agentName) {
5260
try {
53-
nacosAgentCard = a2aService.getAgentCard(agentName);
54-
return AgentCardConverterUtil.convertToA2aAgentCard(nacosAgentCard);
61+
AgentCard nacosAgentCard = a2aService.getAgentCard(agentName);
62+
agentCard = new NacosAgentCardWrapper(AgentCardConverterUtil.convertToA2aAgentCard(nacosAgentCard));
63+
a2aService.subscribeAgentCard(agentName, new AbstractNacosAgentCardListener() {
64+
@Override
65+
public void onEvent(NacosAgentCardEvent event) {
66+
AgentCard newAgentCard = event.getAgentCard();
67+
if (LOGGER.isDebugEnabled()) {
68+
LOGGER.debug("Received new Agent Card: {}", JacksonUtils.toJson(newAgentCard));
69+
}
70+
agentCard.setAgentCard(AgentCardConverterUtil.convertToA2aAgentCard(newAgentCard));
71+
}
72+
});
73+
return agentCard;
5574
}
5675
catch (NacosException e) {
5776
throw new NacosRuntimeException(e.getErrCode(), e.getErrMsg());
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/*
2+
* Copyright 2024-2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.alibaba.cloud.ai.a2a.registry.nacos.discovery;
18+
19+
import java.util.List;
20+
import java.util.concurrent.ThreadLocalRandom;
21+
import java.util.concurrent.atomic.AtomicInteger;
22+
23+
import com.alibaba.cloud.ai.graph.agent.a2a.AgentCardWrapper;
24+
import com.alibaba.nacos.common.utils.CollectionUtils;
25+
import io.a2a.spec.AgentCard;
26+
import io.a2a.spec.AgentInterface;
27+
28+
/**
29+
* Spring AI Alibaba Agent Card Wrapper for Nacos.
30+
*
31+
* @author xiweng.yy
32+
*/
33+
public class NacosAgentCardWrapper extends AgentCardWrapper {
34+
35+
private final AtomicInteger pollingIndex;
36+
37+
public NacosAgentCardWrapper(AgentCard agentCard) {
38+
super(agentCard);
39+
this.pollingIndex = new AtomicInteger(0);
40+
shuffleStartIndex();
41+
}
42+
43+
private void shuffleStartIndex() {
44+
if (CollectionUtils.isNotEmpty(getAgentCard().additionalInterfaces())) {
45+
int shuffleIndex = ThreadLocalRandom.current().nextInt(getAgentCard().additionalInterfaces().size());
46+
pollingIndex.set(shuffleIndex);
47+
}
48+
}
49+
50+
@Override
51+
public String url() {
52+
if (CollectionUtils.isEmpty(getAgentCard().additionalInterfaces())) {
53+
return super.url();
54+
}
55+
List<AgentInterface> agentInterfaces = getAgentCard().additionalInterfaces().stream().filter(agentInterface -> getAgentCard().preferredTransport().equals(agentInterface.transport())).toList();
56+
if (CollectionUtils.isEmpty(agentInterfaces)) {
57+
return super.url();
58+
}
59+
if (1 == agentInterfaces.size()) {
60+
return agentInterfaces.get(0).url();
61+
}
62+
int index = pollingIndex.incrementAndGet() % agentInterfaces.size();
63+
return agentInterfaces.get(index).url();
64+
}
65+
66+
@Override
67+
public void setAgentCard(AgentCard agentCard) {
68+
super.setAgentCard(agentCard);
69+
shuffleStartIndex();
70+
}
71+
}

spring-ai-alibaba-graph-core/src/main/java/com/alibaba/cloud/ai/graph/agent/a2a/A2aNode.java

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@
3838
import com.alibaba.fastjson.JSON;
3939
import com.alibaba.fastjson.TypeReference;
4040
import com.fasterxml.jackson.databind.ObjectMapper;
41-
import io.a2a.spec.AgentCard;
4241
import org.apache.http.HttpEntity;
4342
import org.apache.http.client.methods.CloseableHttpResponse;
4443
import org.apache.http.client.methods.HttpPost;
@@ -52,7 +51,7 @@
5251

5352
public class A2aNode implements NodeAction {
5453

55-
private final AgentCard agentCard;
54+
private final AgentCardWrapper agentCard;
5655

5756
private final String inputKeyFromParent;
5857

@@ -62,11 +61,11 @@ public class A2aNode implements NodeAction {
6261

6362
private final ObjectMapper objectMapper = new ObjectMapper();
6463

65-
public A2aNode(AgentCard agentCard, String inputKeyFromParent, String outputKeyToParent) {
64+
public A2aNode(AgentCardWrapper agentCard, String inputKeyFromParent, String outputKeyToParent) {
6665
this(agentCard, inputKeyFromParent, outputKeyToParent, false);
6766
}
6867

69-
public A2aNode(AgentCard agentCard, String inputKeyFromParent, String outputKeyToParent, boolean streaming) {
68+
public A2aNode(AgentCardWrapper agentCard, String inputKeyFromParent, String outputKeyToParent, boolean streaming) {
7069
this.agentCard = agentCard;
7170
this.inputKeyFromParent = inputKeyFromParent;
7271
this.outputKeyToParent = outputKeyToParent;
@@ -647,7 +646,7 @@ private String buildSendStreamingMessageRequest(OverAllState state, String input
647646
* @param requestPayload JSON string payload built by buildSendMessageRequest
648647
* @return Response body as string
649648
*/
650-
private String sendMessageToServer(AgentCard agentCard, String requestPayload) throws Exception {
649+
private String sendMessageToServer(AgentCardWrapper agentCard, String requestPayload) throws Exception {
651650
String baseUrl = resolveAgentBaseUrl(agentCard);
652651
System.out.println(baseUrl);
653652
System.out.println(requestPayload);
@@ -677,7 +676,7 @@ private String sendMessageToServer(AgentCard agentCard, String requestPayload) t
677676
/**
678677
* Resolve base URL from the AgentCard.
679678
*/
680-
private String resolveAgentBaseUrl(AgentCard agentCard) {
679+
private String resolveAgentBaseUrl(AgentCardWrapper agentCard) {
681680
return agentCard.url();
682681
}
683682

spring-ai-alibaba-graph-core/src/main/java/com/alibaba/cloud/ai/graph/agent/a2a/A2aNodeWithConfig.java

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@
3939
import com.alibaba.fastjson.JSON;
4040
import com.alibaba.fastjson.TypeReference;
4141
import com.fasterxml.jackson.databind.ObjectMapper;
42-
import io.a2a.spec.AgentCard;
4342
import org.apache.http.HttpEntity;
4443
import org.apache.http.client.methods.CloseableHttpResponse;
4544
import org.apache.http.client.methods.HttpPost;
@@ -53,7 +52,7 @@
5352

5453
public class A2aNodeWithConfig implements NodeActionWithConfig {
5554

56-
private final AgentCard agentCard;
55+
private final AgentCardWrapper agentCard;
5756

5857
private final String inputKeyFromParent;
5958

@@ -63,11 +62,11 @@ public class A2aNodeWithConfig implements NodeActionWithConfig {
6362

6463
private final ObjectMapper objectMapper = new ObjectMapper();
6564

66-
public A2aNodeWithConfig(AgentCard agentCard, String inputKeyFromParent, String outputKeyToParent) {
65+
public A2aNodeWithConfig(AgentCardWrapper agentCard, String inputKeyFromParent, String outputKeyToParent) {
6766
this(agentCard, inputKeyFromParent, outputKeyToParent, false);
6867
}
6968

70-
public A2aNodeWithConfig(AgentCard agentCard, String inputKeyFromParent, String outputKeyToParent, boolean streaming) {
69+
public A2aNodeWithConfig(AgentCardWrapper agentCard, String inputKeyFromParent, String outputKeyToParent, boolean streaming) {
7170
this.agentCard = agentCard;
7271
this.inputKeyFromParent = inputKeyFromParent;
7372
this.outputKeyToParent = outputKeyToParent;
@@ -589,9 +588,9 @@ private String buildSendMessageRequest(OverAllState state, String inputKey, Runn
589588

590589
Map<String, Object> params = new HashMap<>();
591590
params.put("message", message);
592-
params.put("threadId", config.threadId());
591+
config.threadId().ifPresent(threadId -> params.put("threadId", threadId));
593592
// FIXME, the key 'userId' should be configurable
594-
params.put("userId", config.metadata("userId"));
593+
config.metadata("userId").ifPresent(userId -> params.put("userId", userId));
595594

596595
Map<String, Object> root = new HashMap<>();
597596
root.put("id", id);
@@ -632,9 +631,9 @@ private String buildSendStreamingMessageRequest(OverAllState state, String input
632631

633632
Map<String, Object> params = new HashMap<>();
634633
params.put("message", message);
635-
params.put("threadId", config.threadId());
634+
config.threadId().ifPresent(threadId -> params.put("threadId", threadId));
636635
// FIXME, the key 'userId' should be configurable
637-
params.put("userId", config.metadata("userId"));
636+
config.metadata("userId").ifPresent(userId -> params.put("userId", userId));
638637

639638
Map<String, Object> root = new HashMap<>();
640639
root.put("id", id);
@@ -656,7 +655,7 @@ private String buildSendStreamingMessageRequest(OverAllState state, String input
656655
* @param requestPayload JSON string payload built by buildSendMessageRequest
657656
* @return Response body as string
658657
*/
659-
private String sendMessageToServer(AgentCard agentCard, String requestPayload) throws Exception {
658+
private String sendMessageToServer(AgentCardWrapper agentCard, String requestPayload) throws Exception {
660659
String baseUrl = resolveAgentBaseUrl(agentCard);
661660
System.out.println(baseUrl);
662661
System.out.println(requestPayload);
@@ -686,7 +685,7 @@ private String sendMessageToServer(AgentCard agentCard, String requestPayload) t
686685
/**
687686
* Resolve base URL from the AgentCard.
688687
*/
689-
private String resolveAgentBaseUrl(AgentCard agentCard) {
688+
private String resolveAgentBaseUrl(AgentCardWrapper agentCard) {
690689
return agentCard.url();
691690
}
692691

spring-ai-alibaba-graph-core/src/main/java/com/alibaba/cloud/ai/graph/agent/a2a/A2aRemoteAgent.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434

3535
public class A2aRemoteAgent extends BaseAgent {
3636

37-
private final AgentCard agentCard;
37+
private final AgentCardWrapper agentCard;
3838

3939
private CompiledGraph compiledGraph;
4040

@@ -126,7 +126,7 @@ public static class Builder {
126126
private String outputKey = "output";
127127

128128
// A2aRemoteAgent specific properties
129-
private AgentCard agentCard;
129+
private AgentCardWrapper agentCard;
130130

131131
private AgentCardProvider agentCardProvider;
132132

@@ -154,7 +154,7 @@ public Builder outputKey(String outputKey) {
154154
}
155155

156156
public Builder agentCard(AgentCard agentCard) {
157-
this.agentCard = agentCard;
157+
this.agentCard = new AgentCardWrapper(agentCard);
158158
return this;
159159
}
160160

spring-ai-alibaba-graph-core/src/main/java/com/alibaba/cloud/ai/graph/agent/a2a/AgentCardProvider.java

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@
1616

1717
package com.alibaba.cloud.ai.graph.agent.a2a;
1818

19-
import io.a2a.spec.AgentCard;
20-
2119
/**
2220
* A2A Agent Card Provider.
2321
*
@@ -29,7 +27,7 @@ public interface AgentCardProvider {
2927
* Get agent card.
3028
* @return agent card
3129
*/
32-
AgentCard getAgentCard();
30+
AgentCardWrapper getAgentCard();
3331

3432
/**
3533
* Get agent card by agent name.
@@ -41,7 +39,7 @@ public interface AgentCardProvider {
4139
* @param agentName agent name
4240
* @return agent card
4341
*/
44-
default AgentCard getAgentCard(String agentName) {
42+
default AgentCardWrapper getAgentCard(String agentName) {
4543
throw new UnsupportedOperationException();
4644
}
4745

0 commit comments

Comments
 (0)