Skip to content

Commit c6932be

Browse files
Adding AI conversation section (#8094)
Co-authored-by: Ian Saultz <[email protected]>
1 parent 1892b12 commit c6932be

File tree

7 files changed

+952
-1
lines changed

7 files changed

+952
-1
lines changed

cspell.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1613,7 +1613,8 @@
16131613
"voteField",
16141614
"ampx",
16151615
"autodetection",
1616-
"jamba"
1616+
"jamba",
1617+
"knowledgebases"
16171618
],
16181619
"flagWords": ["hte", "full-stack", "Full-stack", "Full-Stack", "sudo"],
16191620
"patterns": [
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
import { getCustomStaticPath } from "@/utils/getCustomStaticPath";
2+
3+
export const meta = {
4+
title: "Context",
5+
description:
6+
"How to pass client-side context to the LLM to help it respond.",
7+
platforms: [
8+
"javascript",
9+
"react-native",
10+
"angular",
11+
"nextjs",
12+
"react",
13+
"vue",
14+
],
15+
};
16+
17+
export const getStaticPaths = async () => {
18+
return getCustomStaticPath(meta.platforms);
19+
};
20+
21+
export function getStaticProps(context) {
22+
return {
23+
props: {
24+
platform: context.params.platform,
25+
meta,
26+
showBreadcrumbs: false,
27+
},
28+
};
29+
}
30+
31+
32+
33+
For LLMs to provide high-quality answers to users' questions, they need to have the right information. Sometimes this information is contextual, based on the user or the state of the application. To allow for this, you can send `aiContext` with any user message to the LLM, which can be any unstructured or structured data that might be useful.
34+
35+
<InlineFilter filters={["javascript","vue","angular"]}>
36+
37+
```ts
38+
import { generateClient } from "aws-amplify/data";
39+
import type { Schema } from "../amplify/data/resource";
40+
41+
const client = generateClient<Schema>({ authMode: 'userPool' });
42+
43+
const { data: conversation } = await client.conversations.chat.create();
44+
45+
conversation.sendMessage({
46+
content: [{ text: "hello" }],
47+
// aiContext can be any shape
48+
aiContext: {
49+
username: "danny"
50+
}
51+
})
52+
```
53+
54+
</InlineFilter>
55+
56+
57+
<InlineFilter filters={["react-native"]}>
58+
59+
```tsx
60+
export default function Chat() {
61+
const [
62+
{
63+
data: { messages },
64+
isLoading,
65+
},
66+
sendMessage,
67+
] = useAIConversation('chat');
68+
69+
function handleSendMessage(message) {
70+
sendMessage({
71+
...message,
72+
// this can be any object that can be stringified
73+
aiContext: {
74+
currentTime: new Date().toLocaleTimeString()
75+
}
76+
})
77+
}
78+
79+
return (
80+
//...
81+
)
82+
}
83+
```
84+
85+
</InlineFilter>
86+
87+
88+
<InlineFilter filters={["react", "nextjs"]}>
89+
90+
```tsx
91+
function Chat() {
92+
const [
93+
{
94+
data: { messages },
95+
isLoading,
96+
},
97+
sendMessage,
98+
] = useAIConversation('chat');
99+
100+
return (
101+
<AIConversation
102+
messages={messages}
103+
isLoading={isLoading}
104+
handleSendMessage={sendMessage}
105+
// This will let the LLM know about the current state of this application
106+
// so it can better respond to questions
107+
aiContext={() => {
108+
return {
109+
currentTime: new Date().toLocaleTimeString(),
110+
};
111+
}}
112+
/>
113+
);
114+
}
115+
```
116+
117+
118+
The function passed to the `aiContext` prop will be run immediately before the request is sent in order to get the most up to date information.
119+
120+
You can use React context or other state management systems to update the data passed to `aiContext`. Using React context we can provide more information about the current state of the application:
121+
122+
```tsx
123+
// Create a context to share state across components
124+
const DataContext = React.createContext<{
125+
data: any;
126+
setData: (value: React.SetStateAction<any>) => void;
127+
}>({ data: {}, setData: () => {} });
128+
129+
// Create a component that updates the shared state
130+
function Counter() {
131+
const { data, setData } = React.useContext(AIContext);
132+
const count = data.count ?? 0;
133+
return (
134+
<Button onClick={() => setData({ ...data, count: count + 1 })}>
135+
{count}
136+
</Button>
137+
);
138+
}
139+
140+
// reference shared data in aiContext
141+
function Chat() {
142+
const { data } = React.useContext(DataContext);
143+
const [
144+
{
145+
data: { messages },
146+
isLoading,
147+
},
148+
sendMessage,
149+
] = useAIConversation('pirateChat');
150+
151+
return (
152+
<AIConversation
153+
messages={messages}
154+
isLoading={isLoading}
155+
handleSendMessage={sendMessage}
156+
// This will let the LLM know about the current state of this application
157+
// so it can better respond to questions
158+
aiContext={() => {
159+
return {
160+
...data,
161+
currentTime: new Date().toLocaleTimeString(),
162+
};
163+
}}
164+
/>
165+
);
166+
}
167+
168+
export default function Example() {
169+
const [data, setData] = React.useState({});
170+
return (
171+
<Authenticator>
172+
<DataContext.Provider value={{ data, setData }}>
173+
<Counter />
174+
<Chat />
175+
</DataContext.Provider>
176+
</Authenticator>
177+
)
178+
}
179+
```
180+
181+
182+
</InlineFilter>
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import { getCustomStaticPath } from "@/utils/getCustomStaticPath";
2+
3+
export const meta = {
4+
title: "Conversation History",
5+
description:
6+
"Learn how Amplify AI kit takes care of conversation history",
7+
platforms: [
8+
"javascript",
9+
"react-native",
10+
"angular",
11+
"nextjs",
12+
"react",
13+
"vue",
14+
],
15+
};
16+
17+
export const getStaticPaths = async () => {
18+
return getCustomStaticPath(meta.platforms);
19+
};
20+
21+
export function getStaticProps(context) {
22+
return {
23+
props: {
24+
platform: context.params.platform,
25+
meta,
26+
showBreadcrumbs: false,
27+
},
28+
};
29+
}
30+
31+
The Amplify AI kit automatically and securely stores conversation history per user so you can easily resume past conversations.
32+
33+
<Callout>
34+
35+
If you are looking for a quick way to get stared with conversation history, [this example project](https://github.com/aws-samples/amplify-ai-examples/tree/main/claude-ai) has a similar interface to ChatGPT or Claude where users see past conversations in a sidebar they can manage.
36+
37+
</Callout>
38+
39+
When you define a conversation route in your Amplify data schema, the Amplify AI kit turns that into 2 data models: `Conversation` and `Message`. The `Conversation` model functions mostly the same way as other data models defined in your schema. You can list and filter them (because they use owner-based authorization users will only see their conversations) and you can get a specific conversation by ID. Then once you have a conversation instance you can load the messages in it if there are any, send messages to it, and subscribe to the stream events being sent back.
40+
41+
42+
## Listing conversations
43+
44+
To list all the conversations a user has you can use the `.list()` method. It works the same way as any other Amplify data model would. You can optionally pass a `limit` or `nextToken`.
45+
46+
```ts
47+
const { data: conversations } = await client.conversations.chat.list()
48+
```
49+
50+
The `updatedAt` field gets updated when new messages are sent, so you can use that to see which conversation had the most recent message. Conversations retrieved via `.list()` are sorted in descending order by `updatedAt`.
51+
52+
### Pagination
53+
The result of `.list()` contains a `nextToken` property. This can be used to retrieve subsequent pages of conversations.
54+
55+
```ts
56+
const { data: conversations, nextToken } = await client.conversations.chat.list();
57+
58+
// retrieve next page
59+
if (nextToken) {
60+
const { data: nextPageConversations } = await client.conversations.chat.list({
61+
nextToken
62+
});
63+
}
64+
```
65+
66+
Conversations also have `name` and `metadata` fields you can use to more easily find and resume past conversations. `name` is a string and `metadata` is a JSON object so you can store any extra information you need.
67+
68+
## Resuming conversations
69+
70+
You can resume a conversation by calling the `.get()` method with a conversation ID. Both `.create()` and `.get()` return the a conversation instance.
71+
72+
<InlineFilter filters={['javascript','vue','angular']}>
73+
74+
```ts
75+
// list all conversations a user has
76+
// make sure the user has been authenticated with Amplify Auth
77+
const conversationList = await client.conversations.conversation.list();
78+
79+
// Retrieve a specific conversation
80+
const { data: conversation } = await client.conversations.chat.get({ id: conversationList[0].id });
81+
82+
// list the existing messages in the conversation
83+
const { data: messages } = await conversation.listMessages();
84+
85+
// You can now send a message to the conversation
86+
conversation.sendMessage({
87+
content: [
88+
{text: "hello"}
89+
]
90+
})
91+
```
92+
93+
</InlineFilter>
94+
95+
<InlineFilter filters={['react','nextjs','react-native']}>
96+
97+
```tsx
98+
export function Chat({ id }) {
99+
const [
100+
data: { messages }
101+
handleSendMessage,
102+
] = useAIConversation('chat', { id })
103+
}
104+
```
105+
106+
</InlineFilter>
107+
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import { getChildPageNodes } from '@/utils/getChildPageNodes';
2+
import { getCustomStaticPath } from "@/utils/getCustomStaticPath";
3+
4+
export const meta = {
5+
title: "Conversation",
6+
description:
7+
"Learn about conversational AI patterns and how to implement them in Amplify.",
8+
route: '/[platform]/ai/conversation',
9+
platforms: [
10+
"javascript",
11+
"react-native",
12+
"angular",
13+
"nextjs",
14+
"react",
15+
"vue",
16+
],
17+
};
18+
19+
export const getStaticPaths = async () => {
20+
return getCustomStaticPath(meta.platforms);
21+
};
22+
23+
export function getStaticProps(context) {
24+
const childPageNodes = getChildPageNodes(meta.route);
25+
return {
26+
props: {
27+
meta,
28+
childPageNodes,
29+
showBreadcrumbs: false,
30+
}
31+
};
32+
}
33+
34+
35+
The conversation route simplifies the creation of AI-powered conversation interfaces in your application. It automatically sets up the necessary AppSync API components and Lambda functions to handle streaming multi-turn interactions with Amazon Bedrock foundation models.
36+
37+
## Key Components
38+
39+
1. **AppSync API**: Gateway to the conversation route.
40+
- Create new conversation route instance.
41+
- Send messages to conversation route instance.
42+
- Subscribe to real-time updates for assistant responses.
43+
44+
2. **Lambda Function**: Bridge between AppSync and Amazon Bedrock.
45+
- Retrieve conversation instance history.
46+
- Invokes Bedrock's /converse endpoint.
47+
- Handles tool use responses by invoking AppSync queries.
48+
49+
3. **DynamoDB**: Stores conversation and message data
50+
- Conversations are scoped to a specific application user.
51+
52+
## Authentication Flow
53+
54+
1. The user's OIDC access token is passed from the client to AppSync
55+
2. AppSync forwards this token to the Lambda function
56+
3. The Lambda function uses the token to authenticate requests back to AppSync
57+
58+
## Usage Scenarios
59+
60+
Each of the following scenarios have safeguards in place to mitigate risks associated with invoking tools on behalf of the user, including:
61+
62+
- Amazon CloudWatch log group redacting OIDC access tokens for logs from the Lambda function.
63+
- IAM policies that limit the Lambda function's ability to access other resources.
64+
65+
66+
## Data Flow
67+
68+
1. User sends a message via the AppSync mutation
69+
2. AppSync triggers the Lambda function (default or custom)
70+
3. Lambda processes the message and invokes Bedrock's /converse endpoint
71+
a. If response is a tool use, Lambda function invokes applicable AppSync query.
72+
4. Lambda sends assistant response back to AppSync
73+
5. AppSync sends the response to subscribed clients
74+
75+
This design allows for real-time, scalable conversations while ensuring that the Lambda function's data access matches that of the application user.
76+
77+
## Next Steps
78+
79+
<Overview childPageNodes={props.childPageNodes} />

0 commit comments

Comments
 (0)