Skip to content

Commit

Permalink
feature: better evaluation editor
Browse files Browse the repository at this point in the history
Instead of a random list of inputs we display a collapsible box with the
contents of the provider log.
  • Loading branch information
geclos committed Sep 29, 2024
1 parent 82e97dd commit 4808fd0
Show file tree
Hide file tree
Showing 18 changed files with 482 additions and 69 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,6 @@ export default function Preview({
ref={containerRef}
className='flex flex-col gap-3 h-full overflow-y-auto'
>
<Text.H6M>Preview</Text.H6M>
{(conversation?.messages ?? [])
.filter((message) => message.role === 'assistant')
.map((message, index) => (
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,273 @@
'use client'

import { ReactNode, useEffect, useState } from 'react'

import { Config, readMetadata } from '@latitude-data/compiler'
import { ProviderLogDto } from '@latitude-data/core/browser'
import {
formatContext,
formatConversation,
} from '@latitude-data/core/services/providerLogs/formatForEvaluation'
import {
Badge,
Button,
cn,
CodeBlock,
CollapsibleBox,
Icon,
Popover,
Text,
Tooltip,
} from '@latitude-data/web-ui'
import useDocumentLogWithMetadata from '$/stores/documentLogWithMetadata'

const PARAMETERS = [
'messages',
'context',
'response',
'prompt',
'parameters',
'config',
'duration',
'cost',
]

const VariableSection = ({
title,
content,
tooltip,
height = '36',
popoverContent,
}: {
title: string
content: string
tooltip?: string
height?: string
popoverContent?: ReactNode
}) => (
<div className='flex flex-col gap-2'>
<div className='flex flex-row gap-2 items-center'>
<div className='flex flex-row gap-2 items-center'>
<Badge variant='accent'>{title}</Badge>
{tooltip && (
<Tooltip
trigger={
<div>
<Icon name='info' color='primary' />
</div>
}
>
{tooltip}
</Tooltip>
)}
</div>
{popoverContent && (
<Popover.Root>
<Popover.Trigger asChild>
<Button variant='nope'>
<Icon name='circleHelp' color='foregroundMuted' />
</Button>
</Popover.Trigger>
<Popover.Content className='bg-white shadow-lg rounded-lg p-4 max-w-xl'>
{popoverContent}
</Popover.Content>
</Popover.Root>
)}
</div>
<textarea
className={cn(
`w-full p-2 rounded-lg border bg-secondary resize-y text-xs`,
{
'h-36': height === '36',
'h-24': height === '24',
},
)}
value={content}
disabled
readOnly
/>
</div>
)

const InputSection = ({
title,
content,
tooltip,
type = 'text',
}: {
title: string
content: string
tooltip: string
type?: string
}) => (
<div className='flex flex-col gap-2'>
<div className='flex flex-row gap-2 items-center'>
<Badge variant='accent'>{title}</Badge>
<TooltipInfo text={tooltip} />
</div>
<div>
<input
type={type}
className='p-2 rounded-lg border bg-secondary resize-y text-xs'
value={content}
disabled
readOnly
/>
</div>
</div>
)

export const Variables = ({
providerLog,
}: {
providerLog?: ProviderLogDto
}) => {
if (!providerLog) return null

const [config, setConfig] = useState<Config>()
const { data: documentLogWithMetadata } = useDocumentLogWithMetadata(
providerLog.documentLogUuid,
)

useEffect(() => {
const fn = async () => {
if (!documentLogWithMetadata) return

const metadata = await readMetadata({
prompt: documentLogWithMetadata!.resolvedContent,
})
setConfig(metadata.config)
}

fn()
}, [documentLogWithMetadata])

const collapsedContent = (
<div className='flex gap-2'>
{PARAMETERS.map((param) => (
<Badge key={param} variant='accent'>
{param}
</Badge>
))}
</div>
)

const expandedContent = (
<div className='flex flex-col gap-4'>
<VariableSection
title='messages'
content={JSON.stringify(formatConversation(providerLog), null, 2)}
height='36'
popoverContent={
<div className='flex flex-col gap-4'>
<Text.H5>
The <code>messages</code> variable contains all messages in the
conversation, with the following structure:
</Text.H5>
<CodeBlock language='javascript' copy={false}>
{`messages.all // Array of all messages
messages.first // First message in the conversation
messages.last // Last message in the conversation
messages.user.all // Array of user messages
messages.user.first // First user message
messages.user.last // Last user message
messages.system.all // Array of system messages
messages.system.first // First system message
messages.system.last // Last system message
messages.assistant.all // Array of assistant messages
messages.assistant.first // First assistant message
messages.assistant.last // Last assistant message`}
</CodeBlock>
<Text.H5>
You can access these properties in your prompt template using
JavaScript object accessor syntax. E.g:
</Text.H5>
<CodeBlock language='javascript'>
{`{{messages.user.first}} // This will print the first message from the user in the conversation`}
</CodeBlock>
</div>
}
/>

<VariableSection
title='context'
content={formatContext(providerLog)}
tooltip='The full conversation excluding the last assistant response'
/>

<VariableSection
title='response'
content={providerLog.response}
tooltip='The last assistant response'
/>

<VariableSection
title='prompt'
content={documentLogWithMetadata?.resolvedContent || ''}
tooltip='The original prompt'
/>

<VariableSection
title='config'
content={config ? JSON.stringify(config, null, 2) : ''}
tooltip='The prompt configuration used to generate the prompt'
height='24'
/>

<VariableSection
title='parameters'
content={
documentLogWithMetadata?.parameters
? JSON.stringify(documentLogWithMetadata.parameters, null, 2)
: ''
}
tooltip='The parameters that were used to build the prompt for this log'
height='24'
/>

<InputSection
title='duration'
content={documentLogWithMetadata?.duration?.toString() || '0'}
tooltip='The time it took to run this prompt in milliseconds'
type='number'
/>

<InputSection
title='cost'
content={
documentLogWithMetadata?.costInMillicents
? (documentLogWithMetadata?.costInMillicents / 1000).toString()
: '0'
}
tooltip='The cost of running this prompt in cents'
type='number'
/>
</div>
)

return (
<CollapsibleBox
title='Variables'
collapsedContent={collapsedContent}
expandedContent={expandedContent}
expandedHeight='720px'
/>
)
}

const TooltipInfo = ({ text }: { text: string }) => {
return (
<Tooltip
trigger={
<div>
<Icon name='info' color='primary' />
</div>
}
>
{text}
</Tooltip>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
formatContext,
formatConversation,
} from '@latitude-data/core/services/providerLogs/formatForEvaluation'
import { Badge, Icon, Input, Text } from '@latitude-data/web-ui'
import { Button, Icon, TableBlankSlate, Text } from '@latitude-data/web-ui'
import { convertParams } from '$/app/(private)/projects/[projectId]/versions/[commitUuid]/documents/[documentUuid]/_components/DocumentEditor/Editor/Playground'
import { ROUTES } from '$/services/routes'
import { useProviderLog } from '$/stores/providerLogs'
Expand All @@ -18,6 +18,26 @@ import { useSearchParams } from 'next/navigation'
import { Header } from '../Header'
import Chat, { EVALUATION_PARAMETERS } from './Chat'
import Preview from './Preview'
import { Variables } from './Variables'

const BlankSlate = ({ evaluation }: { evaluation: EvaluationDto }) => (
<>
<Header title='Playground' />
<TableBlankSlate
description='Import data from an existing log to add it to your variables. Then test your evaluation prompt to see how it performs.'
link={
<Link
href={
ROUTES.evaluations.detail({ uuid: evaluation.uuid }).editor
.importLogs.root
}
>
<Button fancy>Import Log</Button>
</Link>
}
/>
</>
)

export default function Playground({
evaluation,
Expand Down Expand Up @@ -58,46 +78,29 @@ export default function Playground({
}
}, [setInput, providerLog])

if (!providerLog) {
return <BlankSlate evaluation={evaluation} />
}

return (
<>
<Header title='Playground' />
<div className='flex flex-row justify-between items-center'>
<Header title='Playground' />
{providerLog && (
<Link
className='flex flex-row gap-2 items-center'
href={
ROUTES.evaluations.detail({ uuid: evaluation.uuid }).editor
.importLogs.root
}
>
<Text.H5M>Import another log</Text.H5M> <Icon name='addSquare' />
</Link>
)}
</div>
<div className='flex flex-col gap-6 h-full relative'>
<div className='flex flex-col gap-3'>
<div className='flex flex-row items-center justify-between gap-4'>
<Text.H6M>Variables</Text.H6M>
<Link
className='flex flex-row gap-2 items-center'
href={
ROUTES.evaluations.detail({ uuid: evaluation.uuid }).editor
.importLogs.root
}
>
<Text.H5M>Import data from logs</Text.H5M>{' '}
<Icon name='addSquare' />
</Link>
</div>
{Object.keys(inputs).length > 0 ? (
Object.entries(inputs).map(([param, value], idx) => (
<div
className='flex flex-row gap-4 w-full items-center'
key={idx}
>
<Badge variant='accent'>&#123;&#123;{param}&#125;&#125;</Badge>
<div className='flex flex-grow w-full'>
<Input
value={value}
onChange={(e) => setInput(param, e.currentTarget.value)}
/>
</div>
</div>
))
) : (
<Text.H6 color='foregroundMuted'>
No inputs. Use &#123;&#123; input_name &#125;&#125; to insert.
</Text.H6>
)}
</div>
<div className='flex flex-col gap-3 h-full'>
<Variables providerLog={providerLog} />
<div className='flex flex-col h-full'>
<div className='flex flex-col flex-grow flex-shrink relative h-full overflow-y-auto'>
<div className='absolute top-0 left-0 right-0 bottom-0'>
{mode === 'preview' ? (
Expand Down
1 change: 1 addition & 0 deletions apps/web/src/app/api/documentLogs/[id]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { authHandler } from '$/middlewares/authHandler'
import { errorHandler } from '$/middlewares/errorHandler'
import { NextRequest, NextResponse } from 'next/server'

// TODO: DRY with api/documentLogs/uuids/[uuid]/route.ts
export const GET = errorHandler(
authHandler(
async (
Expand Down
Loading

0 comments on commit 4808fd0

Please sign in to comment.