logo

Are you need IT Support Engineer? Free Consultant

Building an LLM Agent using CAP

  • By sujay
  • 27/05/2026
  • 21 Views

Agentic systems are all the rage these days, and for good reason. They allow us to build AI systems that can iteratively work towards solutions. And while there are many frameworks and tools available for building standalone agents, the SAP Cloud Application Programming (CAP) model provides a unique opportunity to build agents that capture domain knowledge and can easily integrate with SAP systems and services.
In this tutorial, we will build a simple LLM agent using CAP.

Agenda:

Overview

First, what is an agent? There are many definitions out there, but here are a few that I like:

“Agents combine language models with tools to create systems that can reason about tasks, decide which tools to use, and iteratively work towards solutions.” – langchain
“Agents are systems that independently accomplish tasks on your behalf.” – openai
“An LLM in a loop with an objective” – Simon Willison
“An LLM, some tools and a loop meet in a bar” – me

That last one makes for some good jokes.

In essence, the agent controls the interaction between the model, the tools, and the user.

The tool output is fed back into the model as context for the next step, allowing the agent to iteratively work towards a solution. The loop is continued until the model outputs a final answer or meets a predefined stopping condition like a maximum iteration count. This is what allows agents to handle complex tasks that require multiple steps and interactions with external systems.

TLDR;

If you just want a quick example, here you go. If you want to understand how to integrate agents into a CAP application step by step, read on.

import cds from '@sap/cds'
import { OrchestrationClient } from '@sap-ai-sdk/langchain'
import { createAgent, tool, SystemMessage, HumanMessage } from 'langchain'
import { z } from 'zod'

// aicore models: https://me.sap.com/notes/3437766
const model = new OrchestrationClient({
  promptTemplating: { model: { name: 'mistralai--mistral-small' } }
})

class TravelService extends cds.ApplicationService {
  init() {
    const tripSearch = tool(
      async ({ begin, end }) => {
        const { Trips } = this.entities
        const q = SELECT.from(Trips).where `beginDate >= ${begin} and endDate <= ${end}`
        const availableTrips = await this.run(q)
        return JSON.stringify(availableTrips)
      },
      {
        name: 'tripSearch',
        description: 'Find trips by date range',
        schema: z.object({
          begin: z.iso.date().describe('begin date, format YYYY-MM-DD'),
          end: z.iso.date().describe('end date, format YYYY-MM-DD')
        })
      }
    )

    const agent = createAgent({
      model, tools: [tripSearch],
      systemPrompt: 'Be a helpful travel assistant.',
    })

    this.on('invokeAgent', async function(req) {
      const systemMessage = new SystemMessage(`Today is ${new Date().toDateString()}`)
      const userMessage = new HumanMessage(req.data.input)
      const result = await agent.invoke({ messages: [systemMessage, userMessage] })
      return result.messages.at(-1).content
    })

    return super.init()
  }
}

export default TravelService

Initialize the CAP app

To get started, we will initialize a new CAP project using the cds init command. This will create a new directory with the necessary files and folders for our CAP application. For this tutorial, we will use the Node.js runtime.

cds init cap-agent --nodejs
code cap-agent  # open in vscode

Connecting to aicore with sap-ai-sdk

We will use the sap-ai-sdk to connect our CAP application to the SAP AI Core service. This will allow us to call a variety of language models from our CAP application.

First, make sure that you have an SAP AI Core service instance. The setup is described here: https://help.sap.com/docs/sap-ai-core/sap-ai-core-service-guide/initial-setup

If using the Cloud Foundry cli, you can create a service instance like this:

cf create-service aicore standard agent-ai

Now we can use cds bind to create a service key and save a reference to it in .cdsrc-private.json. This will come in handy later when we want to try it out.

cds bind -2 agent-ai

Next install the sap-ai-sdk package. There are multiple packages available, we want the langchain compatible one. Also install langchain (for creating agents) and zod (for tool schema validation).

npm install @SAP-ai-sdk/langchain langchain zod

Finally, we need to set our project to use ES modules in order to use the sap-ai-sdk package and more importantly top level await.

# set type to module in package.json
npm pkg set type=module

For information on CommonJS usage, refer to this blog post.

We can test out our connection by creating a simple test script, let's save it at test/ai-sdk.js:

// test/sap-ai-sdk.js
import { OrchestrationClient } from '@sap-ai-sdk/langchain';
import { HumanMessage, SystemMessage } from 'langchain';

const model = new OrchestrationClient({
  promptTemplating: {
    model: {
      // aicore models: https://me.sap.com/notes/3437766
      // name: 'gpt-5',
      // name: 'anthropic--claude-4.5-haiku',
      name: 'mistralai--mistral-small',        // cheap for testing
    }
  }
});

const systemMessage = new SystemMessage('Be a helpful assistant!');
const userMessage = new HumanMessage('How many p in strawberry?')
const response = await model.invoke([systemMessage, userMessage]);

console.log(response.content)

Now let's utilize our earlier cds bind reference to run this script with the correct environment variables:

cds bind --exec -- node test/ai-sdk.js

Langchain: Calling an agent

If you're familiar with agents, you can skip this section and jump directly to calling an agent from CAP. But if you're new to agents, this section is for you.

As mentioned earlier, agents combine language models with tools. In their simplest form, an agent contains a model and no tools. So the output is similar to calling the model directly.

const agent = createAgent({
  model: model,
  tools: [],
})

The agent.invoke method is slightly different than the model.invoke method. It expects an object with a messages property.

const messages = [systemMessage, userMessage];
const result = await agent.invoke({ messages });

Let's save this code in a new file test/agent.js and run it:

// test/agent.js
import { OrchestrationClient } from '@sap-ai-sdk/langchain';
import { createAgent, HumanMessage, SystemMessage } from 'langchain';

const model = new OrchestrationClient({
  promptTemplating: { model: { name: 'mistralai--mistral-small' } }
});


const agent = createAgent({
  model: model,
  tools: [],
})

const systemMessage = new SystemMessage('Be a helpful assistant!');
const userMessage = new HumanMessage('How many p in strawberry?')
const messages = [systemMessage, userMessage];
const result = await agent.invoke({ messages });

console.log(result.messages.at(-1).content)
cds bind --exec -- node test/agent.js

Now this is a very simple agent that doesn't do much more than the model itself, but it sets the stage for adding tools and iterating in a loop based on tool output, which is where the real power of agents comes in.

Langchain: Calling a tool

A tool in langchain consists of two parts: a function that performs some action, and a schema that tells the agent how to use the tool. There's also a name and description that helps the agent decide when to use the tool.

Let's say we have some data about available trips…

const availableTrips = [{
  beginDate: '2026-06-13',
  endDate: '2026-06-14',
  description: 'Weekend trip to Heidelberg featuring castle sightseeing, hiking and excellent cuisine.'
}, {
  beginDate: '2026-06-13',
  endDate: '2026-06-13',
  description: 'Daytrip to Europa-Park with exciting rollercoasters.'
}]

We can create a tool that searches for trips based on a date range.

const tripSearch = tool(
  ({ begin, end }) => {
    const trips = availableTrips.filter(t => t.beginDate >= begin && t.endDate <= end)
    return JSON.stringify(trips)
  },
  {
    name: 'tripSearch',
    description: 'Find trips by date range',
    schema: z.object({
      begin: z.iso.date().describe('begin date, format YYYY-MM-DD'),
      end: z.iso.date().describe('end date, format YYYY-MM-DD')
    })
  }
)

Focussing on the schema for a moment, we are using zod to define the expected input format for the tool. The agent will use this schema to provide suitable inputs. Furthermore, the agent uses the schema definition to validate the inputs before calling the tool.

In this case, the tool expects an object with begin and end properties, both of which should be date strings in ISO format YYYY-MM-DD.

The validated inputs are then passed to the defined function, which performs the search and returns the results as a JSON string.

Important Note: The validation only checks for the technical correctness of the input. It is still possible for the agent to provide semantically incorrect inputs that pass validation. This can also be maliciously exploited, so the inputs need to be handled as untrusted data. For example, be aware of potential SQL injection if the tool interacts with a database.

Now we can add this tool to our agent and test it out. Let's save this in a new file test/agent-tool.js:

// test/agent-tool.js
import { OrchestrationClient } from '@sap-ai-sdk/langchain';
import { createAgent, HumanMessage, SystemMessage, tool } from 'langchain';
import { z } from 'zod';

const model = new OrchestrationClient({
  promptTemplating: { model: { name: 'mistralai--mistral-small' } }
});


const availableTrips = [{
  beginDate: '2026-06-13',
  endDate: '2026-06-14',
  description: 'Weekend trip to Heidelberg featuring castle sightseeing, hiking and excellent cuisine.'
}, {
  beginDate: '2026-06-13',
  endDate: '2026-06-13',
  description: 'Daytrip to Europa-Park with exciting rollercoasters.'
}]

const tripSearch = tool(
  ({ begin, end }) => {
    const trips = availableTrips.filter(t => t.beginDate >= begin && t.endDate <= end)
    return JSON.stringify(trips)
  },
  {
    name: 'tripSearch',
    description: 'Find trips by date range',
    schema: z.object({
      begin: z.iso.date().describe('begin date, format YYYY-MM-DD'),
      end: z.iso.date().describe('end date, format YYYY-MM-DD')
    })
  }
)


const agent = createAgent({
  model: model,
  tools: [tripSearch],
})

// Our model needs to know what date it is, so we provide it in the system prompt.
// The date is hardcoded that our model picks a deterministic weekend.
const systemMessage = new SystemMessage('Be a helpful assistant! Today is 2026-06-10');
const userMessage = new HumanMessage('Find me a relaxing trip for the next weekend')
const messages = [systemMessage, userMessage];
const result = await agent.invoke({ messages });

console.log('=== Tool calls ===')
console.log(result.messages.slice(2, 4))

console.log('=== Answer ===')
console.log(result.messages.at(-1).content)
cds bind --exec -- node test/agent-tool.js

The agent should recognize that the user is asking for a trip recommendation, decide to use the tripSearch tool and use the answer for the final response.

Notice that we've also adjusted the system prompt to include the “current” date:

// The date is hardcoded that our model picks a deterministic weekend.
const systemMessage = new SystemMessage('Be a helpful assistant! Today is 2026-06-10');

If we do not provide the current date, the model typically falls back to its training data… in my case, July 2024. Not quite ideal for planning a weekend trip in 2026. Feel free to experiment with different dates and see how it affects the agent's behavior. You can also try asking for trips on specific dates or with different criteria to see how the agent utilizes the tool.

Create a data model and a service

Now that we know how to build an agent, let's see how we can integrate it into a CAP application with a real data model and service.

First, let's create a simple data model for storing trip information. We can define this in the db/schema.cds file:

// db/schema.cds
namespace example.travel;
using { User } from '@sap/cds/common';


entity Trips {
  key ID       : Integer @readonly;
  description  : String(2048);
  beginDate    : Date default $now;
  endDate      : Date default $now;
  bookingFee   : Decimal(9,4) default 0;
  currency     : String(3) default 'EUR';
  agency       : Association to Agencies;
  bookings     : Association to many Bookings on bookings.trip = $self;
}

entity Agencies {
    key ID : Integer @readonly;
    name   : String(256);
}


entity Bookings {
  key trip        : Association to Trips;
  key user        : User @readonly @CDS.on.insert: $user;
      bookingDate : Date default $now;
}

Then define a customer facing service in srv/travel-service.cds:

// srv/travel-service.cds
using { example.travel } from '../db/schema';

@rest // or any other protocol you like
service TravelService {
    @readonly entity Trips as projection on travel.Trips
    actions {
        action book() returns String;
    };

    @readonly entity Agencies as projection on travel.Agencies;
    @readonly entity Bookings as projection on travel.Bookings where $user = user;
}

This service exposes the Trips and Agencies entities and also defines a custom action book for booking a trip. We will implement the logic for this action later.

Next, we need some sample data. Always use cds add data to add syntactically correct sample data. Let's add some trips and agencies:

cds add data -n 5 --filter Agencies
cds add data -n 10 --filter Trips

The test data is generated randomly, but an LLM can be used to generate meaningful test data while adhering to the existing schema. For example:

 db/data/example.travel-Trips.csv 

ID,description,beginDate,endDate,bookingFee,currency,agency_ID
2001,"7-Night Maldives Overwater Bungalow Retreat — private chef dinners, unlimited snorkeling excursions, sunset dolphin cruise, and couples spa all included",2026-06-14,2026-06-21,4299.0000,EUR,1001
2002,"14-Night Grand Mediterranean Yacht Odyssey — sail Barcelona to Athens with stops in Ibiza, Amalfi, and Santorini; premium open bar, fine dining, and water sports throughout",2026-07-05,2026-07-19,6850.0000,EUR,1002
2003,"10-Night Bali Spiritual & Adventure Escape — morning yoga, volcano trekking, rice terrace cycling, nightly Kecak fire dance performances, and full board at a jungle resort",2026-08-01,2026-08-11,3199.0000,USD,1003
2004,"5-Night Dubai Ultra-Luxury City Break — penthouse at Burj Al Arab, desert falcon safari, helicopter city tour, and Michelin-starred dinners every evening",2026-09-10,2026-09-15,7499.0000,USD,1004
2005,"12-Night African Safari & Zanzibar Beach Combo — Great Migration in the Serengeti, game drives at dusk, private Zanzibar beach resort, spice farm tour, all meals included",2026-10-02,2026-10-14,5975.0000,EUR,1005
2006,"8-Night Norway Northern Lights Adventure — dog sledding, ice hotel overnight stay, snowmobile aurora tour, reindeer farm visit, and traditional Viking feast",2027-01-15,2027-01-23,5250.0000,EUR,1006
2007,"11-Night Thailand Island Hopper — Koh Samui, Koh Phi Phi, and Phuket; speedboat transfers, beach parties, Muay Thai show, and a gourmet Thai cooking class",2026-11-08,2026-11-19,2899.0000,USD,1007
2008,"9-Night Amalfi Coast & Tuscany Gourmet Tour — private villa stays, truffle hunting, wine tasting at five vineyards, pasta-making masterclass, and a private boat day",2026-05-20,2026-05-29,5100.0000,EUR,1008
2009,"6-Night Cancún All-Inclusive Sun & Surf — beachfront resort, unlimited cocktails, catamaran party boat, cenote snorkeling, and a Mayan ruins guided day trip",2026-12-01,2026-12-07,1999.0000,USD,1009
2010,"15-Night New Zealand Adventure Circuit — bungee jumping in Queenstown, Milford Sound cruise, hobbit village tour, Rotorua geothermal spa, and Tongariro Alpine Crossing",2027-03-10,2027-03-25,7200.0000,EUR,1010

 db/data/example.travel-Agencies.csv

ID,name
1001,SunEscape Holidays
1002,Wanderlust All-Inclusive
1003,Tropical Getaways Ltd.
1004,Azure Horizon Travel
1005,Paradise Found Agency
1006,Elite Escapes International
1007,Oceanic Dreams Travel
1008,Grand Voyage Co.
1009,Blissful Journeys
1010,The Luxury Travel Bureau

Run the application with

cds w

You can now navigate to http://localhost:4004/ and inspect the api endpoints and the generated test data.

Calling the agent in CAP with a query tool (read)

First, let's save the agent code into its own file srv/agent.js so that it does not clutter our service implementation. This includes the model initialization and system prompt definition. We also add a simple middleware that logs the tool calls for better transparency.

// srv/agent.js
import { OrchestrationClient } from '@sap-ai-sdk/langchain'
import { createAgent as create, createMiddleware, dynamicSystemPromptMiddleware, SystemMessage } from 'langchain'

export const model = new OrchestrationClient({
  promptTemplating: {
    model: {
      // aicore models: https://me.sap.com/notes/3437766
      // name: 'gpt-5',
      // name: 'anthropic--claude-4.5-haiku',
      name: 'mistralai--mistral-small',        // cheap for testing
    }
  }
})

const logToolCalls = createMiddleware({
  name: 'LogToolCalls',
  wrapToolCall: async (request, handler) => {
    const { toolCall: { name, args } } = request

    console.log('[tool:start]', name, args)
    try {
      const result = await handler(request)
      console.log('[tool:end]', name, result.content)
      return result
    } catch (err) {
      console.error('[tool:error]', name, err)
      throw err
    }
  },
})

const dynamicSystemPrompt = dynamicSystemPromptMiddleware(() => {
  return `Today is ${new Date().toDateString()}`
})

/** Wrapper for langchain createAgent function @type {typeof create} */
export const createAgent = (args) => {
  const systemPrompt="Be a helpful travel assistant."

  return create({
    model,
    systemPrompt,
    middleware: [dynamicSystemPrompt, logToolCalls],
    ...args,
  })
}

To the fun part: integrating the agent into our CAP application. We will create a new tool that allows the agent to query available trips based on a date range, similar to the one we created in the testing section, but this time it will query our actual data model.

To query trips, we can use a CQL SELECT query in the context of TravelService.

const { Trips } = this.entities
const { SELECT } = cds.ql

// tip: to interactively test cql queries, use the cds repl
const q = SELECT.from(Trips).where `beginDate >= ${begin} and endDate <= ${end}`

Note that we explicitly use tagged template strings for the where clause. This allows us to safely inject the parameters, as cds.ql will handle the parameterization for us. This is important, as the agent might provide malicious inputs that could lead to SQL injection if not handled properly.

We can then execute this query on the TravelService, which will ensure that all the usual service logic is applied, including user permissions.

// calling TravelService => reusing user permissions
const availableTrips = await this.run(q)

Create a new file srv/travel-service.js with the following code:

// srv/travel-service.js
import cds from '@sap/cds'
import { createAgent } from './agent.js'
import { HumanMessage, tool } from 'langchain'
import { z } from 'zod'

class TravelService extends cds.ApplicationService {
  init() {

    const tripSearch = tool(
      async ({ begin, end }) => {
        const { Trips } = this.entities
        const { SELECT } = cds.ql

        // tip: to interactively test cql queries, use the cds repl
        const q = SELECT.from(Trips).where `beginDate >= ${begin} and endDate <= ${end}`

        // calling TravelService => reusing user permissions
        const availableTrips = await this.run(q)
        return JSON.stringify(availableTrips)
      },
      {
        name: 'tripSearch',
        description: 'Find trips by date range',
        schema: z.object({
          begin: z.iso.date().describe('begin date, format YYYY-MM-DD'),
          end: z.iso.date().describe('end date, format YYYY-MM-DD')
        })
      }
    )

    const agent = createAgent({
      tools: [tripSearch]
    })

    this.on('invokeAgent', async function(req) {
      const { input } = req.data
      const userMessage = new HumanMessage(input)
      const result = await agent.invoke({ messages: [userMessage] })
      return result.messages.at(-1).content
    })

    return super.init()
  }
}

export default TravelService

This code includes the adjusted tripSearch tool and the handler for an invokeAgent action that we can use to interface with the agent.

In srv/travel-service.cds, we define the invokeAgent action, allowing us to call the agent over HTTP.

// srv/travel-service.cds
@rest
service TravelService {
    ...
    action invokeAgent(input: String) returns String;
}

Run the application with

cds w --profile hybrid

The hybrid profile lets us use the environment variables from our earlier cds bind command, so we can call the agent with the correct credentials for SAP AI Core.

To invoke the agent, create a test/http/TravelService.http file using cds add http. Since we chose the rest protocol, we can call our action at /rest/travel/invokeAgent:

# test/http/TravelService.http
@server=http://localhost:4004
@username=alice
@password=

### invokeAgent
# @NAME invokeAgent_POST
POST {{server}}/rest/travel/invokeAgent
Content-Type: application/json
Authorization: Basic {{username}}:{{password}}

{
  "input": "What are exciting trips for this summer? I like to go to the beach"
}

Since we added a middleware logging our tool calls, you should observe the agent calling the tripSearch tool with the appropriate date range for summer and getting the available trips as a response.

[rest] - POST /rest/travel/invokeAgent
[tool:start] tripSearch { begin: '2026-06-01', end: '2026-08-31' }
[tool:end] tripSearch [{"ID":2001,"description":"7-Night Maldives ... }]

The response contains the final answer from the agent, which should include the available trips for this summer.

You can experiment with different queries and see how the agent utilizes the tool to fetch relevant information from our CAP service.

You could also add additional parameters to the tool. For example, you could utilize the description field to search for specific interests like “beach”, “skiing”, or “hiking”. As a simple approach, use a text search, or use vector embeddings with a cosine similarity search. With CAP, this is quite easy to implement: https://cap.cloud.sap/docs/guides/databases/vector-embeddings

Calling the agent in CAP with an action tool (write)

The query tool we implemented is useful to provide the agent with data. However, we can also implement tools that allow an agent to take action. For example, let's implement a tool that allows the agent to book a trip on behalf of the user.

In CAP, a dedicated action allows us to implement the business logic for booking a trip. Earlier, we had already defined the bound action book in our CDS model:

service TravelService {
    @readonly entity Trips as projection on travel.Trips
    actions {
        action book() returns String;
    };
    ...
}

Now we implement the corresponding event handler in srv/travel-service.js.

// srv/travel-service.js
class TravelService extends cds.ApplicationService {
  init() {
    this.on('book', async function(req) {
      const { INSERT } = cds.ql
      const { Bookings } = cds.entities

      const [ trip ] = req.params
      try {
        await INSERT.into(Bookings).entries([{ trip }])
        return `trip with ID ${trip.ID} booked`
      }
      catch (e) {
        if (e.code === 'SQLITE_CONSTRAINT_PRIMARYKEY') return `Could not book trip with ID ${trip?.ID}, it has already been booked`
        else throw e;
      }
    })

    ...
  }
}

As you see, this is a very simple implementation that just inserts a new booking for the given trip. In a real application, you would usually have a multi-step process that checks for availability with the vendor, handles payment and then comes back with a confirmation. But for demonstration purposes, this implementation is sufficient to show the idea and important considerations.

The action itself is registered to the service and available to the user. However, the bookings entity is only exposed to the user as readonly.

@readonly entity Bookings as projection on travel.Bookings where $user = user;

So in the action implementation, we go directly to the database via await INSERT to insert the new booking. This database insert bypasses the service level permission checks while still maintaining the user context.

Recalling our domain model, we automatically insert the user and the booking date:

entity Bookings {
  key trip        : Association to Trips;
  key user        : User @readonly @CDS.on.insert: $user;
      bookingDate : Date default $now;
}

Now that we have implemented the action, we can expose it as a tool for our agent.

// srv/travel-service.js
class TravelService extends cds.ApplicationService {
  init() {
    ...
    const bookTrip = tool(
      async ({ trip }) => {
        const result = await this.send({ event: 'book', entity: 'Trip', data: {}, params: [trip]})
        return JSON.stringify(result)
      },
      {
        name: 'bookTrip',
        description: 'Book a single trip',
        schema: z.object({
          trip: z.object({
            ID: z.string().describe('ID of the trip')
          })
        })
      }
    )
    ...
  }
}

We receive the trip id as a parameter from the model and then call the action via this.send(). This ensures that all the service level logic is applied, including permissions.

Since our agent now has the ability to book a trip, it is also useful to provide a tool for checking existing bookings, so that the agent can verify if a trip has been booked.

// srv/travel-service.js
class TravelService extends cds.ApplicationService {
  init() {
    ...
    const myBookings = tool(
      async () => {
        const { Bookings } = this.entities
        const q = SELECT.from(Bookings).columns `*, trip {ID, description, beginDate, endDate}`
        const bookings = await this.run(q)
        return JSON.stringify(bookings)
      },
      {
        name: 'myBookings',
        description: 'Check the users bookings'
      }
    )
    ...
  }
}

This touches on an important aspect of agent design: providing feedback to the agent about its actions. When an agent takes an action, it should be able to observe the outcome of that action. Not only via the direct response of the action, but also by querying the state of the system.

Now that we have the necessary tools for booking a trip, let's give them to our agent and see how it utilizes them.

// srv/travel-service.js
...
    const agent = createAgent({
      tools: [tripSearch, bookTrip, myBookings]
    })
...
# test/http/TravelService.http
...
### invokeAgent
# @NAME invokeAgent_POST
POST {{server}}/rest/travel/invokeAgent
Content-Type: application/json
Authorization: Basic {{username}}:{{password}}

{
  "input": "Book a trip for me this summer. I like to go to the beach. Don't ask me, just book it."
}

Checking the tool calls, the agent again first searches for available trips in the summer. It then proceeds to simultaneously book two matching trips. Seems like our agent is really eager to go on vacation this summer!

[tool:start] tripSearch { begin: '2026-06-01', end: '2026-08-31' }
[tool:end] tripSearch [{"ID":2001,"description":"7-Night Maldives ... }]
[tool:start] bookTrip { trip: { ID: '2100' } }
[tool:start] bookTrip { trip: { ID: '2001' } }
[tool:end] bookTrip "trip with ID 2100 booked"
[tool:end] bookTrip "trip with ID 2001 booked"

This highlights the importance of careful agent design and implementation of guardrails. Since agents can take actions autonomously, it is crucial to ensure that they do not take unintended actions that could have negative consequences.

There are multiple dials you can use to improve the agent's behavior, such as adjusting the system prompt, providing more specific instructions in the tool descriptions. Play around with these parameters and see how it affects the agent's behavior.

But despite all instructions, there is still a chance that the agent takes unintended actions. So make sure to also implement guardrails such as confirmation steps for critical actions, or a manual review process for high-impact decisions.

What's Next

We have now seen how to implement an agentic system in CAP using the SAP AI SDK and langchain by implementing a simple travel agent that can search for trips and book them on behalf of the user.

CAP integrates nicely with the agentic workflow and allows us to easily expose our services as tools for the agent, while also ensuring that all the necessary service level logic and permissions are applied. Since production qualities like authentication, authorization, and database transactions are handled by CAP, we can focus on the domain.

As an experienced CAP developer you may have noticed… tool calls are simply another protocol for calling CAP services. This means we can also implement an adapter to expose a service as a collection of tools. But that's a story for another time.

Depending on your use case, there are a few advanced topics which may become important:

  • Input and Output: In our implementation, we have a single input and output for each session. If you want to have a conversation with multiple turns, you could keep the agent session and utilize websockets to send messages back and forth. Streaming is another tool to improve the user experience.
  • Structured output: For generating data which can then be given to a technical system, you need to ensure that the output conforms to a specific structure. Langchain already provides corresponding functionality: https://docs.langchain.com/oss/javascript/langchain/structured-output
  • Multi-agent systems: In this example, we have implemented a single agent that can perform multiple tasks. However, in more complex scenarios, you might want to implement multiple agents that specialize in different tasks and can collaborate with each other. Or you want Joule to be able to call your agent. One option is the Agent2Agent (A2A) protocol. There is a blog post on wiring the agent-to-agent protocol into CAP. And as with tools, A2A is just another protocol.

But as always, start simple and grow as you go. So go ahead and implement your own agentic system in CAP, and see how it can transform your applications!

Source link

Leave a Reply

Your email address will not be published. Required fields are marked *