In the previous exercise, you built a basic agent that could reason and respond using an LLM. Now you'll extend it with a tool that allows the agent to call external services and work with structured data.
In this exercise, you will add an input variable and a tool to your agent. You will give the agent the SAP-RPT-1 model as a tool to make predictions on structured data.
SAP-RPT-1 SAP's Relational Pretrained Transformer model is a foundation model trained on structured data. It is available in the Generative AI Hub to gain predictive insights from enterprise data without building models from scratch. The model works by uploading a couple of example data rows as .json or .csv files and can do classification as well as regression predictions on your dataset.
👉 Open the SAP-RPT-1 Playground. Use one of the example files from the playground to understand how the model works.
In order for the agent to run predictions on actual data, you need to be able to pass input data.
👉 Navigate to your basic_agent.py file.
👉 Update the agent's goal and task description to reference the call_rpt1 tool and a payload variable. Add {payload} to the strings and convert them to f-strings (formatted strings) by adding f to the beginning.
Make the following changes to the agent and task defintion:
# Create a Loss Appraiser Agent
appraiser_agent = Agent(
role="Loss Appraiser",
goal=f"Predict the missing values of stolen items using the RPT-1 model via the call_rpt1 tool use this payload {payload} as input.",
backstory="You are an expert insurance appraiser specializing in fine art valuation and theft assessment.",
llm="sap/gpt-4o", # provider/llm - Using one of the models from SAP's model library in Generative AI Hub
verbose=True
)
# Create a task for the appraiser
appraise_loss_task = Task(
description=f"Analyze the theft crime scene and predict the missing values of stolen items using the RPT-1 model via the call_rpt1 tool. Use this dict {payload} as input.",
expected_output="JSON with predicted values for the stolen items.",
agent=appraiser_agent
)Now we need to create the payload variable that we referenced in the agent's goal. Instead of cluttering our main file, we'll keep the data in a separate file for better organization.
👉 Create a new file called payload.py in the starter-project folder.
👉 Add the payload data to this file:
# Payload data for stolen items appraisal
payload = {
"prediction_config": {
"target_columns": [
{
"name": "INSURANCE_VALUE",
"prediction_placeholder": "'[PREDICT]'",
"task_type": "regression",
},
{
"name": "ITEM_CATEGORY",
"prediction_placeholder": "'[PREDICT]'",
"task_type": "classification",
},
]
},
"index_column": "ITEM_ID",
"rows": [
{
"ITEM_ID": "ART_001",
"ITEM_NAME": "Water Lilies - Series 1",
"ARTIST": "Claude Monet",
"ACQUISITION_DATE": "1987-03-15",
"INSURANCE_VALUE": 45000000,
"ITEM_CATEGORY": "Painting",
"DIMENSIONS": "200x180cm",
"CONDITION_SCORE": 9,
"RARITY_SCORE": 9,
"PROVENANCE_CLARITY": 8,
},
{
"ITEM_ID": "ART_002",
"ITEM_NAME": "Japanese Bridge at Giverny",
"ARTIST": "Claude Monet",
"ACQUISITION_DATE": "1995-06-22",
"INSURANCE_VALUE": 42000000,
"ITEM_CATEGORY": "Painting",
"DIMENSIONS": "92x73cm",
"CONDITION_SCORE": 8,
"RARITY_SCORE": 8,
"PROVENANCE_CLARITY": 9,
},
{
"ITEM_ID": "ART_003",
"ITEM_NAME": "Irises",
"ARTIST": "Vincent van Gogh",
"ACQUISITION_DATE": "2001-11-08",
"INSURANCE_VALUE": "'[PREDICT]'",
"ITEM_CATEGORY": "Painting",
"DIMENSIONS": "71x93cm",
"CONDITION_SCORE": 7,
"RARITY_SCORE": 9,
"PROVENANCE_CLARITY": 8,
},
{
"ITEM_ID": "ART_004",
"ITEM_NAME": "Starry Night Over the Rhone",
"ARTIST": "Vincent van Gogh",
"ACQUISITION_DATE": "1998-09-14",
"INSURANCE_VALUE": 48000000,
"ITEM_CATEGORY": "Painting",
"DIMENSIONS": "73x92cm",
"CONDITION_SCORE": 8,
"RARITY_SCORE": 9,
"PROVENANCE_CLARITY": 9,
},
{
"ITEM_ID": "ART_005",
"ITEM_NAME": "The Birth of Venus",
"ARTIST": "Sandro Botticelli",
"ACQUISITION_DATE": "1992-04-30",
"INSURANCE_VALUE": 55000000,
"ITEM_CATEGORY": "Painting",
"DIMENSIONS": "172x278cm",
"CONDITION_SCORE": 6,
"RARITY_SCORE": 10,
"PROVENANCE_CLARITY": 10,
},
{
"ITEM_ID": "ART_006",
"ITEM_NAME": "Primavera",
"ARTIST": "Sandro Botticelli",
"ACQUISITION_DATE": "1989-02-19",
"INSURANCE_VALUE": 52000000,
"ITEM_CATEGORY": "Painting",
"DIMENSIONS": "203x314cm",
"CONDITION_SCORE": 7,
"RARITY_SCORE": 10,
"PROVENANCE_CLARITY": 10,
},
{
"ITEM_ID": "ART_007",
"ITEM_NAME": "Girl with a Pearl Earring",
"ARTIST": "Johannes Vermeer",
"ACQUISITION_DATE": "2003-07-11",
"INSURANCE_VALUE": "'[PREDICT]'",
"ITEM_CATEGORY": "Painting",
"DIMENSIONS": "44x39cm",
"CONDITION_SCORE": 8,
"RARITY_SCORE": 10,
"PROVENANCE_CLARITY": 9,
},
{
"ITEM_ID": "ART_008",
"ITEM_NAME": "The Music Lesson",
"ARTIST": "Johannes Vermeer",
"ACQUISITION_DATE": "1994-05-20",
"INSURANCE_VALUE": 38000000,
"ITEM_CATEGORY": "Painting",
"DIMENSIONS": "64x73cm",
"CONDITION_SCORE": 8,
"RARITY_SCORE": 9,
"PROVENANCE_CLARITY": 9,
},
{
"ITEM_ID": "ART_009",
"ITEM_NAME": "The Persistence of Memory",
"ARTIST": "Salvador Dalí",
"ACQUISITION_DATE": "2005-03-10",
"INSURANCE_VALUE": 35000000,
"ITEM_CATEGORY": "'[PREDICT]'",
"DIMENSIONS": "24x33cm",
"CONDITION_SCORE": 9,
"RARITY_SCORE": 9,
"PROVENANCE_CLARITY": 10,
},
{
"ITEM_ID": "ART_010",
"ITEM_NAME": "Metamorphosis of Narcissus",
"ARTIST": "Salvador Dalí",
"ACQUISITION_DATE": "1996-08-12",
"INSURANCE_VALUE": 32000000,
"ITEM_CATEGORY": "Painting",
"DIMENSIONS": "51x78cm",
"CONDITION_SCORE": 8,
"RARITY_SCORE": 8,
"PROVENANCE_CLARITY": 8,
},
{
"ITEM_ID": "ART_011",
"ITEM_NAME": "The Bronze Dancer",
"ARTIST": "Auguste Rodin",
"ACQUISITION_DATE": "1991-07-22",
"INSURANCE_VALUE": 8500000,
"ITEM_CATEGORY": "Sculpture",
"DIMENSIONS": "Height: 1.8m",
"CONDITION_SCORE": 9,
"RARITY_SCORE": 7,
"PROVENANCE_CLARITY": 8,
},
{
"ITEM_ID": "ART_012",
"ITEM_NAME": "The Thinker",
"ARTIST": "Auguste Rodin",
"ACQUISITION_DATE": "2000-11-05",
"INSURANCE_VALUE": "'[PREDICT]'",
"ITEM_CATEGORY": "Sculpture",
"DIMENSIONS": "Height: 1.9m",
"CONDITION_SCORE": 9,
"RARITY_SCORE": 7,
"PROVENANCE_CLARITY": 9,
},
{
"ITEM_ID": "ART_013",
"ITEM_NAME": "Hope Diamond Replica - Royal Cut",
"ARTIST": "Unknown Jeweler",
"ACQUISITION_DATE": "1988-02-19",
"INSURANCE_VALUE": 12000000,
"ITEM_CATEGORY": "Jewelry",
"DIMENSIONS": "Width: 15cm",
"CONDITION_SCORE": 10,
"RARITY_SCORE": 10,
"PROVENANCE_CLARITY": 7,
},
{
"ITEM_ID": "ART_014",
"ITEM_NAME": "Cartier Ruby Necklace - 1920s",
"ARTIST": "Cartier",
"ACQUISITION_DATE": "2002-09-11",
"INSURANCE_VALUE": 9500000,
"ITEM_CATEGORY": "Jewelry",
"DIMENSIONS": "Length: 45cm",
"CONDITION_SCORE": 9,
"RARITY_SCORE": 8,
"PROVENANCE_CLARITY": 9,
},
],
"data_schema": {
"ITEM_ID": {"dtype": "string"},
"ITEM_NAME": {"dtype": "string"},
"ARTIST": {"dtype": "string"},
"ACQUISITION_DATE": {"dtype": "date"},
"INSURANCE_VALUE": {"dtype": "numeric"},
"ITEM_CATEGORY": {"dtype": "string", "categories": ["Painting", "Sculpture", "Jewelry"]},
"DIMENSIONS": {"dtype": "string"},
"CONDITION_SCORE": {"dtype": "numeric", "range": [1, 10], "description": "1=Poor to 10=Pristine"},
"RARITY_SCORE": {"dtype": "numeric", "range": [1, 10], "description": "1=Common to 10=Extremely Rare"},
"PROVENANCE_CLARITY": {"dtype": "numeric", "range": [1, 10], "description": "1=Unknown to 10=Perfect Documentation"},
},
}👉 Now update your basic_agent.py file to import the payload from this new file. Add this import at the top:
from payload import payload👉 Pass your input to the crew
# Execute the crew
def main():
result = crew.kickoff(inputs={'payload': payload})
print("\n" + "="*50)
print("Insurance Appraiser Report:")
print("="*50)
print(result)
if __name__ == "__main__":
main()👉 Run your crew to test it.
☝️ Make sure you're in the repository root directory (e.g.,
codejam-code-based-agents) when running this command. If you're already in thestarter-projectfolder, usepython basic_agent.pyinstead.
python project/Python/starter-project/basic_agent.py☝️ You added an input variable to your agent but the agent is still not using a tool. Let's build the actual tool next.
SAP-RPT-1 will be added to the SAP Cloud SDK for AI but for now we will build our own client.
👉 Create a new file /project/Python/starter-project/rpt_client.py
👉 Add the code below:
import os
import requests
class RPT1Client:
def __init__(self):
# Read env vars (assume dotenv already loaded in main)
self.client_id = os.getenv("AICORE_CLIENT_ID")
self.client_secret = os.getenv("AICORE_CLIENT_SECRET")
self.auth_url = os.getenv("AICORE_AUTH_URL")
self.deployment_url = os.getenv("RPT1_DEPLOYMENT_URL")
self.token = self._fetch_token()
self.resource_group = os.getenv("AICORE_RESOURCE_GROUP", "default")
# Function to fetch OAuth token
def _fetch_token(self, timeout: int = 30) -> str:
if not self.auth_url:
raise ValueError("AICORE_AUTH_URL must be provided (env or arg).")
if not self.client_id:
raise ValueError("AICORE_CLIENT_ID must be provided (env or arg).")
if not self.client_secret:
raise ValueError("AICORE_CLIENT_SECRET must be provided (env or arg).")
data = {
"grant_type": "client_credentials",
"client_id": self.client_id,
"client_secret": self.client_secret
}
headers = {"Content-Type": "application/x-www-form-urlencoded"}
resp = requests.post(self.auth_url, data=data, headers=headers, timeout=timeout)
resp.raise_for_status()
token = resp.json()
access_token = token["access_token"]
return access_token
def post_request(self, json_payload: dict, timeout: int = 60):
headers = {
"Authorization": f"Bearer {self.token}",
"Content-Type": "application/json",
"AI-Resource-Group": self.resource_group
}
# Send the POST request to the deployment URL
response = requests.post(
self.deployment_url, json=json_payload, headers=headers
)
return responseTo add a custom tool to an agent, you create a function that encapsulates the functionality you want to expose. This function will be available for the agent to call when completing its task.
👉 Import the SAP-RPT-1 client at the top of your basic_agent.py file:
from rpt_client import RPT1Client
⚠️ Important: Make sure you initialize theRPT1Client()after loading the.envfile withload_dotenv(), otherwise the environment variables won't be available. Your initialization should look like this:
# Load .env from the same directory as this script
env_path = Path(__file__).parent / '.env'
load_dotenv(dotenv_path=env_path)
# Initialize RPT1 client after loading environment variables
rpt1_client = RPT1Client()👉 Add this code above your agent definition:
def call_rpt1(payload: dict) -> str:
"""Function to call RPT-1 model via RPT1Client"""
response = rpt1_client.post_request(json_payload=payload)
if response.status_code == 200:
return response.json()
else:
return f"Error: {response.status_code} - {response.text}"👉 Add the following line of codeto your import section at the top of your basic_agent.py file:
from crewai.tools import tool👉 Add the following line of code above your call_rpt1() function:
@tool("call_rpt1")The function should look like this now:
@tool("call_rpt1")
def call_rpt1(payload: dict) -> str:
"""Call RPT-1 model to predict missing values in the payload.
Args:
payload: A dictionary containing the stolen items data with prediction placeholders.
This should be the exact payload provided in the task inputs.
Returns:
JSON string with predicted insurance values and item categories.
"""
try:
response = rpt1_client.post_request(json_payload=payload)
if response.status_code == 200:
import json
return json.dumps(response.json(), indent=2)
else:
return f"Error {response.status_code}: {response.text}"
except Exception as e:
return f"Error calling RPT-1: {str(e)}"👉 Add the following line of code to your agent definition:
tools=[call_rpt1],Your agent defintion should look like this now:
# Create a Loss Appraiser Agent
appraiser_agent = Agent(
role="Loss Appraiser",
goal=f"Predict the missing values of stolen items using the RPT-1 model via the call_rpt1 tool use this payload {payload} as input.",
backstory="You are an expert insurance appraiser specializing in fine art valuation and theft assessment.",
llm="sap/gpt-4o", # provider/llm - Using one of the models from SAP's model library in Generative AI Hub
tools=[call_rpt1],
verbose=True
)👉 Your code in basic_agent.py should now look like this:
import os
from pathlib import Path
from dotenv import load_dotenv
from crewai import Agent, Task, Crew
from crewai.tools import tool
from rpt_client import RPT1Client
from payload import payload
# Load .env from the same directory as this script
env_path = Path(__file__).parent / '.env'
load_dotenv(dotenv_path=env_path)
# Initialize RPT1 client after loading environment variables
rpt1_client = RPT1Client()
@tool("call_rpt1")
def call_rpt1(payload: dict) -> str:
"""Function to call RPT-1 model via RPT1Client"""
response = rpt1_client.post_request(json_payload=payload)
if response.status_code == 200:
return response.json()
else:
return f"Error: {response.status_code} - {response.text}"
# Create a Loss Appraiser Agent
appraiser_agent = Agent(
role="Loss Appraiser",
goal=f"Predict the missing values of stolen items using the RPT-1 model via the call_rpt1 tool use this payload {payload} as input.",
backstory="You are an expert insurance appraiser specializing in fine art valuation and theft assessment.",
llm="sap/gpt-4o", # provider/llm - Using one of the models from SAP's model library in Generative AI Hub
tools=[call_rpt1],
verbose=True
)
# Create a task for the appraiser
appraise_loss_task = Task(
description=f"Analyze the theft crime scene and predict the missing values of stolen items using the RPT-1 model via the call_rpt1 tool. Use this dict {payload} as input.",
expected_output="JSON with predicted values for the stolen items.",
agent=appraiser_agent
)
# Create a crew with the appraiser agent
crew = Crew(
agents=[appraiser_agent],
tasks=[appraise_loss_task],
verbose=True
)
# Execute the crew
def main():
result = crew.kickoff(inputs={'payload': payload})
print("\n" + "="*50)
print("Insurance Appraiser Report:")
print("="*50)
print(result)
if __name__ == "__main__":
main()👉 Go to SAP AI Launchpad
☝️ In this subaccount the connection between the SAP AI Core service instance and the SAP AI Launchpad application is already established. Otherwise you would have to add a new AI runtime using the SAP AI Core service key information.
DO NOT USE THE
defaultRESOURCE GROUP!
👉 Go to Workspaces.
👉 Select your workspace (like codejam-aicore-connection) and your resource group ai-agents-codejam.
👉 Make sure it is set as a context. The proper name of the context, like codejam-aicore-connection (ai-agents-codejam) should show up at the top next to SAP AI Launchpad.
👉 Navigate to ML Operations > Deployments > sap-rpt-1-large_autogenerated
👉 Copy the ID of the SAP-RPT-1 deployment and paste it into the .env file.
RPT1_DEPLOYMENT_URL="https://api.ai.prod.eu-central-1.aws.ml.hana.ondemand.com/v2/inference/deployments/<ID GOES HERE>/predict"👉 Run your crew to test it.
☝️ Make sure you're in the repository root directory (e.g.,
codejam-code-based-agents) when running this command. If you're already in thestarter-projectfolder, usepython basic_agent.pyinstead.
python project/Python/starter-project/basic_agent.py👉 Understand the output of the agent using SAP-RPT-1 as a tool.
SAP-RPT-1 not only predicts missing values with the [PREDICT] placeholder but also returns a confidence score for classification tasks, indicating how confident the model is in its predictions.
You extended your agent with:
- A custom tool function decorated with
@tool()that encapsulates external functionality - Tool assignment by passing
tools=[call_rpt1]to the agent, making it available for use - Tool invocation in the agent's task description, allowing the LLM to decide when and how to use it
Agent Task → LLM Reasoning → Tool Decision → Tool Execution → Result → Agent Processing → LLM Reasoning → Output
Tools are essential for agents to:
- Access External APIs and services (like the RPT-1 model)
- Perform Real Actions beyond text generation
- Provide Grounded Responses based on actual data and computations
- Enable Autonomous Operation by expanding the agent's capabilities
- Tools extend agent capabilities beyond pure reasoning—they enable actions and external integrations
- Decorators like
@tool()transform functions into CrewAI-compatible tools with descriptions - Tool Assignment is crucial—agents only have access to tools explicitly passed in the
toolsparameter - Tool Availability should be reflected in the agent's goal and task descriptions so the LLM knows they exist
- Custom Clients like
RPT1Clientencapsulate API interactions, keeping tool functions clean and focused
In the following exercises, you will:
- ✅ Build a basic agent
- ✅ Add custom tools to your agents so they can access external data (this exercise)
- 📌 Create a complete crew with multiple agents working together
- 📌 Integrate the Grounding Service for better reasoning and fact-checking
- 📌 Solve the museum art theft mystery using your fully-featured agent team
Issue: AttributeError: 'Agent' object has no attribute 'tools' or tool is not being called
- Solution: Ensure you've added
tools=[call_rpt1]to your Agent definition. Without this, the agent won't have access to the tool.
Issue: Tool not found or agent ignores the tool
- Solution: Verify that:
- The
@tool()decorator is above the function definition - The tool is assigned to the agent via
tools=[call_rpt1] - Your task description mentions the tool so the LLM knows to use it
- The
Issue: ModuleNotFoundError: No module named 'rpt_client'
- Solution: Ensure you've created the
rpt_client.pyfile in/project/Python/starter-project/and that you're in the correct directory when running the script.
Issue: Authentication error calling RPT-1
- Solution: Verify your
.envfile contains valid credentials:AICORE_CLIENT_IDAICORE_CLIENT_SECRETAICORE_AUTH_URLRPT1_DEPLOYMENT_URLAICORE_RESOURCE_GROUP(make sure you set it to ai-agents-codejam)
Issue: Tool returns error 400 or 422
- Solution: Verify your payload structure matches the RPT-1 API specification. Check the SAP-RPT-1 Playground for valid payload examples.