-
Notifications
You must be signed in to change notification settings - Fork 1
Enhance Module 6: Improve Task Instructions and Code Examples #19
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
d96d954
7803a6a
93dd88f
7e0f2d1
b893bcd
abbda57
80d2a48
f348405
7cf7960
bf9df31
2ef1752
f87a9c2
8f81279
83ff7c3
4651e9f
8fbd9f6
047d4bf
b942676
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -375,14 +375,45 @@ Return an instance of `auth0AI.withAsyncUserConfirmation` that: | |
|
|
||
| 3. Add the custom scope (aka 'permission') we created earlier to the existing scopes array. | ||
|
|
||
| 4. Ensure the <kbd>userID</kbd> parameter is a <mark>promise</mark> that returns the userβs ID. | ||
| > [!TIP] | ||
| > **Scope Configuration Best Practices:** | ||
| > | ||
| > - Always include baseline OIDC scopes: `openid`, `profile`, `email` | ||
| > - Add custom API scopes like `create:transfer` for specific permissions | ||
| > - In production, consider loading scopes from environment variables: | ||
| > ```typescript | ||
| > const scopes = process.env.AUTH0_API_SCOPES?.split(',') || ['openid', 'profile', 'email']; | ||
| > ``` | ||
| > - Scopes should match exactly what you configured in the Auth0 API | ||
|
|
||
| ====== Move to 1. (line 371) and adjust numbering accordingly. ===== | ||
| 1. Ensure the necessary imports have been added to the file: | ||
| ```typescript | ||
| import type { AuthorizerToolParameter, TokenSet } from '@auth0/ai'; | ||
| import { | ||
| AccessDeniedInterrupt, | ||
| CIBAInterrupt, | ||
| UserDoesNotHavePushNotificationsInterrupt, | ||
| } from '@auth0/ai/interrupts'; | ||
|
|
||
| 4. **Update the Guard Condition**: Change the return statement in the guard to return the tool instead of nothing: | ||
| ```typescript | ||
| if (!auth0AI) { | ||
| console.warn('Auth0AI client not initialized!'); | ||
| return tool; // Instead of just 'return;' | ||
| } | ||
| ``` | ||
|
|
||
| i.e. <kbd>getUser</kbd> β <kbd>user.sub</kbd> | ||
| 5. **Add the Custom Scope**: Update the scopes array to include the transfer permission: | ||
| ```typescript | ||
| const scopes = ['openid', 'profile', 'email', 'create:transfer']; | ||
| ``` | ||
|
|
||
| 5. Insert the <kbd>audience</kbd> value we created earlier. | ||
|
|
||
| 6. Enhance <kbd>onAuthorizationRequest</kbd> by plugging in our custom <kbd>handleOnAuthorize</kbd> helper function. | ||
|
|
||
|
|
||
| <br> | ||
|
|
||
| > [!NOTE] | ||
|
|
@@ -399,23 +430,13 @@ Return an instance of `auth0AI.withAsyncUserConfirmation` that: | |
|
|
||
| <br> | ||
|
|
||
| 7. Ensure any errors (i.e. from `onUnauthorized`) are *normalized*. | ||
|
|
||
| This callback provides a lot of opportunity to improve the UX even further. This is where you *could* handle cases such as where the user *denies* authorization. | ||
|
|
||
| The <kbd>handleOnAuthorize</kbd> is meant to provide you with a *pattern* you can use to differentiate between denial, missing enrollment, or generic errors. | ||
|
|
||
| The SDK provides normalized error codes to make this easier. For example: | ||
| - <kbd>AccessDeniedInterrupt</kbd> | ||
| - <kbd>UserDoesNotHavePushNotificationsInterrupt</kbd> | ||
|
|
||
| *Check out the SDK types (`node_modules/@auth0/ai/dist/esm/interrupts/CIBAInterrupts.d.ts`) for more.* | ||
|
|
||
| <br> | ||
|
|
||
| > [!TIP] | ||
| > **Advanced Error Handling Options:** | ||
| > | ||
| > *Not sure what to do here?* | ||
| > *Not sure what to do here?* | ||
| > | ||
| > This wrapper returns to a *tool*, which then returns to the *streaming function*. | ||
| > | ||
|
|
@@ -435,12 +456,6 @@ Return an instance of `auth0AI.withAsyncUserConfirmation` that: | |
| - // ...options, /** π β Step 8: The Auth0AI wrapper spreads the same options as our wrapper! TypeScript interface to the rescue? π§ */ | ||
| + ...options, | ||
|
|
||
| 9. Lastly, make sure the tool being wrapped is *actually injected*! | ||
| ```diff | ||
| - // })(tool) /** β Step 9: Don't forget to inject the `tool` being wrapped! */; | ||
| + })(tool); | ||
| ``` | ||
|
Comment on lines
-438
to
-442
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Keep |
||
|
|
||
| --- | ||
| #### <span style="font-variant: small-caps">Congrats!</span> | ||
| *You have completed Task 6.* | ||
|
|
@@ -472,12 +487,18 @@ This will be accomplished by requiring Aiya to fetch a *fresh and ephemeral* acc | |
|
|
||
| <span style="font-variant: small-caps; font-weight: 700">Setup</span> | ||
|
|
||
| - From your code editor, open `lib/auth0/ai/transfer-funds.ts`. | ||
| - From your code editor, open `lib/ai/tools/transfer-funds.ts`. | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ππ» this has already been updated in the demo platform version. This version is slightly outdated. |
||
|
|
||
| <span style="font-variant: small-caps; font-weight: 700">Steps</span> | ||
|
|
||
| 1. Wrap <kbd>transferFunds</kbd> with <kbd>withAsyncAuthorization</kbd>. | ||
| 1. Ensure the necessary imports have been added to the file: | ||
| ```typescript | ||
| import { withAsyncAuthorization } from '@/lib/auth0/ai/with-async-authorization'; | ||
| import { getCIBACredentials } from '@auth0/ai-vercel'; | ||
| import { tool, type UIMessageStreamWriter } from 'ai'; | ||
| ``` | ||
|
|
||
| 2. Wrap <kbd>transferFunds</kbd> with <kbd>withAsyncAuthorization</kbd>. | ||
| You will need to: | ||
| - import the function from the `lib/auth0/ai` directory. | ||
| - *wrap the tool* -- instead of simply returning it, pass it as the <kbd>tool</kbd> parameter of <kbd>withAsyncAuthorization</kbd>. | ||
|
|
@@ -486,26 +507,46 @@ This will be accomplished by requiring Aiya to fetch a *fresh and ephemeral* acc | |
|
|
||
| <br> | ||
|
|
||
| > [!TIP] | ||
| > **Higher-Order Function Pattern:** | ||
| > | ||
| > This creates a "factory function" that: | ||
| > - Accepts an optional `writer` parameter for streaming updates | ||
| > - Returns a wrapped tool with authorization capabilities | ||
| > - Maintains the original tool's TypeScript interface | ||
| > | ||
| > **Why this pattern?** It allows the same tool to be used with or without streaming capabilities while maintaining type safety. | ||
|
|
||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ππ» I am in favor. There was a similar tip in the original version but it ended up getting removed.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am not sure how we missed the fact that 7 and 8 are redundant. I think originally they were combined. I'm going to refactor them entirely and incorporate some of your wording suggestions. |
||
| 3. Be sure to pass <kbd>bindingMessage</kbd> to <kbd>withAsyncAuthorization</kbd>: | ||
|
|
||
| > [!NOTE] | ||
| > | ||
| > ***What is <kbd>bindingMessage</kbd>?*** | ||
| > | ||
| > When using the Auth0 Guardian SDK this message can be displayed to the user in order to provide context about the request. It is *not* used in our demo but still required. | ||
|
|
||
| <br> | ||
|
|
||
| 2. Import <kbd>getCIBACredentials</kbd> and use it to retrieve an <kbd>accessToken</kbd> to be sent in the <kbd>Authorization</kbd> header of the API call. | ||
| 4. In **step one** you imported <kbd>getCIBACredentials</kbd>. Now use it to retrieve an <kbd>accessToken</kbd>. Ensure the token is sent in the tool's <kbd>fetch</kbd> call as the <kbd>Authorization</kbd> header | ||
|
|
||
| 3. Update the tool <kbd>description</kbd>. | ||
| - Right now the tool states to always require confirmation. However, we are implementing confirmation via push notification so having Aiya confirm first would be very annoying. | ||
| - Read the <kbd>description</kbd> and modify the instructions so Aiya *never* asks for confirmation. | ||
|
|
||
| <br> | ||
| <br> | ||
|
|
||
| > [!TIP] | ||
| > When instructing <abbr title='large language models'>LLMs</abbr> be explicit but concise. | ||
| > [!TIP] | ||
| > **Understanding CIBA Credentials:** | ||
| > | ||
| > - `getCIBACredentials()` returns the fresh access token obtained through the CIBA flow | ||
| > - This token contains the `create:transfer` scope we configured earlier | ||
| > - The token is ephemeral and specific to this authorization request | ||
| > - Always use optional chaining (`?.`) when accessing token properties for safety | ||
|
|
||
| 5. **Update the Tool Description**: Change the description to remove the manual confirmation requirement: | ||
| Change the following text to explicitly **not** require confirmation. There is no "right" way to word this -- you decide. | ||
| ``` | ||
| Always confirm the details of the transfer with the user before continuing. | ||
| ``` | ||
|
|
||
| <br> | ||
| > [!TIP] | ||
| > Instructing an <abbr title='large language models'>LLM</abbr> is like training someone to perform the task. Sometimes adding an explanation as to *why* the model should do/not do something can be helpful (i.e. *the user will be getting a push notification instead*). | ||
| > | ||
| > When instructing <abbr title='large language models'>LLMs</abbr> be explicit but concise. | ||
|
|
||
| --- | ||
| #### <span style="font-variant: small-caps">Congrats!</span> | ||
|
|
@@ -523,14 +564,12 @@ Feel free to give it a try, just know you'll be missing out on a better UX! ***W | |
|
|
||
| <br> | ||
|
|
||
| ## Task 8: Enhance the UX | ||
| ## Task 8: Enhance the UX (Higher-Order Function) | ||
|
|
||
| #### <span style="font-variant: small-caps">Goal</span> | ||
| To enhance the user's experience we need a way to *inject* the streaming writer into `withAsyncAuthorization` so the authorization portion of the flow (where the push notification gets sent) can stream *status messages* to the chat UI. | ||
|
|
||
| *The plain exported tool has no place to accept that writer.* | ||
| In Task 7, you already transformed `transferFunds` into a higher-order function that accepts a `writer` parameter. This allows us to inject the streaming writer into `withAsyncAuthorization` so the authorization flow can stream status messages to the chat UI. | ||
|
|
||
| Although this is *not* a *requirement* to enable the core feature functionality, it sure does make for a better user experience! | ||
| *This pattern enables real-time progress updates during the authorization process.* | ||
|
|
||
| #### <span style="font-variant: small-caps"><em>What</em> are we doing?</span> | ||
|
|
||
|
|
@@ -626,23 +665,12 @@ Without being able to stream message updates, the user is left with a loading in | |
|
|
||
| ***How?*** | ||
|
|
||
| A βhigher-order factory functionβ (*of course*)! We need to βwrap the wrapperβ. | ||
|
|
||
| It's easy. Just create a function that *returns* the wrapped tool. | ||
|
|
||
| <br> | ||
|
|
||
| > [!TIP] | ||
| > | ||
| > We will give you a hint... | ||
| > | ||
| > | ||
| > ```diff | ||
| > - export const transferFunds = /* β TASK 7 - STEP 1: */ withAsyncAuthorization({ | ||
| > + export const transferFunds = /* β TASK 8 */ () => withAsyncAuthorization({... | ||
| > ``` | ||
| This was already accomplished in Task 7! By transforming the export to: | ||
| ```typescript | ||
| export const transferFunds = (writer?: UIMessageStreamWriter) => withAsyncAuthorization({... | ||
| ``` | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Will refactor all of this. |
||
|
|
||
| <br> | ||
| You created a higher-order function that accepts a `writer` parameter and returns the authorized tool. | ||
|
|
||
| --- | ||
| #### <span style="font-variant: small-caps">Congrats!</span> | ||
|
|
@@ -675,20 +703,32 @@ In the previous task we transformed <kbd>transfer-funds</kbd> into a higher-orde | |
| <span style="font-variant: small-caps; font-weight: 700">Steps</span> | ||
|
|
||
| 1. Open `lib/ai/tool-registry.ts`. | ||
| 2. Simply change <kbd>transferFunds</kbd> β <kbd>transferFunds()</kbd>. π | ||
| ```diff | ||
| - transferFunds | ||
| + transferFunds() | ||
| 2. Find the line with `transferFunds` and update it to call the function: | ||
| ```ts | ||
| - transferFunds /* β οΈ TASK 9: Modify to call higher-order function (see `transfer-funds.ts`) */, | ||
| + transferFunds: transferFunds(), // call the higher-order function to obtain the tool | ||
| ``` | ||
|
|
||
| <br> | ||
|
|
||
| > [!TIP] | ||
| > **Tool Registry Patterns:** | ||
| > | ||
| > - **Simple tools**: `toolName` (direct reference) | ||
| > - **Factory functions**: `toolName()` (function call) | ||
| > - **Parameterized tools**: `toolName(config)` (with configuration) | ||
| > | ||
| > The registry calls `transferFunds()` without arguments, using the optional writer parameter pattern. | ||
|
|
||
| ***But wait, there's more!*** | ||
|
|
||
| 3. Open ```app/(chat)/api/chat/[id]/_handlers/post.ts``` | ||
| 4. Scroll to **line 81** and ***uncomment*** ```transferFunds()```. You *might* see a red squiggly, *this can be ignored* (for now). ```transferFunds()``` does not require an arg to compile and run. The writer arg is *optional* | ||
| ```diff | ||
| - // transferFunds: transferFunds(), | ||
| + transferFunds: transferFunds(), | ||
| 3. Open `app/(chat)/api/chat/[id]/_handlers/post.ts` | ||
| 4. Find the commented line in the initial tools setup and **remove** the comment: | ||
| ```ts | ||
| - // transferFunds: transferFunds(), /* β οΈ TASK 9 */ | ||
| + transferFunds: transferFunds(), /* β οΈ TASK 9 */ | ||
| ``` | ||
| **Note:** This registers the tool without a writer initially, which is fine since the writer parameter is optional. | ||
|
|
||
| ***But, where is the datastream writer? I thought we were injecting it?*** | ||
|
|
||
|
|
@@ -698,14 +738,34 @@ Once the datastream writer is available, in the actual <kbd>createUIMessageStrea | |
|
|
||
| Nifty trick, *right*? π€ | ||
|
|
||
| 5. Speaking of that, let's do it now! Scroll to **line 110** and ***uncomment*** ```transferFunds: transferFunds(dataStream)```, and remove the line above it. | ||
| 5. Now find the section where tools are re-initialized with the dataStream writer (around line 110). **Uncomment** the line with `dataStream` and **comment out** the line above it: | ||
|
|
||
| ```diff | ||
| ```ts | ||
| - transferFunds, | ||
| - // transferFunds: transferFunds(dataStream), | ||
| + transferFunds: transferFunds(dataStream), | ||
| - // transferFunds: transferFunds(dataStream), /* β οΈ TASK 9 */ | ||
| + // transferFunds, | ||
| + transferFunds: transferFunds(dataStream), /* β οΈ TASK 9 */ | ||
| ``` | ||
|
|
||
| **Why both registrations?** | ||
| - First registration: Makes the tool available to the AI system initially (without writer) | ||
| - Second registration: Re-initializes the tool with the dataStream writer for real-time updates | ||
|
|
||
| <br> | ||
|
|
||
| > [!TIP] | ||
| > **Dual Registration Pattern:** | ||
| > | ||
| > This pattern is useful for tools that need streaming capabilities: | ||
| > | ||
| > 1. **Initial registration**: `toolName()` - Basic tool availability | ||
| > 2. **Enhanced registration**: `toolName(writer)` - With streaming capabilities | ||
| > | ||
| > **Alternative approaches:** | ||
| > - Use dependency injection patterns | ||
| > - Implement lazy initialization within the tool | ||
| > - Create separate streaming and non-streaming variants | ||
|
|
||
| > [!NOTE] | ||
| > | ||
| > The code in the <kbd>POST</kbd> function is on the ***advanced*** side of the Vercel SDK implementation. | ||
|
|
@@ -726,26 +786,48 @@ Only one more to go... | |
|
|
||
| <br> | ||
|
|
||
| ## Task 10: Try **it** | ||
| ## Task 10: Test the Implementation | ||
|
|
||
| #### <span style="font-variant: small-caps">Steps</span> | ||
|
|
||
| 1. Stop the application and restart it again with: | ||
| 1. **Restart the Application**: If the application is running, restart it to pick up all changes: | ||
|
|
||
| ```bash | ||
| npm run dev | ||
| ``` | ||
|
|
||
| 2. Navigate to [http://localhost:3000](http://localhost:3000) | ||
| 2. **Navigate to the Application**: Open [http://localhost:3000](http://localhost:3000) | ||
|
|
||
| 3. Ask the Aiya to transfer funds from your checking to savings, (*or whichever accounts you would like*). | ||
| 3. **Start a New Chat**: Click the "+" button to create a new chat session. | ||
|
|
||
| 4. **Test the Transfer**: Ask Aiya to transfer funds, for example: | ||
| ``` | ||
| Transfer $25 from checking to savings. | ||
| Transfer $25 from checking to savings | ||
| ``` | ||
|
|
||
|  | ||
|
|
||
| <br> | ||
|
|
||
| > [!TIP] | ||
| > **Troubleshooting Common Issues:** | ||
| > | ||
| > **If transfers fail:** | ||
| > - Check browser console for error messages | ||
| > - Verify Auth0 API configuration matches your audience URL | ||
| > - Ensure CIBA is enabled in your Auth0 application | ||
| > - Confirm push notifications are enabled in Auth0 Dashboard | ||
| > | ||
| > **If push notifications don't arrive:** | ||
| > - Check Guardian app enrollment status | ||
| > - Verify device has internet connectivity | ||
| > - Look for notifications in Auth0 Dashboard logs | ||
| > | ||
| > **If authorization hangs:** | ||
| > - Check network connectivity | ||
| > - Verify Auth0 API audience configuration | ||
| > - Ensure correct scopes are configured | ||
|
|
||
| 4. If you are ***NOT*** already enrolled in push notifications, you will be prompted to enroll. | ||
|
|
||
|  | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jansinger what would the benefit be of returning the tool in this situation? Are you suggesting we modify the behavior so that transferFunds() works correctly without any authorization?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@eatplaysleep this avoids a type error in tool-registry.ts, see screenshot. You could change the ToolSet type to prevent this as well.
