Last weekend, while attending an AWS West Africa Meetup on "AI-Powered Innovation", I heard about Amazon Bedrock for the first time in a way that piqued my interest. Amazon Bedrock is a "fully managed service that offers a choice of high-performing foundation models (FMs) from leading AI companies". In simpler terms, it allows you build generative AI applications, with easy access to FMs like ChatGPT and Claude via an API.
I immediately knew I had to try it out, and I decided to build a Retrieval Augmented Generation (RAG) agent to recommend Fantasy Premier League (FPL) transfers. FPL is a game of strategy, data, and smart decision-making. Deciding which players to transfer in and out of their team each week is basically the biggest challenge for FPL managers.
Here's the repo, where I plan to keep evolving the agent, and a live demo.
A RAG agent is simply an AI agent with retrieval capability, allowing it to have access to current and accurate data. In this example, I'll build an agent with access to the latest FPL rules and selection guidelines. For now, the recommendation options would be given to it manually in the prompt.
You will:
- Set up a rules knowledge base for your agent
- Store player data and query it for recommendations
- Get your agent to recommend the best possible transfers
Setting Up
After setting up your project (in this example, I am using TypeScript & Express.js), you can go ahead and set up the necessary AWS infrastructure. These are:
- Amazon Bedrock: for accessing FMs and building agents
- S3: for storing knowledge base resources. There are other options, such as Confluence pages and crawled web pages.
- DynamoDB (specific to this option): for easy storage and retrieval of player data
DynamoDB
- Create a DynamoDB table
FPLPlayers
with a primary key ofplayerId
. Then, add a global secondary index (GSI) with a partition key ofposition
and a sort key ofform
. This would allow you to easily query players in top form.
Amazon Bedrock
- Request access to an Amazon Bedrock LLM. For this, I chose Claude 3 Sonnet, but you will have access to almost 50 others.
Once the model is available, it's time to set up your knowledge base. Create an S3 bucket and upload your documents there. For me, this was a PDF containing the rules for FPL transfers and team selections. When that is done, from the AWS Bedrock console, create a new Knowledge Base and select the path to the S3 bucket in the settings.
Next, select the embeddings model and the vector store. I used Titan Text Embeddings v2 and Amazon OpenSearch Serverless Vector Store, respectively, as they are great options.
Getting Data
We can set up some methods for our FPL data. First, we need to add some types.
export interface Main {
elements: Player[];
}
export interface Player {
id: number;
web_name: string;
element_type: number;
team: number;
now_cost: number;
total_points: number;
selected_by_percent: string;
form: string;
status: string;
expected_goals_conceded: string;
minutes: number;
team_code: number;
chance_of_playing_next_round: number;
}
export interface FPLTeam {
picks: {
element: number,
element_type: number;
name?: string;
}[];
transfers: {
bank: number;
limit: number;
};
}
export interface TransferSuggestion {
out: string;
in: string;
cost: number;
}
Then, we store players on DynamoDB. I added the next fixture difficulty using data from the FPL API. I also added a job to call this endpoint daily, making sure the data is up-to-date:
And retrieve them for recommendation options:
As the project evolves, we can store more statistics to enable the agent to make better decisions. But for now, we'll rely on the player's form, price, and the difficulty of the next fixture.
Let's set up our agent to make decisions using the Knowledge Base. On the AWS console, create a new agent. Select your Foundational Model,
and enter precise instructions for your agent to follow.
Then, create an Action Group. After you create the agent, you can assign a Knowledge Base and prepare the agent. After that, you can create an Alias for your agent. Note down your Agent ID and the Alias ID.
Now, we can call our agent. First, I get the user's team data (players, free transfers left, and budget) and top recommendation (players with a form equal to or greater than a set value). With those, I can create a prompt, which is then sent to my agent.
import {
BedrockAgentRuntimeClient,
InvokeAgentCommand,
} from "@aws-sdk/client-bedrock-agent-runtime";
import config from './config';
import {getRecommendationData} from "./dynamo";
const client = new BedrockAgentRuntimeClient({
region: config.awsRegion,
credentials: {
accessKeyId: config.awsAccessKey,
secretAccessKey: config.awsSecretKey,
}
});
const agentId = config.awsAgentId;
const agentAliasId = config.awsAgentAliasId;
export async function invokeBedrock(modelId: string, prompt: string) {
const sessionId = Math.random().toString(36).substring(2);
const command = new InvokeAgentCommand({
agentId,
agentAliasId,
sessionId,
inputText: prompt,
streamingConfigurations: {
streamFinalResponse: true,
},
});
let recommendations = "";
const response = await client.send(command);
if (response.completion === undefined) {
throw new Error("Completion is undefined");
}
for await (const chunkEvent of response.completion!) {
const chunk = chunkEvent.chunk!;
const decodedResponse = new TextDecoder("utf-8").decode(chunk.bytes);
recommendations += decodedResponse;
}
return recommendations;
}
/**
* API function to get FPL advice.
*/
export async function getFPLAdvice(teamId: number, cookie: string): Promise {
const {userPlayers, recommendations, freeTransfers, budget} = await getRecommendationData(teamId, cookie);
const prompt = `
The user’s current team is:\n
${userPlayers.map((p) => `- ${p.name} (Position: ${p.position}, Team: ${p.team}) (Form: ${p.form}, Price: £${p.price}, Next Fixture Difficulty: ${p.nextFixtureDifficulty})`).join(`\n`)}.\n
The user has only ${freeTransfers} free transfers left, and a budget of ${budget}.
Based on the current gameweek, the best transfer options are:\n
${recommendations.map((p) => `- ${p.name} (Position: ${p.position}, Team: ${p.team}) (Form: ${p.form}, Price: £${p.price}, Next Fixture Difficulty: ${p.nextFixtureDifficulty})`).join(`\n`)}.
Return nothing else but the recommendations in a readable format, clearly showing the player going out and the player coming in for each suggested transfer. For example:
Out: [Player Name from Current Team]
In: [Player Name from Best Transfer Options]
Cost: [Cost to make transfer, should be negative if out > in]
`;
return await invokeBedrock(
"anthropic.claude-3-sonnet-20240229-v1:0",
prompt
);
}
And that's it! We can connect this function to an endpoint; calling it should give us some recommendations.
But there's always room for improvement. We can add more to our Knowledge Base or even build more agents to handle other tasks, allowing multi-agent orchestration.
As you can see, the ability to build agents without almost no code and the value they can deliver makes Amazon Bedrock a powerful tool for utilising AI modes.
I hope you found this helpful and that you start building powerful RAG agents to supercharge your ideas. Happy coding!