Skip to content

Commit 743190f

Browse files
Copilotphrocker
andauthored
Add create_agent_with_context verb combining context creation and agent creation (#175)
* Initial plan * Add create_agent_with_context verb that combines context creation, endpoint discovery, trust policy generation, and agent creation Co-authored-by: phrocker <[email protected]> * Address code review feedback: improve consistency with original method implementations and use assertThrows Co-authored-by: phrocker <[email protected]> * Remove create_agent_context and create_agent verbs, keeping only the combined create_agent_with_context verb Co-authored-by: phrocker <[email protected]> --------- Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: phrocker <[email protected]>
1 parent 30eee22 commit 743190f

File tree

2 files changed

+340
-154
lines changed

2 files changed

+340
-154
lines changed

enterprise-agent/src/main/java/io/sentrius/agent/analysis/agents/verbs/AgentVerbs.java

Lines changed: 188 additions & 154 deletions
Original file line numberDiff line numberDiff line change
@@ -587,112 +587,6 @@ public List<ZtatAsessment> analyzeAtatRequests(AgentExecution execution, List<At
587587
}
588588

589589

590-
@Verb(
591-
name = "create_agent_context", returnType = AgentContextDTO.class, description = "Creates an agent Context." +
592-
" must be done before creating an agent.",
593-
requiresTokenManagement = true,
594-
returnName = "created_context",
595-
exampleJson = "{ \"context\": \"Notify when a new user is added\" }"
596-
)
597-
public AgentContextDTO createAgentContext(AgentExecution execution, AgentExecutionContextDTO context)
598-
throws ZtatException, Exception {
599-
log.info("Creating agent context");
600-
var contextArgs = context.getExecutionArgs();
601-
if (contextArgs == null || contextArgs.isEmpty()) {
602-
throw new RuntimeException("Context is required to create an agent context");
603-
}
604-
var name = context.getExecutionArgument("agentName");
605-
606-
String agentName = name.isPresent() ? name.get().toString() : "name";
607-
if (!agentName.isEmpty()) {
608-
agentName = agentName.replaceAll("_", "-");
609-
}
610-
611-
var originalContext = context.getExecutionArgument("context");
612-
613-
var requestDtoContext = normalize( originalContext.orElseThrow().toString() );
614-
requestDtoContext += ". Please request endpoints to perform your work.";
615-
AgentContextRequestDTO dto = AgentContextRequestDTO.builder().context(requestDtoContext).
616-
description(requestDtoContext).name(agentName).build();
617-
var createdContext = agentClientService.createAgentContext(execution, dto);
618-
// Here you would typically create a context in your system, e.g., store it in a database or cache.
619-
620-
context.setAgentContext(AgentContextDTO.builder()
621-
.contextId(createdContext.getContextId())
622-
.name(createdContext.getName())
623-
.context(createdContext.getContext())
624-
.description(createdContext.getDescription())
625-
.build());
626-
627-
// load the endpoints
628-
var messages = new ArrayList<Message>();
629-
630-
messages.add(Message.builder().role("system").content("The user will provide the context of what an agent to " +
631-
"be created will do. Respond with a json response { \"endpoints_like\" : [ array ] } where array is the " +
632-
"features " +
633-
"or tools to be called. Do not put endpoints in there, just text and explanation of the endpoint. " +
634-
"We'll perform a text " +
635-
"search to find" +
636-
" endpoints").build());
637-
messages.add(Message.builder().role("user").content(originalContext.get().asText()).build());
638-
639-
LLMRequest chatRequest = LLMRequest.builder().model("gpt-4o-mini").messages(messages).build();
640-
var resp = llmService.askQuestion(execution, chatRequest);
641-
642-
Response response = JsonUtil.MAPPER.readValue(resp, Response.class);
643-
log.info("Response is {}", resp);
644-
ArrayNode endpointsLikeList = JsonUtil.MAPPER.createArrayNode();
645-
for (Response.OutputItem choice : response.getOutputItems()) {
646-
var content = choice.getContent().stream().filter(c -> "output_text".equals(c.getType()) || "text".equals(c.getType())).map(c -> c.getText()).findFirst().orElse("");
647-
if (content.startsWith("```json")) {
648-
content = content.substring(7, content.length() - 3);
649-
} else if (content.startsWith("```")) {
650-
content = content.substring(3, content.length() - 3);
651-
}
652-
log.info("content is {}", content);
653-
if (null != content && !content.isEmpty()) {
654-
655-
var node = JsonUtil.MAPPER.enable(JsonParser.Feature.ALLOW_COMMENTS).readTree(content);
656-
657-
if (node.get("endpoints_like") == null || !node.get("endpoints_like").isArray()) {
658-
log.info("No endpoints_like found in response");
659-
continue;
660-
}
661-
var arrayNode = (ArrayNode) node.get("endpoints_like");
662-
for (JsonNode localNode : arrayNode) {
663-
if (localNode.isNull() || localNode.asText().isEmpty()) {
664-
continue;
665-
}
666-
if (localNode.has("method") && localNode.has("endpoint")) {
667-
668-
if (localNode.get("endpoint").asText().isEmpty() || localNode.get("method").asText().isEmpty()) {
669-
log.info("Skipping empty endpoint or method");
670-
continue;
671-
}
672-
673-
endpointsLikeList.add(localNode.asText());
674-
}
675-
676-
}
677-
678-
}
679-
}
680-
681-
if (endpointsLikeList.size() > 0) {
682-
683-
ObjectNode endpointsLike = JsonUtil.MAPPER.createObjectNode();
684-
endpointsLike.put("context", originalContext.orElseThrow().toString());
685-
endpointsLike.put("endpoints_like", endpointsLikeList);
686-
context.setExecutionArgs(endpointsLike);
687-
var endpoints = getEndpointsLike(execution, context);
688-
log.info("Endpoints like {}", endpoints);
689-
690-
context.addToMemory("endpoints", endpoints);
691-
}
692-
693-
return createdContext;
694-
}
695-
696590
private String normalize(String s) {
697591
if (s == null) return null;
698592
if (s.startsWith("\"") && s.endsWith("\"")) {
@@ -865,74 +759,214 @@ public ObjectNode getCurrentAgentStatus(AgentExecution execution, AgentExecution
865759
return result;
866760
}
867761

868-
@Verb(name = "create_agent", returnType = AgentExecutionContextDTO.class, description = "Creates an agent who has the " +
869-
"context. a previously defined contextId is required. previously defined endpoints can be used to build a " +
870-
"trust policy. must call create_agent_context before this verb. agent type is chat or chat-autonomous. chat is chat only, chat-autonomous is chat and autonomous. determine based on workload.",
871-
exampleJson = "{ \"agentName\": \"agentName\", \"agentType\": \"agentType\" }",
872-
requiresTokenManagement = true )
873-
public ObjectNode createAgent(AgentExecution execution, AgentExecutionContextDTO context)
874-
throws ZtatException, JsonProcessingException {
875-
log.info("Creating agent with context: {}", context);
876-
877-
var contextId=context.getSafeLabel("created_context", "contextId");
878-
var agentName = context.getSafeLabel("agentName");
879-
var agentType = context.getSafeLabel("agentType");
880-
Optional<ObjectNode> optEndpoints = context.getExecutionArgumentScoped("endpoints", ObjectNode.class);
881-
var policyId = "";
882-
log.info("Context ID is {}, agentName is {}", contextId, agentName);
883-
if (null != optEndpoints && optEndpoints.isPresent()) {
884-
var policyBuilder = ATPLPolicy.builder()
885-
.version("v0")
886-
.description("Policy for agent " + agentName)
887-
.policyId(UUID.randomUUID().toString());
888-
889-
var endpoints = optEndpoints.get().get("endpoints");
890-
log.info("Endpoints are {}", endpoints);
891-
List<Capability> capabilities = new ArrayList<>();
892-
for(JsonNode endpoint : endpoints) {
893-
var endpointStr = endpoint.get("endpoint").asText();
894-
895-
Capability capability = Capability.builder()
896-
.description(endpoint.get("name").asText())
897-
.endpoints(List.of(extractNormalizedPath(endpointStr)))
898-
.build();
899-
capabilities.add(capability);
762+
/**
763+
* Creates an agent with its context, trust policy, and endpoint discovery in a single call.
764+
* This is the primary method for agent creation, handling context creation,
765+
* LLM-driven endpoint discovery, trust policy generation, and agent registration.
766+
*
767+
* @param execution The agent execution context containing authentication and execution details
768+
* @param context The execution context DTO containing agentName, context, and agentType parameters
769+
* @return ObjectNode containing the created agent's ID and context information
770+
* @throws ZtatException If there is an error during API communication
771+
* @throws Exception If there is an error during LLM communication or endpoint discovery
772+
*/
773+
@Verb(
774+
name = "create_agent_with_context",
775+
returnType = ObjectNode.class,
776+
description = "Creates an agent with context, trust policy, and endpoint discovery in a single call. " +
777+
"This handles context creation, endpoint discovery via LLM, trust policy generation, and agent creation. " +
778+
"Agent type can be 'chat' (chat only) or 'chat-autonomous' (chat and autonomous). " +
779+
"Determine agent type based on whether the workload requires autonomous operation.",
780+
exampleJson = "{ \"agentName\": \"my-agent\", \"context\": \"Notify when a new user is added\", \"agentType\": \"chat\" }",
781+
requiresTokenManagement = true,
782+
returnName = "created_agent"
783+
)
784+
public ObjectNode createAgentWithContext(AgentExecution execution, AgentExecutionContextDTO context)
785+
throws ZtatException, Exception {
786+
log.info("Creating agent with context in a single call");
787+
788+
// Step 1: Extract and validate parameters
789+
var contextArgs = context.getExecutionArgs();
790+
if (contextArgs == null || contextArgs.isEmpty()) {
791+
throw new RuntimeException("Arguments are required to create an agent. Expected: agentName, context, agentType");
792+
}
793+
794+
var nameArg = context.getExecutionArgument("agentName");
795+
String agentName = nameArg.isPresent() ? nameArg.get().asText() : "agent-" + UUID.randomUUID().toString().substring(0, 8);
796+
if (!agentName.isEmpty()) {
797+
agentName = agentName.replaceAll("_", "-");
798+
}
799+
800+
var originalContext = context.getExecutionArgument("context");
801+
if (originalContext.isEmpty()) {
802+
throw new RuntimeException("Context is required to create an agent. Please provide a description of what the agent should do.");
803+
}
804+
805+
var agentTypeArg = context.getExecutionArgument("agentType");
806+
String agentType = agentTypeArg.isPresent() ? agentTypeArg.get().asText() : "chat";
807+
808+
log.info("Creating agent '{}' with type '{}' and context: {}", agentName, agentType, originalContext.get().asText());
809+
810+
// Step 2: Create the agent context via API
811+
var requestDtoContext = normalize(originalContext.get().asText());
812+
requestDtoContext += ". Please request endpoints to perform your work.";
813+
AgentContextRequestDTO dto = AgentContextRequestDTO.builder()
814+
.context(requestDtoContext)
815+
.description(requestDtoContext)
816+
.name(agentName)
817+
.build();
818+
var createdContext = agentClientService.createAgentContext(execution, dto);
819+
820+
if (createdContext == null || createdContext.getContextId() == null) {
821+
throw new RuntimeException("Failed to create agent context");
822+
}
900823

824+
context.setAgentContext(AgentContextDTO.builder()
825+
.contextId(createdContext.getContextId())
826+
.name(createdContext.getName())
827+
.context(createdContext.getContext())
828+
.description(createdContext.getDescription())
829+
.build());
901830

831+
log.info("Created agent context with ID: {}", createdContext.getContextId());
832+
833+
// Step 3: Use LLM to discover endpoints based on context
834+
var messages = new ArrayList<Message>();
835+
messages.add(Message.builder().role("system").content("The user will provide the context of what an agent to " +
836+
"be created will do. Respond with a json response { \"endpoints_like\" : [ array ] } where array is the " +
837+
"features " +
838+
"or tools to be called. Do not put endpoints in there, just text and explanation of the endpoint. " +
839+
"We'll perform a text " +
840+
"search to find" +
841+
" endpoints").build());
842+
messages.add(Message.builder().role("user").content(originalContext.get().asText()).build());
843+
844+
LLMRequest chatRequest = LLMRequest.builder().model("gpt-4o-mini").messages(messages).build();
845+
var resp = llmService.askQuestion(execution, chatRequest);
846+
847+
Response response = JsonUtil.MAPPER.readValue(resp, Response.class);
848+
log.info("LLM response for endpoint discovery: {}", resp);
849+
850+
ArrayNode endpointsLikeList = JsonUtil.MAPPER.createArrayNode();
851+
for (Response.OutputItem choice : response.getOutputItems()) {
852+
var content = choice.getContent().stream()
853+
.filter(c -> "output_text".equals(c.getType()) || "text".equals(c.getType()))
854+
.map(c -> c.getText())
855+
.findFirst()
856+
.orElse("");
857+
if (content.startsWith("```json")) {
858+
content = content.substring(7, content.length() - 3);
859+
} else if (content.startsWith("```")) {
860+
content = content.substring(3, content.length() - 3);
902861
}
903-
CapabilitySet capabilitySet = CapabilitySet.builder()
904-
.primitives(capabilities)
905-
.build();
906-
policyBuilder.capabilities(capabilitySet);
907862

908-
ATPLPolicy policy = policyBuilder.build();
863+
if (null != content && !content.isEmpty()) {
864+
var node = JsonUtil.MAPPER.enable(JsonParser.Feature.ALLOW_COMMENTS).readTree(content);
909865

910-
policyId = savePolicy(execution, true, policy);
866+
if (node.get("endpoints_like") == null || !node.get("endpoints_like").isArray()) {
867+
log.info("No endpoints_like found in response");
868+
continue;
869+
}
870+
var arrayNode = (ArrayNode) node.get("endpoints_like");
871+
for (JsonNode localNode : arrayNode) {
872+
if (localNode.isNull() || localNode.asText().isEmpty()) {
873+
continue;
874+
}
875+
if (localNode.has("method") && localNode.has("endpoint")) {
876+
if (localNode.get("endpoint").asText().isEmpty() || localNode.get("method").asText().isEmpty()) {
877+
log.info("Skipping empty endpoint or method");
878+
continue;
879+
}
880+
endpointsLikeList.add(localNode.asText());
881+
} else {
882+
// Handle simple text entries (common LLM response format)
883+
endpointsLikeList.add(localNode.asText());
884+
}
885+
}
886+
}
887+
}
911888

912-
} else {
913-
log.info("No endpoints provided, using default");
889+
// Step 4: Discover actual endpoints based on LLM suggestions
890+
ObjectNode discoveredEndpoints = null;
891+
if (endpointsLikeList.size() > 0) {
892+
ObjectNode endpointsLike = JsonUtil.MAPPER.createObjectNode();
893+
endpointsLike.put("context", originalContext.get().asText());
894+
endpointsLike.put("endpoints_like", endpointsLikeList);
895+
context.setExecutionArgs(endpointsLike);
896+
discoveredEndpoints = getEndpointsLike(execution, context);
897+
log.info("Discovered endpoints: {}", discoveredEndpoints);
898+
context.addToMemory("endpoints", discoveredEndpoints);
914899
}
915900

901+
// Step 5: Build trust policy from discovered endpoints
902+
String policyId = "";
903+
if (discoveredEndpoints != null && discoveredEndpoints.has("endpoints")) {
904+
var endpoints = discoveredEndpoints.get("endpoints");
905+
if (endpoints.isArray() && endpoints.size() > 0) {
906+
var policyBuilder = ATPLPolicy.builder()
907+
.version("v0")
908+
.description("Policy for agent " + agentName)
909+
.policyId(UUID.randomUUID().toString());
910+
911+
List<Capability> capabilities = new ArrayList<>();
912+
for (JsonNode endpoint : endpoints) {
913+
if (endpoint.has("endpoint") && endpoint.has("name")) {
914+
var endpointStr = endpoint.get("endpoint").asText();
915+
Capability capability = Capability.builder()
916+
.description(endpoint.get("name").asText())
917+
.endpoints(List.of(extractNormalizedPath(endpointStr)))
918+
.build();
919+
capabilities.add(capability);
920+
}
921+
}
922+
923+
if (!capabilities.isEmpty()) {
924+
CapabilitySet capabilitySet = CapabilitySet.builder()
925+
.primitives(capabilities)
926+
.build();
927+
policyBuilder.capabilities(capabilitySet);
916928

929+
ATPLPolicy policy = policyBuilder.build();
930+
policyId = savePolicy(execution, true, policy);
931+
log.info("Created trust policy with ID: {}", policyId);
932+
}
933+
}
934+
}
917935

918-
var agentBuilder = AgentRegistrationDTO.builder()
919-
.agentContextId(contextId)
936+
// Step 6: Create the agent with context and policy
937+
var agentBuilder = AgentRegistrationDTO.builder()
938+
.agentContextId(createdContext.getContextId().toString())
920939
.clientId(UUID.randomUUID().toString())
921940
.agentType(agentType)
922941
.agentName(agentName);
923-
if (!policyId.isEmpty()){
942+
943+
if (!policyId.isEmpty()) {
924944
log.info("Using policyId {}", policyId);
925945
agentBuilder.agentPolicyId(policyId);
926946
} else {
927-
log.info("No policyId provided, using default");
947+
log.info("No policy created, using default policy");
928948
}
929949

930950
AgentRegistrationDTO agentRegistration = agentBuilder.build();
931-
var response = agentClientService.createAgent(execution, agentRegistration);
932-
ObjectNode contextNode = JsonUtil.MAPPER.createObjectNode();
933-
contextNode.put("agentId", agentRegistration.getAgentName());
951+
var agentResponse = agentClientService.createAgent(execution, agentRegistration);
952+
log.info("Agent creation response: {}", agentResponse);
953+
954+
// Step 7: Build and return the result
955+
ObjectNode resultNode = JsonUtil.MAPPER.createObjectNode();
956+
resultNode.put("agentId", agentName);
957+
resultNode.put("agentName", agentName);
958+
resultNode.put("agentType", agentType);
959+
resultNode.put("contextId", createdContext.getContextId().toString());
960+
resultNode.put("policyId", policyId.isEmpty() ? "default" : policyId);
961+
962+
if (discoveredEndpoints != null && discoveredEndpoints.has("endpoints")) {
963+
resultNode.put("endpointCount", discoveredEndpoints.get("endpoints").size());
964+
} else {
965+
resultNode.put("endpointCount", 0);
966+
}
934967

935-
return contextNode;
968+
log.info("Successfully created agent '{}' with context and trust policy", agentName);
969+
return resultNode;
936970
}
937971

938972
@Verb(name = "get_agent_status", returnType = AgentExecutionContextDTO.class, description = "Queries the agent " +

0 commit comments

Comments
 (0)