Skip to content

Commit 112ebf7

Browse files
Update: [AEA-5919] - Split reformulation and rag prompts, versioning and refinement (#169)
## Summary - Routine Change ### Details - Update CDK for Prompt Management - Reformulate user prompt for improved user queries (default value for now) - Set system and user prompt for RAG - Set prompt inference for consistent results - Move prompts to .txt files for easier readability - Update slack bot to read prompts and inference from Bedrock - Add tests for prompt retrieval --------- Co-authored-by: Bence Gadanyi <[email protected]>
1 parent a031d7b commit 112ebf7

File tree

15 files changed

+647
-57
lines changed

15 files changed

+647
-57
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Return the user query exactly as provided without any modifications, changes, or reformulations.
2+
Do not alter, rephrase, or modify the input in any way.
3+
Simply return: {{user_query}}
4+
5+
User Query: {{user_query}}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<SystemInstructions>
2+
You are an AI assistant designed to provide helpful information and guidance related to healthcare systems,
3+
data integration and user setup.
4+
5+
<Requirements>
6+
1. Break down the question(s) based on the context
7+
2. Examine the information provided in the question(s) or requirement(s).
8+
3. Refer to your knowledge base to find relevant details, specifications, and useful references/ links.
9+
4. The knowledge base is your source of truth before anything else
10+
5. Acknowledge explicit and implicit evidence
11+
5a. If no explicit evidence is available, state implicit evidence with a caveat
12+
6. Provide critical thinking before replying to make the direction actionable and authoritative
13+
7. Provide a clear and comprehensive answer by drawing inferences,
14+
making logical connections from the available information, comparing previous messages,
15+
and providing users with link and/ or references to follow.
16+
8. Be clear in answers, direct actions are preferred (eg., "Check Postcode" &gt; "Refer to documentation")
17+
</Requirements>
18+
19+
<Constraints>
20+
1. Quotes should be italic
21+
2. Document titles and document section names should be bold
22+
3. If there is a single question, or the user is asking for direction, do not list items
23+
4. If the query has multiple questions *and* the answer includes multiple answers for multiple questions
24+
(as lists or bullet), the list items must be formatted as \`*<question>*
25+
- <answer(s)>\`.
26+
4a. If there are multiple questions in the query, shorten the question to less than 50 characters
27+
</Constraints>
28+
29+
<Output>
30+
- Use Markdown, avoid XML
31+
- Structured, informative, and tailored to the specific context of the question.
32+
- Provide evidence to support results
33+
- Acknowledging any assumptions or limitations in your knowledge or understanding.
34+
- Text structure should be in Markdown
35+
</Output>
36+
37+
<Tone>
38+
Professional, helpful, authoritative.
39+
</Tone>
40+
41+
<Examples>
42+
<Example1>
43+
Q: Should alerts be automated?
44+
A: *Section 1.14.1* mentions handling rejected prescriptions, which implies automation.
45+
</Example1>
46+
</Examples>
47+
</SystemInstructions>
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
- Using your knowledge around the National Health Service (NHS), Electronic Prescription Service (EPS) and the Fast Healthcare Interoperability Resources' (FHIR) onboarding, Supplier Conformance Assessment List (SCAL), APIs, developer guides and error resolution; please answer the following question and cite direct quotes and document sections.
2+
- If my query is asking for instructions (i.e., "How to...", "How do I...") provide step by steps instructions
3+
- Do not provide general advice or external instructions
4+
5+
<SearchResults>$search_results$</SearchResults>
6+
7+
<UserQuery>{{user_query}}</UserQuery>`
Lines changed: 37 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,60 @@
11
import {Construct} from "constructs"
2-
import {BedrockFoundationModel, Prompt, PromptVariant} from "@cdklabs/generative-ai-cdk-constructs/lib/cdk-lib/bedrock"
2+
import {
3+
BedrockFoundationModel,
4+
Prompt,
5+
PromptVariant
6+
} from "@cdklabs/generative-ai-cdk-constructs/lib/cdk-lib/bedrock"
7+
import {BedrockPromptSettings} from "./BedrockPromptSettings"
38

49
export interface BedrockPromptResourcesProps {
510
readonly stackName: string
11+
readonly settings: BedrockPromptSettings
612
}
713

814
export class BedrockPromptResources extends Construct {
915
public readonly queryReformulationPrompt: Prompt
16+
public readonly ragResponsePrompt: Prompt
1017

1118
constructor(scope: Construct, id: string, props: BedrockPromptResourcesProps) {
1219
super(scope, id)
1320

14-
const claudeModel = BedrockFoundationModel.ANTHROPIC_CLAUDE_HAIKU_V1_0
15-
const promptVariant = PromptVariant.text({
21+
const claudeHaikuModel = BedrockFoundationModel.ANTHROPIC_CLAUDE_HAIKU_V1_0
22+
const claudeSonnetModel = BedrockFoundationModel.ANTHROPIC_CLAUDE_SONNET_V1_0
23+
24+
const queryReformulationPromptVariant = PromptVariant.text({
1625
variantName: "default",
17-
model: claudeModel,
26+
model: claudeHaikuModel,
1827
promptVariables: ["topic"],
19-
promptText: `Return the user query exactly as provided without any modifications, changes, or reformulations.
20-
Do not alter, rephrase, or modify the input in any way.
21-
Simply return: {{user_query}}
22-
23-
User Query: {{user_query}}`
28+
promptText: props.settings.reformulationPrompt.text
2429
})
30+
2531
const queryReformulationPrompt = new Prompt(this, "QueryReformulationPrompt", {
2632
promptName: `${props.stackName}-queryReformulation`,
2733
description: "Prompt for reformulating user queries to improve RAG retrieval",
28-
defaultVariant: promptVariant,
29-
variants: [promptVariant]
34+
defaultVariant: queryReformulationPromptVariant,
35+
variants: [queryReformulationPromptVariant]
36+
})
37+
38+
const ragResponsePromptVariant = PromptVariant.chat({
39+
variantName: "default",
40+
model: claudeSonnetModel,
41+
promptVariables: ["query", "search_results"],
42+
system: props.settings.systemPrompt.text,
43+
messages: [props.settings.userPrompt]
44+
})
45+
46+
ragResponsePromptVariant.inferenceConfiguration = {
47+
text: props.settings.inferenceConfig
48+
}
49+
50+
const ragPrompt = new Prompt(this, "ragResponsePrompt", {
51+
promptName: `${props.stackName}-ragResponse`,
52+
description: "Prompt for generating RAG responses with knowledge base context and system instructions",
53+
defaultVariant: ragResponsePromptVariant,
54+
variants: [ragResponsePromptVariant]
3055
})
3156

3257
this.queryReformulationPrompt = queryReformulationPrompt
58+
this.ragResponsePrompt = ragPrompt
3359
}
3460
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import * as fs from "fs"
2+
import {ChatMessage} from "@cdklabs/generative-ai-cdk-constructs/lib/cdk-lib/bedrock"
3+
import {Construct} from "constructs"
4+
import {CfnPrompt} from "aws-cdk-lib/aws-bedrock"
5+
6+
export type BedrockPromptSettingsType = "system" | "user" | "reformulation"
7+
8+
/** BedrockPromptSettings is responsible for loading and providing
9+
* the system, user, and reformulation prompts along with their
10+
* inference configurations.
11+
*/
12+
export class BedrockPromptSettings extends Construct {
13+
public readonly systemPrompt: ChatMessage
14+
public readonly userPrompt: ChatMessage
15+
public readonly reformulationPrompt: ChatMessage
16+
public readonly inferenceConfig: CfnPrompt.PromptModelInferenceConfigurationProperty
17+
18+
/**
19+
* @param scope The Construct scope
20+
* @param id The Construct ID
21+
* @param props BedrockPromptSettingsProps containing optional version info for each prompt
22+
*/
23+
constructor(scope: Construct, id: string) {
24+
super(scope, id)
25+
26+
const systemPromptData = this.getTypedPrompt("system")
27+
this.systemPrompt = ChatMessage.assistant(systemPromptData.text)
28+
29+
const userPromptData = this.getTypedPrompt("user")
30+
this.userPrompt = ChatMessage.user(userPromptData.text)
31+
32+
const reformulationPrompt = this.getTypedPrompt("reformulation")
33+
this.reformulationPrompt = ChatMessage.user(reformulationPrompt.text)
34+
35+
this.inferenceConfig = {
36+
temperature: 0,
37+
topP: 1,
38+
maxTokens: 512,
39+
stopSequences: [
40+
"Human:"
41+
]
42+
}
43+
}
44+
45+
/** Get the latest prompt text from files in the specified directory.
46+
* If a version is provided, it retrieves that specific version.
47+
* Otherwise, it retrieves the latest version based on file naming.
48+
*
49+
* @param type The type of prompt (system, user, reformulation)
50+
* @returns An object containing the prompt text and filename
51+
*/
52+
private getTypedPrompt(type: BedrockPromptSettingsType)
53+
: { text: string; filename: string } {
54+
// Read all files in the folder)
55+
const path = __dirname + "/../prompts"
56+
const files = fs
57+
.readdirSync(path)
58+
59+
if (files.length === 0) {
60+
throw new Error("No variant files found in the folder.")
61+
}
62+
63+
const file = files.find(file => file.startsWith(`${type}Prompt`))!
64+
65+
if (!file) {
66+
throw new Error(`No prompt file found for type: ${type}`)
67+
}
68+
69+
const text = fs.readFileSync(`${path}/${file}`, "utf-8")
70+
71+
return {text, filename: file}
72+
}
73+
}

packages/cdk/resources/Functions.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import {TableV2} from "aws-cdk-lib/aws-dynamodb"
99
const RAG_MODEL_ID = "anthropic.claude-3-sonnet-20240229-v1:0"
1010
// Claude model for query reformulation
1111
const QUERY_REFORMULATION_MODEL_ID = "anthropic.claude-3-haiku-20240307-v1:0"
12-
const QUERY_REFORMULATION_PROMPT_VERSION = "DRAFT"
1312
const BEDROCK_KB_DATA_SOURCE = "eps-assist-kb-ds"
1413
const LAMBDA_MEMORY_SIZE = "265"
1514

@@ -33,7 +32,10 @@ export interface FunctionsProps {
3332
readonly slackBotTokenSecret: Secret
3433
readonly slackBotSigningSecret: Secret
3534
readonly slackBotStateTable: TableV2
36-
readonly promptName: string
35+
readonly reformulationPromptName: string
36+
readonly ragResponsePromptName: string
37+
readonly reformulationPromptVersion: string
38+
readonly ragResponsePromptVersion: string
3739
readonly isPullRequest: boolean
3840
readonly mainSlackBotLambdaExecutionRoleArn : string
3941
}
@@ -66,8 +68,10 @@ export class Functions extends Construct {
6668
"GUARD_RAIL_ID": props.guardrailId,
6769
"GUARD_RAIL_VERSION": props.guardrailVersion,
6870
"SLACK_BOT_STATE_TABLE": props.slackBotStateTable.tableName,
69-
"QUERY_REFORMULATION_PROMPT_NAME": props.promptName,
70-
"QUERY_REFORMULATION_PROMPT_VERSION": QUERY_REFORMULATION_PROMPT_VERSION
71+
"QUERY_REFORMULATION_PROMPT_NAME": props.reformulationPromptName,
72+
"RAG_RESPONSE_PROMPT_NAME": props.ragResponsePromptName,
73+
"QUERY_REFORMULATION_PROMPT_VERSION": props.reformulationPromptVersion,
74+
"RAG_RESPONSE_PROMPT_VERSION": props.ragResponsePromptVersion
7175
}
7276
})
7377

packages/cdk/stacks/EpsAssistMeStack.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {BedrockPromptResources} from "../resources/BedrockPromptResources"
1919
import {S3LambdaNotification} from "../constructs/S3LambdaNotification"
2020
import {VectorIndex} from "../resources/VectorIndex"
2121
import {ManagedPolicy, PolicyStatement, Role} from "aws-cdk-lib/aws-iam"
22+
import {BedrockPromptSettings} from "../resources/BedrockPromptSettings"
2223

2324
export interface EpsAssistMeStackProps extends StackProps {
2425
readonly stackName: string
@@ -66,9 +67,13 @@ export class EpsAssistMeStack extends Stack {
6667
stackName: props.stackName
6768
})
6869

70+
// Create Bedrock Prompt Collection
71+
const bedrockPromptCollection = new BedrockPromptSettings(this, "BedrockPromptCollection")
72+
6973
// Create Bedrock Prompt Resources
7074
const bedrockPromptResources = new BedrockPromptResources(this, "BedrockPromptResources", {
71-
stackName: props.stackName
75+
stackName: props.stackName,
76+
settings: bedrockPromptCollection
7277
})
7378

7479
// Create Storage construct first as it has no dependencies
@@ -95,7 +100,8 @@ export class EpsAssistMeStack extends Stack {
95100
stackName: props.stackName,
96101
collection: openSearchResources.collection
97102
})
98-
// this dependency ensures the OpenSearch access policy is created before the VectorIndex
103+
104+
// This dependency ensures the OpenSearch access policy is created before the VectorIndex
99105
// and deleted after the VectorIndex is deleted to prevent deletion or deployment failures
100106
vectorIndex.node.addDependency(openSearchResources.deploymentPolicy)
101107

@@ -147,7 +153,10 @@ export class EpsAssistMeStack extends Stack {
147153
slackBotTokenSecret: secrets.slackBotTokenSecret,
148154
slackBotSigningSecret: secrets.slackBotSigningSecret,
149155
slackBotStateTable: tables.slackBotStateTable.table,
150-
promptName: bedrockPromptResources.queryReformulationPrompt.promptName,
156+
reformulationPromptName: bedrockPromptResources.queryReformulationPrompt.promptName,
157+
ragResponsePromptName: bedrockPromptResources.ragResponsePrompt.promptName,
158+
reformulationPromptVersion: bedrockPromptResources.queryReformulationPrompt.promptVersion,
159+
ragResponsePromptVersion: bedrockPromptResources.ragResponsePrompt.promptVersion,
151160
isPullRequest: isPullRequest,
152161
mainSlackBotLambdaExecutionRoleArn: mainSlackBotLambdaExecutionRoleArn
153162
})

packages/slackBotFunction/app/core/config.py

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
Sets up all the AWS and Slack connections we need.
44
"""
55

6+
from __future__ import annotations
67
from dataclasses import dataclass
78
from functools import lru_cache
89
import os
@@ -71,18 +72,29 @@ def get_bot_token() -> str:
7172

7273

7374
@lru_cache
74-
def get_guardrail_config() -> Tuple[str, str, str, str, str]:
75+
def get_retrieve_generate_config() -> BedrockConfig:
7576
# Bedrock configuration from environment
7677
KNOWLEDGEBASE_ID = os.environ["KNOWLEDGEBASE_ID"]
7778
RAG_MODEL_ID = os.environ["RAG_MODEL_ID"]
7879
AWS_REGION = os.environ["AWS_REGION"]
7980
GUARD_RAIL_ID = os.environ["GUARD_RAIL_ID"]
8081
GUARD_VERSION = os.environ["GUARD_RAIL_VERSION"]
82+
RAG_RESPONSE_PROMPT_NAME = os.environ["RAG_RESPONSE_PROMPT_NAME"]
83+
RAG_RESPONSE_PROMPT_VERSION = os.environ["RAG_RESPONSE_PROMPT_VERSION"]
8184

8285
logger.info(
8386
"Guardrail configuration loaded", extra={"guardrail_id": GUARD_RAIL_ID, "guardrail_version": GUARD_VERSION}
8487
)
85-
return KNOWLEDGEBASE_ID, RAG_MODEL_ID, AWS_REGION, GUARD_RAIL_ID, GUARD_VERSION
88+
89+
return BedrockConfig(
90+
KNOWLEDGEBASE_ID,
91+
RAG_MODEL_ID,
92+
AWS_REGION,
93+
GUARD_RAIL_ID,
94+
GUARD_VERSION,
95+
RAG_RESPONSE_PROMPT_NAME,
96+
RAG_RESPONSE_PROMPT_VERSION,
97+
)
8698

8799

88100
@dataclass
@@ -127,6 +139,17 @@ class Constants:
127139
)
128140

129141

142+
@dataclass
143+
class BedrockConfig:
144+
KNOWLEDGEBASE_ID: str
145+
RAG_MODEL_ID: str
146+
AWS_REGION: str
147+
GUARD_RAIL_ID: str
148+
GUARD_VERSION: str
149+
RAG_RESPONSE_PROMPT_NAME: str
150+
RAG_RESPONSE_PROMPT_VERSION: str
151+
152+
130153
@dataclass
131154
class BotMessages:
132155
EMPTY_QUERY: str

0 commit comments

Comments
 (0)