Skip to content
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

coral-web: support langchain multihop #62

Merged
merged 1 commit into from
Apr 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions src/interfaces/coral_web/src/cohere-client/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,39 @@ export class CohereClient {
});
}

public async langchainChat({
request,
headers,
signal,
onOpen,
onMessage,
onClose,
onError,
}: {
request: CohereChatRequest;
headers?: Record<string, string>;
signal?: AbortSignal;
onOpen?: FetchEventSourceInit['onopen'];
onMessage?: FetchEventSourceInit['onmessage'];
onClose?: FetchEventSourceInit['onclose'];
onError?: FetchEventSourceInit['onerror'];
}) {
const chatRequest = mapToChatRequest(request);
const requestBody = JSON.stringify({
...chatRequest,
});
return await fetchEventSource(this.getEndpoint('langchain-chat'), {
method: 'POST',
headers: { ...this.getHeaders(), ...headers },
body: requestBody,
signal,
onopen: onOpen,
onmessage: onMessage,
onclose: onClose,
onerror: onError,
});
}

public async listConversations({
signal,
}: {
Expand Down Expand Up @@ -344,6 +377,7 @@ export class CohereClient {
endpoint:
wujessica marked this conversation as resolved.
Show resolved Hide resolved
| 'upload'
| 'chat-stream'
| 'langchain-chat'
| 'conversations'
| 'tools'
| 'deployments'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export type { StreamSearchQueriesGeneration } from './models/StreamSearchQueries
export type { StreamSearchResults } from './models/StreamSearchResults';
export type { StreamStart } from './models/StreamStart';
export type { StreamTextGeneration } from './models/StreamTextGeneration';
export type { StreamToolCallsGeneration } from './models/StreamToolCallsGeneration';
export type { StreamToolInput } from './models/StreamToolInput';
export type { StreamToolResult } from './models/StreamToolResult';
export type { Tool } from './models/Tool';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import type { StreamSearchQueriesGeneration } from './StreamSearchQueriesGenerat
import type { StreamSearchResults } from './StreamSearchResults';
import type { StreamStart } from './StreamStart';
import type { StreamTextGeneration } from './StreamTextGeneration';
import type { StreamToolCallsGeneration } from './StreamToolCallsGeneration';
import type { StreamToolInput } from './StreamToolInput';
import type { StreamToolResult } from './StreamToolResult';

Expand All @@ -29,5 +30,6 @@ export type ChatResponseEvent = {
| StreamToolInput
| StreamToolResult
| StreamSearchQueriesGeneration
| StreamToolCallsGeneration
| NonStreamedChatResponse;
};
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,7 @@ export type ManagedTool = {
parameter_definitions?: Record<string, any> | null;
kwargs?: Record<string, any>;
is_visible?: boolean;
is_available?: boolean;
error_message?: string | null;
category?: Category;
};
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,17 @@
import type { Citation } from './Citation';
import type { Document } from './Document';
import type { SearchQuery } from './SearchQuery';
import type { ToolCall } from './ToolCall';

export type StreamEnd = {
response_id: string | null;
generation_id: string | null;
conversation_id: string | null;
response_id?: string | null;
generation_id?: string | null;
conversation_id?: string | null;
text: string;
citations?: Array<Citation>;
documents?: Array<Document>;
search_results?: Array<Record<string, any>>;
search_queries?: Array<SearchQuery>;
tool_calls?: Array<ToolCall>;
finish_reason: string;
};
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@
*/
export type StreamStart = {
is_finished: boolean;
generation_id: string;
generation_id?: string | null;
conversation_id?: string | null;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/* generated using openapi-typescript-codegen -- do no edit */

/* istanbul ignore file */

/* tslint:disable */

/* eslint-disable */
import type { ToolCall } from './ToolCall';

/**
* Stream tool calls generation event.
*/
export type StreamToolCallsGeneration = {
is_finished: boolean;
tool_calls?: Array<ToolCall>;
};
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import type { Document } from './Document';

export type StreamToolResult = {
is_finished: boolean;
result: string | null;
result: any;
tool_name: string;
documents?: Array<Document>;
};
Original file line number Diff line number Diff line change
Expand Up @@ -597,7 +597,7 @@ export class DefaultService {
* List all available tools.
*
* Returns:
* list[Tool]: List of available tools.
* list[ManagedTool]: List of available tools.
* @returns ManagedTool Successful Response
* @throws ApiError
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,6 @@ export const CitationDocument: React.FC<Props> = ({ document, isExpandable = fal
{isExpanded && isExpandable && (
<div className="flex flex-col">
{getSnippet(30, 'line-clamp-3')}
{/* TODO(jessica): should we enable this/will we support this? */}
<button
className="self-end p-0 text-primary-900 transition-colors ease-in-out hover:text-primary-700"
onClick={openFullSnippetModal}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,6 @@ export const CitationTextHighlighter: React.FC<Props> = ({
generationId={generationId}
// Used to find the keyword to bold but since we don't have it yet here when the message is
// still streaming in we can just ignore it for now instead of not showing the popup at all
// TODO(jessica): pass in cited text from citation event as a backup
message={message?.originalText ?? ''}
/>
),
Expand Down
53 changes: 53 additions & 0 deletions src/interfaces/coral_web/src/components/MarkdownImage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { Text } from '@/components/Shared';
import { useCitationsStore } from '@/stores';

type Props = {
node: {
properties: {
alt: string;
src: string;
};
};
};

/**
* @description Renders an image markdown node. Inserts files from the citations
* store if they exist as a base64 image, otherwise uses the file path.
*/
export const MarkdownImage: React.FC<Props> = ({ node }) => {
const {
citations: { outputFiles },
} = useCitationsStore();

const caption = node.properties.alt;
// Remove quotes from the url
const fileName = decodeURIComponent(node.properties.src).replace(/['"]/g, '');

if (outputFiles[fileName]) {
return <B64Image data={outputFiles[fileName].data} caption={caption} />;
} else {
return (
<>
<img className="w-full" src={fileName} alt={caption} />
{caption && (
<Text as="span" styleAs="caption" className="mb-2 text-secondary-800">
{caption}
</Text>
)}
</>
);
}
};

export const B64Image: React.FC<{ data: string; caption?: string }> = ({ data, caption }) => {
return (
<>
<img className="w-full" src={`data:image/png;base64,${data}`} alt={caption} />
{caption && (
<Text as="span" styleAs="caption" className="mb-2 text-secondary-800">
{caption}
</Text>
)}
</>
);
};
2 changes: 2 additions & 0 deletions src/interfaces/coral_web/src/components/MessageContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { PropsWithChildren } from 'react';

import { CitationTextHighlighter } from '@/components/Citations/CitationTextHighlighter';
import { DataTable } from '@/components/DataTable';
import { MarkdownImage } from '@/components/MarkdownImage';
import { Icon } from '@/components/Shared';
import { Markdown, Text } from '@/components/Shared';
import { UploadedFile } from '@/components/UploadedFile';
Expand Down Expand Up @@ -108,6 +109,7 @@ export const MessageContent: React.FC<Props> = ({ isLast, message, onRetry }) =>
})}
text={message.text}
customComponents={{
img: MarkdownImage as any,
cite: CitationTextHighlighter as any,
table: DataTable as any,
}}
Expand Down
59 changes: 56 additions & 3 deletions src/interfaces/coral_web/src/components/MessageRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,25 @@ import { forwardRef, useEffect, useState } from 'react';
import { useLongPress } from 'react-aria';

import { Avatar } from '@/components/Avatar';
import IconButton from '@/components/IconButton';
import { LongPressMenu } from '@/components/LongPressMenu';
import { MessageContent } from '@/components/MessageContent';
import { CopyToClipboardButton, CopyToClipboardIconButton } from '@/components/Shared';
import {
Button,
CopyToClipboardButton,
CopyToClipboardIconButton,
Icon,
Tooltip,
} from '@/components/Shared';
import { ToolEvents } from '@/components/ToolEvents';
import { ReservedClasses } from '@/constants';
import { Breakpoint, useBreakpoint } from '@/hooks/breakpoint';
import { getMessageRowId } from '@/hooks/citations';
import { useCitationsStore } from '@/stores';
import {
type ChatMessage,
isAbortedMessage,
isErroredMessage,
isFulfilledMessage,
isFulfilledOrTypingMessage,
isFulfilledOrTypingMessageWithCitations,
Expand Down Expand Up @@ -39,10 +49,17 @@ const MessageRow = forwardRef<HTMLDivElement, Props>(function MessageRowInternal

const [isShowing, setIsShowing] = useState(false);
const [isLongPressMenuOpen, setIsLongPressMenuOpen] = useState(false);
const [isStepsExpanded, setIsStepsExpanded] = useState<boolean>(isLast);
const {
citations: { selectedCitation, hoveredGenerationId },
hoverCitation,
} = useCitationsStore();
const hasSteps =
(isFulfilledOrTypingMessage(message) ||
isErroredMessage(message) ||
isAbortedMessage(message)) &&
!!message.toolEvents &&
message.toolEvents.length > 0;

const getMessageText = () => {
if (isFulfilledMessage(message)) {
Expand All @@ -65,6 +82,12 @@ const MessageRow = forwardRef<HTMLDivElement, Props>(function MessageRowInternal
}
}, []);

useEffect(() => {
if (isLast) {
setIsStepsExpanded(true);
}
}, [isLast]);

const [highlightMessage, setHighlightMessage] = useState(false);
const prevSelectedCitationGenId = usePreviousDistinct(selectedCitation?.generationId);

Expand Down Expand Up @@ -123,6 +146,17 @@ const MessageRow = forwardRef<HTMLDivElement, Props>(function MessageRowInternal
iconAtStart
onClick={onCopy}
/>
{hasSteps && (
<Button
label={`${isStepsExpanded ? 'Hide' : 'Show'} steps`}
startIcon={<Icon name="list" />}
kind="secondary"
size="md"
aria-label={`${isStepsExpanded ? 'Hide' : 'Show'} steps`}
animate={false}
onClick={() => setIsStepsExpanded((prevIsExpanded) => !prevIsExpanded)}
/>
)}
</div>
</div>
</LongPressMenu>
Expand All @@ -144,15 +178,34 @@ const MessageRow = forwardRef<HTMLDivElement, Props>(function MessageRowInternal
>
<div className="flex w-full gap-x-2">
<Avatar message={message} />
<div className="flex w-full min-w-0 max-w-message flex-1 flex-col items-center gap-x-1 md:flex-row">
<MessageContent isLast={isLast} message={message} onRetry={onRetry} />
<div className="flex w-full min-w-0 max-w-message flex-1 flex-col items-center gap-x-3 md:flex-row">
<div className="w-full">
{hasSteps && <ToolEvents show={isStepsExpanded} events={message.toolEvents} />}

<MessageContent isLast={isLast} message={message} onRetry={onRetry} />
</div>
<div
className={cn('flex h-full items-end justify-end self-end', {
'hidden md:invisible md:flex':
!isFulfilledMessage(message) && !isUserMessage(message),
'hidden md:invisible md:flex md:group-hover:visible': !isLast,
})}
>
{hasSteps && (
<Tooltip label={`${isStepsExpanded ? 'Hide' : 'Show'} steps`} hover>
<IconButton
iconName="list"
className="rounded hover:bg-secondary-100"
iconClassName={cn(
'text-volcanic-800 group-hover/icon-button:text-secondary-800',
{
'hidden md:invisible md:flex': !isFulfilledMessage(message),
}
)}
onClick={() => setIsStepsExpanded((prevIsExpanded) => !prevIsExpanded)}
/>
</Tooltip>
)}
<CopyToClipboardIconButton value={getMessageText()} onClick={onCopy} />
</div>
</div>
Expand Down
Loading
Loading