diff --git a/docs/source/_static/bedrock-chat-basemodel-arn.png b/docs/source/_static/bedrock-chat-basemodel-arn.png new file mode 100644 index 000000000..fbd0457f5 Binary files /dev/null and b/docs/source/_static/bedrock-chat-basemodel-arn.png differ diff --git a/docs/source/_static/bedrock-chat-basemodel-modelid.png b/docs/source/_static/bedrock-chat-basemodel-modelid.png new file mode 100644 index 000000000..e930edb3c Binary files /dev/null and b/docs/source/_static/bedrock-chat-basemodel-modelid.png differ diff --git a/docs/source/_static/bedrock-chat-basemodel.png b/docs/source/_static/bedrock-chat-basemodel.png new file mode 100644 index 000000000..5dd261a4a Binary files /dev/null and b/docs/source/_static/bedrock-chat-basemodel.png differ diff --git a/docs/source/_static/bedrock-chat-custom-model-arn.png b/docs/source/_static/bedrock-chat-custom-model-arn.png new file mode 100644 index 000000000..111fc7cce Binary files /dev/null and b/docs/source/_static/bedrock-chat-custom-model-arn.png differ diff --git a/docs/source/_static/bedrock-custom-models.png b/docs/source/_static/bedrock-custom-models.png new file mode 100644 index 000000000..86b67d1ac Binary files /dev/null and b/docs/source/_static/bedrock-custom-models.png differ diff --git a/docs/source/_static/bedrock-finetuned-model.png b/docs/source/_static/bedrock-finetuned-model.png new file mode 100644 index 000000000..4687e33e6 Binary files /dev/null and b/docs/source/_static/bedrock-finetuned-model.png differ diff --git a/docs/source/_static/bedrock-model-access.png b/docs/source/_static/bedrock-model-access.png new file mode 100644 index 000000000..b3325d484 Binary files /dev/null and b/docs/source/_static/bedrock-model-access.png differ diff --git a/docs/source/_static/bedrock-model-select.png b/docs/source/_static/bedrock-model-select.png new file mode 100644 index 000000000..a321c52cf Binary files /dev/null and b/docs/source/_static/bedrock-model-select.png differ diff --git a/docs/source/conf.py b/docs/source/conf.py index 190b7e0a2..142d47b78 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -57,3 +57,5 @@ }, ], } + +html_sidebars = {"**": []} diff --git a/docs/source/developers/index.md b/docs/source/developers/index.md index 6763486dc..644dc0a4e 100644 --- a/docs/source/developers/index.md +++ b/docs/source/developers/index.md @@ -391,3 +391,50 @@ custom = "custom_package:CustomChatHandler" Then, install your package so that Jupyter AI adds custom chat handlers to the existing chat handlers. + +## Custom message footer + +You can provide a custom message footer that will be rendered under each message +in the UI. To do so, you need to write or install a labextension containing a +plugin that provides the `IJaiMessageFooter` token. This plugin should return a +`IJaiMessageFooter` object, which defines the custom footer to be rendered. + +The `IJaiMessageFooter` object contains a single property `component`, which +should reference a React component that defines the custom message footer. +Jupyter AI will render this component under each chat message, passing the +component a `message` prop with the definition of each chat message as an +object. The `message` prop takes the type `AiService.ChatMessage`, where +`AiService` is imported from `@jupyter-ai/core/handler`. + +Here is a reference plugin that shows some custom text under each agent message: + +```tsx +import React from 'react'; +import { + JupyterFrontEnd, + JupyterFrontEndPlugin +} from '@jupyterlab/application'; +import { IJaiMessageFooter, IJaiMessageFooterProps } from '@jupyter-ai/core/tokens'; + +export const footerPlugin: JupyterFrontEndPlugin = { + id: '@your-org/your-package:custom-footer', + autoStart: true, + requires: [], + provides: IJaiMessageFooter, + activate: (app: JupyterFrontEnd): IJaiMessageFooter => { + return { + component: MessageFooter + }; + } +}; + +function MessageFooter(props: IJaiMessageFooterProps) { + if (props.message.type !== 'agent' && props.message.type !== 'agent-stream') { + return null; + } + + return ( +
This is a test footer that renders under each agent message.
+ ); +} +``` diff --git a/docs/source/users/bedrock.md b/docs/source/users/bedrock.md new file mode 100644 index 000000000..558bf93c9 --- /dev/null +++ b/docs/source/users/bedrock.md @@ -0,0 +1,60 @@ +# Using Amazon Bedrock with Jupyter AI + +[(Return to Chat Interface page for Bedrock)](index.md#amazon-bedrock-usage) + +Bedrock supports many language model providers such as AI21 Labs, Amazon, Anthropic, Cohere, Meta, and Mistral AI. To use the base models from any supported provider make sure to enable them in Amazon Bedrock by using the AWS console. Go to Amazon Bedrock and select `Model Access` as shown here: + +Screenshot of the left panel in the AWS console where Bedrock model access is provided. + +Click through on `Model Access` and follow the instructions to grant access to the models you wish to use, as shown below. Make sure to accept the end user license (EULA) as required by each model. You may need your system administrator to grant access to your account if you do not have authority to do so. + +Screenshot of the Bedrock console where models may be selected. + +You should also select embedding models in addition to language completion models if you intend to use retrieval augmented generation (RAG) on your documents. + +You may now select a chosen Bedrock model from the drop-down menu box title `Completion model` in the chat interface. If RAG is going to be used then pick an embedding model that you chose from the Bedrock models as well. An example of these selections is shown below: + +Screenshot of the Jupyter AI chat panel where the base language model and embedding model is selected. + +Bedrock also allows custom models to be trained from scratch or fine-tuned from a base model. Jupyter AI enables a custom model to be called in the chat panel using its `arn` (Amazon Resource Name). As with custom models, you can also call a base model by its `model id` or its `arn`. An example of using a base model with its `model id` through the custom model interface is shown below: + +Screenshot of the Jupyter AI chat panel where the base model is selected using model id. + +An example of using a base model using its `arn` through the custom model interface is shown below: + +Screenshot of the Jupyter AI chat panel where the base model is selected using its ARN. + +To train a custom model in Amazon Bedrock, select `Custom models` in the Bedrock console as shown below, and then you may customize a base model by fine-tuning it or continuing to pre-train it: + +Screenshot of the Bedrock custom models access in the left panel of the Bedrock console. + +For details on fine-tuning a base model from Bedrock, see this [reference](https://aws.amazon.com/blogs/aws/customize-models-in-amazon-bedrock-with-your-own-data-using-fine-tuning-and-continued-pre-training/); with related [documentation](https://docs.aws.amazon.com/bedrock/latest/userguide/custom-models.html). + +Once the model is fine-tuned, it will have its own `arn`, as shown below: + +Screenshot of the Bedrock fine-tuned model ARN in the Bedrock console. + +As seen above, you may click on `Purchase provisioned throughput` to buy inference units with which to call the custom model's API. Enter the model's `arn` in Jupyter AI's Language model user interface to use the provisioned model. + +[(Return to Chat Interface page for Bedrock)](index.md#amazon-bedrock-usage) diff --git a/docs/source/users/index.md b/docs/source/users/index.md index bb660c882..a57afbde7 100644 --- a/docs/source/users/index.md +++ b/docs/source/users/index.md @@ -175,11 +175,7 @@ Jupyter AI supports the following model providers: The environment variable names shown above are also the names of the settings keys used when setting up the chat interface. If multiple variables are listed for a provider, **all** must be specified. -To use the Bedrock models, you need access to the Bedrock service. For more information, see the -[Amazon Bedrock Homepage](https://aws.amazon.com/bedrock/). - -To use Bedrock models, you will need to authenticate via -[boto3](https://github.com/boto/boto3). +To use the Bedrock models, you need access to the Bedrock service, and you will need to authenticate via [boto3](https://github.com/boto/boto3). For more information, see the [Amazon Bedrock Homepage](https://aws.amazon.com/bedrock/). You need the `pillow` Python package to use Hugging Face Hub's text-to-image models. @@ -273,6 +269,34 @@ The chat backend remembers the last two exchanges in your conversation and passe alt='Screen shot of an example follow up question sent to Jupyternaut, who responds with the improved code and explanation.' class="screenshot" /> + +### Amazon Bedrock Usage + +Jupyter AI enables use of language models hosted on [Amazon Bedrock](https://aws.amazon.com/bedrock/) on AWS. First, ensure that you have authentication to use AWS using the `boto3` SDK with credentials stored in the `default` profile. Guidance on how to do this can be found in the [`boto3` documentation](https://boto3.amazonaws.com/v1/documentation/api/latest/guide/credentials.html). + +For more detailed workflows, see [Using Amazon Bedrock with Jupter AI](bedrock.md). + +Bedrock supports many language model providers such as AI21 Labs, Amazon, Anthropic, Cohere, Meta, and Mistral AI. To use the base models from any supported provider make sure to enable them in Amazon Bedrock by using the AWS console. You should also select embedding models in Bedrock in addition to language completion models if you intend to use retrieval augmented generation (RAG) on your documents. + +You may now select a chosen Bedrock model from the drop-down menu box title `Completion model` in the chat interface. If RAG is going to be used then pick an embedding model that you chose from the Bedrock models as well. An example of these selections is shown below: + +Screenshot of the Jupyter AI chat panel where the base language model and embedding model is selected. + +If your provider requires an API key, please enter it in the box that will show for that provider. Make sure to click on `Save Changes` to ensure that the inputs have been saved. + +Bedrock also allows custom models to be trained from scratch or fine-tuned from a base model. Jupyter AI enables a custom model to be called in the chat panel using its `arn` (Amazon Resource Name). The interface is shown below: + +Screenshot of the Jupyter AI chat panel where the custom model is selected using model arn. + +For detailed workflows, see [Using Amazon Bedrock with Jupter AI](bedrock.md). + + ### SageMaker endpoints usage Jupyter AI supports language models hosted on SageMaker endpoints that use JSON diff --git a/packages/jupyter-ai/src/components/chat-input.tsx b/packages/jupyter-ai/src/components/chat-input.tsx index 4ab9bcd36..ddc2d4209 100644 --- a/packages/jupyter-ai/src/components/chat-input.tsx +++ b/packages/jupyter-ai/src/components/chat-input.tsx @@ -207,6 +207,7 @@ export function ChatInput(props: ChatInputProps): JSX.Element { props.chatHandler.sendMessage({ prompt, selection }); } + const inputExists = !!input.trim(); function handleKeyDown(event: React.KeyboardEvent) { if (event.key !== 'Enter') { return; @@ -218,6 +219,12 @@ export function ChatInput(props: ChatInputProps): JSX.Element { return; } + if (!inputExists) { + event.stopPropagation(); + event.preventDefault(); + return; + } + if ( event.key === 'Enter' && ((props.sendWithShiftEnter && event.shiftKey) || @@ -240,7 +247,6 @@ export function ChatInput(props: ChatInputProps): JSX.Element { ); - const inputExists = !!input.trim(); const sendButtonProps: SendButtonProps = { onSend, sendWithShiftEnter: props.sendWithShiftEnter, diff --git a/packages/jupyter-ai/src/components/chat-messages.tsx b/packages/jupyter-ai/src/components/chat-messages.tsx index ec2e0cf1a..86b6793d9 100644 --- a/packages/jupyter-ai/src/components/chat-messages.tsx +++ b/packages/jupyter-ai/src/components/chat-messages.tsx @@ -10,10 +10,12 @@ import { AiService } from '../handler'; import { RendermimeMarkdown } from './rendermime-markdown'; import { useCollaboratorsContext } from '../contexts/collaborators-context'; import { ChatMessageMenu } from './chat-messages/chat-message-menu'; +import { IJaiMessageFooter } from '../tokens'; type ChatMessagesProps = { rmRegistry: IRenderMimeRegistry; messages: AiService.ChatMessage[]; + messageFooter: IJaiMessageFooter | null; }; type ChatMessageHeaderProps = { @@ -215,6 +217,9 @@ export function ChatMessages(props: ChatMessagesProps): JSX.Element { message.type === 'agent-stream' ? !!message.complete : true } /> + {props.messageFooter && ( + + )} ); })} diff --git a/packages/jupyter-ai/src/components/chat.tsx b/packages/jupyter-ai/src/components/chat.tsx index 09edfef5a..0915d6ca6 100644 --- a/packages/jupyter-ai/src/components/chat.tsx +++ b/packages/jupyter-ai/src/components/chat.tsx @@ -18,7 +18,7 @@ import { SelectionContextProvider } from '../contexts/selection-context'; import { SelectionWatcher } from '../selection-watcher'; import { ChatHandler } from '../chat_handler'; import { CollaboratorsContextProvider } from '../contexts/collaborators-context'; -import { IJaiCompletionProvider } from '../tokens'; +import { IJaiCompletionProvider, IJaiMessageFooter } from '../tokens'; import { ActiveCellContextProvider, ActiveCellManager @@ -30,6 +30,7 @@ type ChatBodyProps = { setChatView: (view: ChatView) => void; rmRegistry: IRenderMimeRegistry; focusInputSignal: ISignal; + messageFooter: IJaiMessageFooter | null; }; /** @@ -51,7 +52,8 @@ function ChatBody({ chatHandler, focusInputSignal, setChatView: chatViewHandler, - rmRegistry: renderMimeRegistry + rmRegistry: renderMimeRegistry, + messageFooter }: ChatBodyProps): JSX.Element { const [messages, setMessages] = useState([ ...chatHandler.history.messages @@ -139,7 +141,11 @@ function ChatBody({ return ( <> - + void; activeCellManager: ActiveCellManager; focusInputSignal: ISignal; + messageFooter: IJaiMessageFooter | null; }; enum ChatView { @@ -223,6 +230,7 @@ export function Chat(props: ChatProps): JSX.Element { setChatView={setView} rmRegistry={props.rmRegistry} focusInputSignal={props.focusInputSignal} + messageFooter={props.messageFooter} /> )} {view === ChatView.Settings && ( diff --git a/packages/jupyter-ai/src/index.ts b/packages/jupyter-ai/src/index.ts index cd9d8b322..e42091980 100644 --- a/packages/jupyter-ai/src/index.ts +++ b/packages/jupyter-ai/src/index.ts @@ -18,7 +18,7 @@ import { ChatHandler } from './chat_handler'; import { buildErrorWidget } from './widgets/chat-error'; import { completionPlugin } from './completions'; import { statusItemPlugin } from './status'; -import { IJaiCompletionProvider } from './tokens'; +import { IJaiCompletionProvider, IJaiMessageFooter } from './tokens'; import { IRenderMimeRegistry } from '@jupyterlab/rendermime'; import { ActiveCellManager } from './contexts/active-cell-context'; import { Signal } from '@lumino/signaling'; @@ -42,7 +42,8 @@ const plugin: JupyterFrontEndPlugin = { IGlobalAwareness, ILayoutRestorer, IThemeManager, - IJaiCompletionProvider + IJaiCompletionProvider, + IJaiMessageFooter ], requires: [IRenderMimeRegistry], activate: async ( @@ -51,7 +52,8 @@ const plugin: JupyterFrontEndPlugin = { globalAwareness: Awareness | null, restorer: ILayoutRestorer | null, themeManager: IThemeManager | null, - completionProvider: IJaiCompletionProvider | null + completionProvider: IJaiCompletionProvider | null, + messageFooter: IJaiMessageFooter | null ) => { /** * Initialize selection watcher singleton @@ -88,7 +90,8 @@ const plugin: JupyterFrontEndPlugin = { completionProvider, openInlineCompleterSettings, activeCellManager, - focusInputSignal + focusInputSignal, + messageFooter ); } catch (e) { chatWidget = buildErrorWidget(themeManager); diff --git a/packages/jupyter-ai/src/tokens.ts b/packages/jupyter-ai/src/tokens.ts index f0f301982..efcada10f 100644 --- a/packages/jupyter-ai/src/tokens.ts +++ b/packages/jupyter-ai/src/tokens.ts @@ -1,6 +1,8 @@ +import React from 'react'; import { Token } from '@lumino/coreutils'; import { ISignal } from '@lumino/signaling'; import type { IRankedMenu } from '@jupyterlab/ui-components'; +import { AiService } from './handler'; export interface IJaiStatusItem { addItem(item: IRankedMenu.IItemOptions): void; @@ -26,3 +28,21 @@ export const IJaiCompletionProvider = new Token( 'jupyter_ai:IJaiCompletionProvider', 'The jupyter-ai inline completion provider API' ); + +export type IJaiMessageFooterProps = { + message: AiService.ChatMessage; +}; + +export interface IJaiMessageFooter { + component: React.FC; +} + +/** + * The message footer provider token. Another extension should provide this + * token to add a footer to each message. + */ + +export const IJaiMessageFooter = new Token( + 'jupyter_ai:IJaiMessageFooter', + 'Optional component that is used to render a footer on each Jupyter AI chat message, when provided.' +); diff --git a/packages/jupyter-ai/src/widgets/chat-sidebar.tsx b/packages/jupyter-ai/src/widgets/chat-sidebar.tsx index 40cf5945f..e7eee11bd 100644 --- a/packages/jupyter-ai/src/widgets/chat-sidebar.tsx +++ b/packages/jupyter-ai/src/widgets/chat-sidebar.tsx @@ -8,7 +8,7 @@ import { Chat } from '../components/chat'; import { chatIcon } from '../icons'; import { SelectionWatcher } from '../selection-watcher'; import { ChatHandler } from '../chat_handler'; -import { IJaiCompletionProvider } from '../tokens'; +import { IJaiCompletionProvider, IJaiMessageFooter } from '../tokens'; import { IRenderMimeRegistry } from '@jupyterlab/rendermime'; import type { ActiveCellManager } from '../contexts/active-cell-context'; @@ -21,7 +21,8 @@ export function buildChatSidebar( completionProvider: IJaiCompletionProvider | null, openInlineCompleterSettings: () => void, activeCellManager: ActiveCellManager, - focusInputSignal: ISignal + focusInputSignal: ISignal, + messageFooter: IJaiMessageFooter | null ): ReactWidget { const ChatWidget = ReactWidget.create( ); ChatWidget.id = 'jupyter-ai::chat';