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

Integrate list traces api of agent framework #25

Merged
merged 4 commits into from
Nov 30, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
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
4 changes: 2 additions & 2 deletions public/chat_flyout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ 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';

let chatHistoryPageLoaded = false;

Expand Down Expand Up @@ -134,7 +134,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
91 changes: 91 additions & 0 deletions public/components/agent_framework_traces.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/*
* 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[0].input;
const result = traces[0].output;
const questionAndResult = `# How was this generated
#### Question
${question}
#### Result
${result}
`;

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

<EuiSpacer size="l" />

<EuiText>
<h3>Response</h3>
</EuiText>
{traces
.filter((trace) => trace.origin?.includes('Tool') && (trace.input || trace.output))
gaobinlong marked this conversation as resolved.
Show resolved Hide resolved
.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>
);
})}
</>
);
};
70 changes: 70 additions & 0 deletions public/components/agent_framework_traces_flyout_body.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*
* 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';

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 === '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 ? 'history' : 'chat');
SuZhou-Joe marked this conversation as resolved.
Show resolved Hide resolved
}}
iconType="arrowLeft"
>
Back
</EuiButtonEmpty>
)}
</EuiPageHeaderSection>
<EuiPageHeaderSection>
{!showBack && (
<EuiButtonIcon
aria-label="close"
size="xs"
color="text"
iconType="cross"
onClick={() => {
chatContext.setSelectedTabId('chat');
}}
/>
)}
</EuiPageHeaderSection>
</EuiPageHeader>
<EuiPageContentBody>
<AgentFrameworkTraces traceId={traceId} />
</EuiPageContentBody>
</EuiPageBody>
</EuiPage>
</EuiFlyoutBody>
);
};
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 @@ -188,6 +188,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 @@ -197,6 +198,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
4 changes: 2 additions & 2 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';
gaobinlong marked this conversation as resolved.
Show resolved Hide resolved
import { useChatContext } from '../../../contexts/chat_context';
import { useCore } from '../../../contexts/core_context';
import { AgentFrameworkTracesFlyoutBody } from '../../..//components/agent_framework_traces_flyout_body';
gaobinlong marked this conversation as resolved.
Show resolved Hide resolved

interface MessageFooterProps {
message: IMessage;
Expand All @@ -32,7 +32,7 @@ export const MessageFooter: React.FC<MessageFooterProps> = React.memo((props) =>
flush="left"
onClick={() => {
chatContext.setFlyoutComponent(
<LangchainTracesFlyoutBody
<AgentFrameworkTracesFlyoutBody
closeFlyout={() => chatContext.setFlyoutComponent(null)}
gaobinlong marked this conversation as resolved.
Show resolved Hide resolved
traceId={traceId}
/>
Expand Down
6 changes: 5 additions & 1 deletion public/tabs/history/chat_history_page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,11 @@ export const ChatHistoryPage: React.FC<ChatHistoryPageProps> = React.memo((props
{flyoutFullScreen ? (
<EuiFlexGroup gutterSize="none" justifyContent="flexEnd">
<EuiFlexItem grow={false}>
<EuiButtonIcon iconType="cross" onClick={handleBack} />
<EuiButtonIcon
aria-label="full screen back"
iconType="cross"
onClick={handleBack}
/>
</EuiFlexItem>
</EuiFlexGroup>
) : (
Expand Down
28 changes: 28 additions & 0 deletions server/routes/chat_routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,15 @@ const updateSessionRoute = {
},
};

const getTracesRoute = {
path: `${ASSISTANT_API.TRACE}/{traceId}`,
validate: {
params: schema.object({
traceId: schema.string(),
}),
},
};

export function registerChatRoutes(router: IRouter, routeOptions: RoutesOptions) {
const createStorageService = (context: RequestHandlerContext) =>
new AgentFrameworkStorageService(
Expand Down Expand Up @@ -226,6 +235,25 @@ export function registerChatRoutes(router: IRouter, routeOptions: RoutesOptions)
}
);

router.get(
getTracesRoute,
async (
context,
request,
response
): Promise<IOpenSearchDashboardsResponse<HttpResponsePayload | ResponseError>> => {
const storageService = createStorageService(context);

try {
const getResponse = await storageService.getTraces(request.params.traceId);
return response.ok({ body: getResponse });
} catch (error) {
context.assistant_plugin.logger.error(error);
return response.custom({ statusCode: error.statusCode || 500, body: error.message });
}
}
);

router.post(
abortAgentExecutionRoute,
async (
Expand Down
1 change: 0 additions & 1 deletion server/services/chat/olly_chat_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import { LLMModelFactory } from '../../olly/models/llm_model_factory';
import { PPLTools } from '../../olly/tools/tool_sets/ppl';
import { PPLGenerationRequestSchema } from '../../routes/langchain_routes';
import { ChatService } from './chat_service';
import { LLMRequestSchema } from '../../routes/chat_routes';

const MEMORY_ID_FIELD = 'memory_id';

Expand Down
35 changes: 35 additions & 0 deletions server/services/storage/agent_framework_storage_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
*/

import { ApiResponse } from '@opensearch-project/opensearch/.';
import { AgentFrameworkTrace } from 'common/utils/llm_chat/traces';
gaobinlong marked this conversation as resolved.
Show resolved Hide resolved
import { OpenSearchClient } from '../../../../../src/core/server';
import {
IMessage,
Expand Down Expand Up @@ -181,4 +182,38 @@ export class AgentFrameworkStorageService implements StorageService {
throw new Error('update converstaion failed, reason:' + JSON.stringify(error.meta?.body));
}
}

async getTraces(interactionId: string): Promise<AgentFrameworkTrace[]> {
try {
const response = (await this.client.transport.request({
method: 'GET',
path: `/_plugins/_ml/memory/trace/${interactionId}/_list`,
})) as ApiResponse<{
traces: Array<{
conversation_id: string;
interaction_id: string;
create_time: string;
input: string;
response: string;
origin: string;
parent_interaction_id: string;
trace_number: number;
}>;
}>;

return response.body.traces
.map((item) => ({
interactionId: item.interaction_id,
parentInteractionId: item.parent_interaction_id,
input: item.input,
output: item.response,
createTime: item.create_time,
origin: item.origin,
traceNumber: item.trace_number,
}))
.reverse();
gaobinlong marked this conversation as resolved.
Show resolved Hide resolved
gaobinlong marked this conversation as resolved.
Show resolved Hide resolved
} catch (error) {
throw new Error('get traces failed, reason:' + JSON.stringify(error.meta?.body));
}
}
}
Loading