Skip to content

Commit

Permalink
Integrate list traces api of agent framework (#25)
Browse files Browse the repository at this point in the history
* Integrate list traces api of agent framework

Signed-off-by: gaobinlong <[email protected]>

* Fix import issue

Signed-off-by: gaobinlong <[email protected]>

* Fix some issues

Signed-off-by: gaobinlong <[email protected]>

* Add some comments

Signed-off-by: gaobinlong <[email protected]>

---------

Signed-off-by: gaobinlong <[email protected]>
  • Loading branch information
gaobinlong authored and SuZhou-Joe committed Dec 5, 2023
1 parent 491a111 commit ca6ab44
Show file tree
Hide file tree
Showing 16 changed files with 369 additions and 74 deletions.
1 change: 1 addition & 0 deletions common/constants/llm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export const ASSISTANT_API = {
FEEDBACK: `${API_BASE}/feedback`,
ABORT_AGENT_EXECUTION: `${API_BASE}/abort`,
REGENERATE: `${API_BASE}/regenerate`,
TRACE: `${API_BASE}/trace`,
} as const;

export const LLM_INDEX = {
Expand Down
10 changes: 10 additions & 0 deletions common/utils/llm_chat/traces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,16 @@ import { Run } from 'langchain/callbacks';
import { AgentRun } from 'langchain/dist/callbacks/handlers/tracer';
import _ from 'lodash';

export interface AgentFrameworkTrace {
interactionId: string;
parentInteractionId: string;
createTime: string;
input: string;
output: string;
origin: string;
traceNumber: number;
}

export interface LangchainTrace {
id: Run['id'];
parentRunId?: Run['parent_run_id'];
Expand Down
11 changes: 6 additions & 5 deletions public/chat_flyout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import { useChatContext } from './contexts/chat_context';
import { ChatPage } from './tabs/chat/chat_page';
import { ChatWindowHeader } from './tabs/chat_window_header';
import { ChatHistoryPage } from './tabs/history/chat_history_page';
import { LangchainTracesFlyoutBody } from './components/langchain_traces_flyout_body';
import { AgentFrameworkTracesFlyoutBody } from './components/agent_framework_traces_flyout_body';
import { TAB_ID } from './utils/constants';

let chatHistoryPageLoaded = false;

Expand All @@ -31,15 +32,15 @@ export const ChatFlyout: React.FC<ChatFlyoutProps> = (props) => {

if (!props.overrideComponent) {
switch (chatContext.selectedTabId) {
case 'chat':
case TAB_ID.CHAT:
chatPageVisible = true;
break;

case 'history':
case TAB_ID.HISTORY:
chatHistoryPageVisible = true;
break;

case 'trace':
case TAB_ID.TRACE:
chatTraceVisible = true;
break;

Expand Down Expand Up @@ -134,7 +135,7 @@ export const ChatFlyout: React.FC<ChatFlyoutProps> = (props) => {
className={cs({ 'llm-chat-hidden': !chatHistoryPageVisible })}
/>
)}
{chatTraceVisible && chatContext.traceId && <LangchainTracesFlyoutBody />}
{chatTraceVisible && chatContext.traceId && <AgentFrameworkTracesFlyoutBody />}
</Panel>
</>
</>
Expand Down
3 changes: 2 additions & 1 deletion public/chat_header_button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { ChatStateProvider } from './hooks';
import './index.scss';
import chatIcon from './assets/chat.svg';
import { ActionExecutor, AssistantActions, ContentRenderer, UserAccount, TabId } from './types';
import { TAB_ID } from './utils/constants';

interface HeaderChatButtonProps {
application: ApplicationStart;
Expand All @@ -33,7 +34,7 @@ export const HeaderChatButton: React.FC<HeaderChatButtonProps> = (props) => {
const [title, setTitle] = useState<string>();
const [flyoutVisible, setFlyoutVisible] = useState(false);
const [flyoutComponent, setFlyoutComponent] = useState<React.ReactNode | null>(null);
const [selectedTabId, setSelectedTabId] = useState<TabId>('chat');
const [selectedTabId, setSelectedTabId] = useState<TabId>(TAB_ID.CHAT);
const [preSelectedTabId, setPreSelectedTabId] = useState<TabId | undefined>(undefined);
const [traceId, setTraceId] = useState<string | undefined>(undefined);
const [chatSize, setChatSize] = useState<number | 'fullscreen' | 'dock-right'>('dock-right');
Expand Down
92 changes: 92 additions & 0 deletions public/components/agent_framework_traces.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import {
EuiAccordion,
EuiCodeBlock,
EuiEmptyPrompt,
EuiLoadingContent,
EuiSpacer,
EuiText,
EuiMarkdownFormat,
EuiHorizontalRule,
} from '@elastic/eui';
import React from 'react';
import { useFetchAgentFrameworkTraces } from '../hooks/use_fetch_agentframework_traces';

interface AgentFrameworkTracesProps {
traceId: string;
}

export const AgentFrameworkTraces: React.FC<AgentFrameworkTracesProps> = (props) => {
const { data: traces, loading, error } = useFetchAgentFrameworkTraces(props.traceId);

if (loading) {
return (
<>
<EuiText>Loading...</EuiText>
<EuiLoadingContent lines={10} />
</>
);
}
if (error) {
return (
<EuiEmptyPrompt
iconType="alert"
iconColor="danger"
title={<h2>Error loading details</h2>}
body={error.toString()}
/>
);
}
if (!traces?.length) {
return <EuiText>Data not available.</EuiText>;
}

const question = traces[traces.length - 1].input;
const result = traces[traces.length - 1].output;
const questionAndResult = `# How was this generated
#### Question
${question}
#### Result
${result}
`;

return (
<>
<EuiMarkdownFormat>{questionAndResult}</EuiMarkdownFormat>

<EuiSpacer size="l" />

<EuiText>
<h3>Response</h3>
</EuiText>
{traces
// if origin exists, it indicates that the trace was generated by a tool, we only show the non-empty traces of tools
.filter((trace) => trace.origin && (trace.input || trace.output))
.map((trace, i) => {
const stepContent = `Step ${i + 1}`;
return (
<div key={trace.interactionId}>
<EuiSpacer size="s" />
<EuiAccordion id={stepContent} buttonContent={stepContent}>
{trace.input && (
<EuiCodeBlock fontSize="m" paddingSize="s">
Input: {trace.input}
</EuiCodeBlock>
)}
{trace.output && (
<EuiCodeBlock fontSize="m" paddingSize="s">
Output: {trace.output}
</EuiCodeBlock>
)}
</EuiAccordion>
<EuiHorizontalRule margin="xs" />
</div>
);
})}
</>
);
};
73 changes: 73 additions & 0 deletions public/components/agent_framework_traces_flyout_body.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import {
EuiButtonEmpty,
EuiFlyoutBody,
EuiPage,
EuiPageBody,
EuiPageContentBody,
EuiPageHeader,
EuiButtonIcon,
EuiPageHeaderSection,
} from '@elastic/eui';
import React from 'react';
import { useChatContext } from '../contexts/chat_context';
import { AgentFrameworkTraces } from './agent_framework_traces';
import { TAB_ID } from '../utils/constants';

export const AgentFrameworkTracesFlyoutBody: React.FC = () => {
const chatContext = useChatContext();
const traceId = chatContext.traceId;
if (!traceId) {
return null;
}

// docked right or fullscreen with history open
const showBack = !chatContext.flyoutFullScreen || chatContext.preSelectedTabId === TAB_ID.HISTORY;

return (
<EuiFlyoutBody className="llm-chat-flyout llm-chat-flyout-body">
<EuiPage>
<EuiPageBody>
<EuiPageHeader>
<EuiPageHeaderSection>
{showBack && (
<EuiButtonEmpty
size="xs"
flush="left"
onClick={() => {
chatContext.setSelectedTabId(
chatContext.flyoutFullScreen ? TAB_ID.HISTORY : TAB_ID.CHAT
);
}}
iconType="arrowLeft"
>
Back
</EuiButtonEmpty>
)}
</EuiPageHeaderSection>
<EuiPageHeaderSection>
{!showBack && (
<EuiButtonIcon
aria-label="close"
size="xs"
color="text"
iconType="cross"
onClick={() => {
chatContext.setSelectedTabId(TAB_ID.CHAT);
}}
/>
)}
</EuiPageHeaderSection>
</EuiPageHeader>
<EuiPageContentBody>
<AgentFrameworkTraces traceId={traceId} />
</EuiPageContentBody>
</EuiPageBody>
</EuiPage>
</EuiFlyoutBody>
);
};
7 changes: 4 additions & 3 deletions public/hooks/use_chat_actions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
* SPDX-License-Identifier: Apache-2.0
*/

import { TAB_ID } from '../utils/constants';
import { ASSISTANT_API } from '../../common/constants/llm';
import {
IMessage,
Expand Down Expand Up @@ -52,7 +53,7 @@ export const useChatActions = (): AssistantActions => {
!chatContext.sessionId &&
response.sessionId &&
core.services.sessions.options?.page === 1 &&
chatContext.selectedTabId === 'history'
chatContext.selectedTabId === TAB_ID.HISTORY
) {
core.services.sessions.reload();
}
Expand Down Expand Up @@ -81,7 +82,7 @@ export const useChatActions = (): AssistantActions => {
chatContext.setTitle(title);
// Chat page will always visible in fullscreen mode, we don't need to change the tab anymore
if (!chatContext.flyoutFullScreen) {
chatContext.setSelectedTabId('chat');
chatContext.setSelectedTabId(TAB_ID.CHAT);
}
chatContext.setFlyoutComponent(null);
if (!sessionId) {
Expand All @@ -102,7 +103,7 @@ export const useChatActions = (): AssistantActions => {

const openChatUI = () => {
chatContext.setFlyoutVisible(true);
chatContext.setSelectedTabId('chat');
chatContext.setSelectedTabId(TAB_ID.CHAT);
};

const executeAction = async (suggestedAction: ISuggestedAction, message: IMessage) => {
Expand Down
39 changes: 39 additions & 0 deletions public/hooks/use_fetch_agentframework_traces.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import { useEffect, useReducer } from 'react';
import { ASSISTANT_API } from '../../common/constants/llm';
import { AgentFrameworkTrace } from '../../common/utils/llm_chat/traces';
import { useCore } from '../contexts/core_context';
import { GenericReducer, genericReducer } from './fetch_reducer';

export const useFetchAgentFrameworkTraces = (traceId: string) => {
const core = useCore();
const reducer: GenericReducer<AgentFrameworkTrace[]> = genericReducer;
const [state, dispatch] = useReducer(reducer, { loading: false });

useEffect(() => {
const abortController = new AbortController();
dispatch({ type: 'request' });
if (!traceId) {
dispatch({ type: 'success', payload: undefined });
return;
}

core.services.http
.get<AgentFrameworkTrace[]>(`${ASSISTANT_API.TRACE}/${traceId}`)
.then((payload) =>
dispatch({
type: 'success',
payload,
})
)
.catch((error) => dispatch({ type: 'failure', error }));

return () => abortController.abort();
}, [traceId]);

return { ...state };
};
2 changes: 2 additions & 0 deletions public/tabs/chat/messages/message_bubble.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@ export const MessageBubble: React.FC<MessageBubbleProps> = React.memo((props) =>
{feedbackResult !== false ? (
<EuiFlexItem grow={false}>
<EuiButtonIcon
aria-label="feedback"
color={feedbackResult === true ? 'primary' : 'text'}
iconType="thumbsUp"
onClick={() => feedbackOutput(true, feedbackResult)}
Expand All @@ -212,6 +213,7 @@ export const MessageBubble: React.FC<MessageBubbleProps> = React.memo((props) =>
{feedbackResult !== true ? (
<EuiFlexItem grow={false}>
<EuiButtonIcon
aria-label="feedback result"
color={feedbackResult === false ? 'primary' : 'text'}
iconType="thumbsDown"
onClick={() => feedbackOutput(false, feedbackResult)}
Expand Down
9 changes: 2 additions & 7 deletions public/tabs/chat/messages/message_footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiHorizontalRule } from '@e
import React from 'react';
import { IMessage } from '../../../../common/types/chat_saved_object_attributes';
import { FeedbackModal } from '../../../components/feedback_modal';
import { LangchainTracesFlyoutBody } from '../../../components/langchain_traces_flyout_body';
import { useChatContext } from '../../../contexts/chat_context';
import { useCore } from '../../../contexts/core_context';
import { AgentFrameworkTracesFlyoutBody } from '../../../components/agent_framework_traces_flyout_body';

interface MessageFooterProps {
message: IMessage;
Expand All @@ -31,12 +31,7 @@ export const MessageFooter: React.FC<MessageFooterProps> = React.memo((props) =>
size="xs"
flush="left"
onClick={() => {
chatContext.setFlyoutComponent(
<LangchainTracesFlyoutBody
closeFlyout={() => chatContext.setFlyoutComponent(null)}
traceId={traceId}
/>
);
chatContext.setFlyoutComponent(<AgentFrameworkTracesFlyoutBody />);
}}
>
How was this generated?
Expand Down
Loading

0 comments on commit ca6ab44

Please sign in to comment.