Skip to content

Commit

Permalink
discard tool use; more timestamps
Browse files Browse the repository at this point in the history
  • Loading branch information
nl0 committed Aug 6, 2024
1 parent 105cdca commit fa73d75
Show file tree
Hide file tree
Showing 3 changed files with 125 additions and 61 deletions.
2 changes: 1 addition & 1 deletion catalog/app/components/Assistant/Model/Assistant.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ function useConstructAssistantAPI() {
)
const [state, dispatch] = Actor.useActorLayer(

Check warning on line 31 in catalog/app/components/Assistant/Model/Assistant.tsx

View check run for this annotation

Codecov / codecov/patch/informational

catalog/app/components/Assistant/Model/Assistant.tsx#L31

Added line #L31 was not covered by tests
Conversation.ConversationActor,
Eff.Effect.succeed(Conversation.init),
Conversation.init,
layerEff,
)

Expand Down
51 changes: 34 additions & 17 deletions catalog/app/components/Assistant/Model/Conversation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const MODULE = 'Conversation'
// TODO: make this a globally available service?
const genId = Eff.Effect.sync(uuid.v4)

Check warning on line 16 in catalog/app/components/Assistant/Model/Conversation.ts

View check run for this annotation

Codecov / codecov/patch/informational

catalog/app/components/Assistant/Model/Conversation.ts#L16

Added line #L16 was not covered by tests

interface ToolCall {
export interface ToolCall {
readonly name: string
readonly input: Record<string, any>
readonly fiber: Eff.Fiber.RuntimeFiber<void>
Expand Down Expand Up @@ -49,12 +49,16 @@ export type Event = Eff.Data.TaggedEnum<{
// eslint-disable-next-line @typescript-eslint/no-redeclare
export const Event = Eff.Data.taggedEnum<Event>()

Check warning on line 50 in catalog/app/components/Assistant/Model/Conversation.ts

View check run for this annotation

Codecov / codecov/patch/informational

catalog/app/components/Assistant/Model/Conversation.ts#L50

Added line #L50 was not covered by tests

interface StateBase {
readonly events: Event[]
readonly timestamp: Date
}

export type State = Eff.Data.TaggedEnum<{
/**
* Waiting for user input
*/
Idle: {
readonly events: Event[]
Idle: StateBase & {
readonly error: Eff.Option.Option<{
message: string
details: string
Expand All @@ -64,16 +68,14 @@ export type State = Eff.Data.TaggedEnum<{
/**
* Waiting for assistant (LLM) to respond
*/
WaitingForAssistant: {
readonly events: Event[]
WaitingForAssistant: StateBase & {
readonly requestFiber: Eff.Fiber.RuntimeFiber<boolean>
}

/**
* Tool use in progress
*/
ToolUse: {
readonly events: Event[]
ToolUse: StateBase & {
// TODO: use HashMap?
readonly calls: Record<string, ToolCall>
// readonly retries: number
Expand Down Expand Up @@ -112,9 +114,12 @@ export type Action = Eff.Data.TaggedEnum<{
// eslint-disable-next-line @typescript-eslint/no-redeclare
export const Action = Eff.Data.taggedEnum<Action>()

Check warning on line 115 in catalog/app/components/Assistant/Model/Conversation.ts

View check run for this annotation

Codecov / codecov/patch/informational

catalog/app/components/Assistant/Model/Conversation.ts#L115

Added line #L115 was not covered by tests

export const init = State.Idle({
events: [],
error: Eff.Option.none(),
export const init = Eff.Effect.gen(function* () {

Check warning on line 117 in catalog/app/components/Assistant/Model/Conversation.ts

View check run for this annotation

Codecov / codecov/patch/informational

catalog/app/components/Assistant/Model/Conversation.ts#L117

Added line #L117 was not covered by tests
return State.Idle({
timestamp: new Date(yield* Eff.Clock.currentTimeMillis),

Check warning on line 119 in catalog/app/components/Assistant/Model/Conversation.ts

View check run for this annotation

Codecov / codecov/patch/informational

catalog/app/components/Assistant/Model/Conversation.ts#L119

Added line #L119 was not covered by tests
events: [],
error: Eff.Option.none(),
})
})

const llmRequest = (events: Event[]) =>
Expand Down Expand Up @@ -153,9 +158,10 @@ export const ConversationActor = Eff.Effect.succeed(
Idle: {
Ask: (state, action, dispatch) =>
Eff.Effect.gen(function* () {
const timestamp = new Date(yield* Eff.Clock.currentTimeMillis)
const event = Event.Message({
id: yield* genId,

Check warning on line 163 in catalog/app/components/Assistant/Model/Conversation.ts

View check run for this annotation

Codecov / codecov/patch/informational

catalog/app/components/Assistant/Model/Conversation.ts#L159-L163

Added lines #L159 - L163 were not covered by tests
timestamp: new Date(yield* Eff.Clock.currentTimeMillis),
timestamp,
role: 'user',
content: Content.text(action.content),
})
Expand All @@ -167,10 +173,13 @@ export const ConversationActor = Eff.Effect.succeed(
(r) => Eff.Effect.succeed(Action.LLMResponse(r)),
(error) => Eff.Effect.succeed(Action.LLMError({ error })),

Check warning on line 174 in catalog/app/components/Assistant/Model/Conversation.ts

View check run for this annotation

Codecov / codecov/patch/informational

catalog/app/components/Assistant/Model/Conversation.ts#L173-L174

Added lines #L173 - L174 were not covered by tests
)
return State.WaitingForAssistant({ events, requestFiber })
return State.WaitingForAssistant({ events, timestamp, requestFiber })

Check warning on line 176 in catalog/app/components/Assistant/Model/Conversation.ts

View check run for this annotation

Codecov / codecov/patch/informational

catalog/app/components/Assistant/Model/Conversation.ts#L176

Added line #L176 was not covered by tests
}),
Clear: () =>
Eff.Effect.succeed(State.Idle({ events: [], error: Eff.Option.none() })),
Eff.Effect.gen(function* () {
const timestamp = new Date(yield* Eff.Clock.currentTimeMillis)
return State.Idle({ events: [], timestamp, error: Eff.Option.none() })

Check warning on line 181 in catalog/app/components/Assistant/Model/Conversation.ts

View check run for this annotation

Codecov / codecov/patch/informational

catalog/app/components/Assistant/Model/Conversation.ts#L178-L181

Added lines #L178 - L181 were not covered by tests
}),
Discard: (state, { id }) =>
Eff.Effect.succeed({

Check warning on line 184 in catalog/app/components/Assistant/Model/Conversation.ts

View check run for this annotation

Codecov / codecov/patch/informational

catalog/app/components/Assistant/Model/Conversation.ts#L183-L184

Added lines #L183 - L184 were not covered by tests
...state,
Expand All @@ -184,6 +193,7 @@ export const ConversationActor = Eff.Effect.succeed(
Eff.Effect.gen(function* () {

Check warning on line 193 in catalog/app/components/Assistant/Model/Conversation.ts

View check run for this annotation

Codecov / codecov/patch/informational

catalog/app/components/Assistant/Model/Conversation.ts#L192-L193

Added lines #L192 - L193 were not covered by tests
return State.Idle({
events: state.events,
timestamp: new Date(yield* Eff.Clock.currentTimeMillis),

Check warning on line 196 in catalog/app/components/Assistant/Model/Conversation.ts

View check run for this annotation

Codecov / codecov/patch/informational

catalog/app/components/Assistant/Model/Conversation.ts#L196

Added line #L196 was not covered by tests
error: Eff.Option.some({
message: 'Error while interacting with LLM. Please try again.',
details: `${error}`,
Expand All @@ -210,7 +220,8 @@ export const ConversationActor = Eff.Effect.succeed(
)
}

if (!toolUses.length) return State.Idle({ events, error: Eff.Option.none() })
if (!toolUses.length)
return State.Idle({ events, timestamp, error: Eff.Option.none() })

Check warning on line 224 in catalog/app/components/Assistant/Model/Conversation.ts

View check run for this annotation

Codecov / codecov/patch/informational

catalog/app/components/Assistant/Model/Conversation.ts#L224

Added line #L224 was not covered by tests

const ctxService = yield* Context.ConversationContext
const { tools } = yield* ctxService.context
Expand All @@ -229,7 +240,7 @@ export const ConversationActor = Eff.Effect.succeed(
}
}

return State.ToolUse({ events, calls })
return State.ToolUse({ events, timestamp: state.timestamp, calls })
}),
Abort: (state) =>
Eff.Effect.gen(function* () {

Check warning on line 246 in catalog/app/components/Assistant/Model/Conversation.ts

View check run for this annotation

Codecov / codecov/patch/informational

catalog/app/components/Assistant/Model/Conversation.ts#L245-L246

Added lines #L245 - L246 were not covered by tests
Expand Down Expand Up @@ -259,7 +270,8 @@ export const ConversationActor = Eff.Effect.succeed(
events = events.concat(event)

Check warning on line 270 in catalog/app/components/Assistant/Model/Conversation.ts

View check run for this annotation

Codecov / codecov/patch/informational

catalog/app/components/Assistant/Model/Conversation.ts#L270

Added line #L270 was not covered by tests
}

if (Object.keys(calls).length) return State.ToolUse({ events, calls })
if (Object.keys(calls).length)
return State.ToolUse({ events, timestamp: state.timestamp, calls })

Check warning on line 274 in catalog/app/components/Assistant/Model/Conversation.ts

View check run for this annotation

Codecov / codecov/patch/informational

catalog/app/components/Assistant/Model/Conversation.ts#L274

Added line #L274 was not covered by tests

// all calls completed, send results back to LLM
const requestFiber = yield* Actor.forkRequest(

Check warning on line 277 in catalog/app/components/Assistant/Model/Conversation.ts

View check run for this annotation

Codecov / codecov/patch/informational

catalog/app/components/Assistant/Model/Conversation.ts#L277

Added line #L277 was not covered by tests
Expand All @@ -268,7 +280,12 @@ export const ConversationActor = Eff.Effect.succeed(
(r) => Eff.Effect.succeed(Action.LLMResponse(r)),
(error) => Eff.Effect.succeed(Action.LLMError({ error })),

Check warning on line 281 in catalog/app/components/Assistant/Model/Conversation.ts

View check run for this annotation

Codecov / codecov/patch/informational

catalog/app/components/Assistant/Model/Conversation.ts#L280-L281

Added lines #L280 - L281 were not covered by tests
)
return State.WaitingForAssistant({ events, requestFiber })
return State.WaitingForAssistant({
events,

timestamp: new Date(yield* Eff.Clock.currentTimeMillis),

Check warning on line 286 in catalog/app/components/Assistant/Model/Conversation.ts

View check run for this annotation

Codecov / codecov/patch/informational

catalog/app/components/Assistant/Model/Conversation.ts#L286

Added line #L286 was not covered by tests
requestFiber,
})
}),
Abort: (state) =>
Eff.Effect.gen(function* () {

Check warning on line 291 in catalog/app/components/Assistant/Model/Conversation.ts

View check run for this annotation

Codecov / codecov/patch/informational

catalog/app/components/Assistant/Model/Conversation.ts#L290-L291

Added lines #L290 - L291 were not covered by tests
Expand Down
133 changes: 90 additions & 43 deletions catalog/app/components/Assistant/UI/Chat/Chat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import * as React from 'react'
import * as M from '@material-ui/core'

Check warning on line 4 in catalog/app/components/Assistant/UI/Chat/Chat.tsx

View check run for this annotation

Codecov / codecov/patch/informational

catalog/app/components/Assistant/UI/Chat/Chat.tsx#L1-L4

Added lines #L1 - L4 were not covered by tests
// import * as Lab from '@material-ui/lab'

import JsonDisplay from 'components/JsonDisplay'
import Markdown from 'components/Markdown'

Check warning on line 8 in catalog/app/components/Assistant/UI/Chat/Chat.tsx

View check run for this annotation

Codecov / codecov/patch/informational

catalog/app/components/Assistant/UI/Chat/Chat.tsx#L7-L8

Added lines #L7 - L8 were not covered by tests

import * as Model from '../../Model'

Check warning on line 10 in catalog/app/components/Assistant/UI/Chat/Chat.tsx

View check run for this annotation

Codecov / codecov/patch/informational

catalog/app/components/Assistant/UI/Chat/Chat.tsx#L10

Added line #L10 was not covered by tests
Expand Down Expand Up @@ -53,6 +54,7 @@ const useMessageContainerStyles = M.makeStyles((t) => ({
contents: {
...t.typography.body2,
color: t.palette.text.primary,
maxWidth: 'calc(50vw - 112px)',
padding: `${t.spacing(1.5)}px`,
},
footer: {
Expand Down Expand Up @@ -131,19 +133,26 @@ function MessageAction({ children, onClick }: MessageActionProps) {

type ConversationDispatch = (action: Model.Conversation.Action) => void

interface MessageSharedProps {
interface ConversationDispatchProps {
dispatch: ConversationDispatch
}

interface ConversationStateProps {
state: Model.Conversation.State['_tag']
}

function Message({
type MessageEventProps = ConversationDispatchProps &
ConversationStateProps &
ReturnType<typeof Model.Conversation.Event.Message>

function MessageEvent({
state,
id,
timestamp,
dispatch,
role,
content,
}: MessageSharedProps & ReturnType<typeof Model.Conversation.Event.Message>) {
}: MessageEventProps) {
const discard = React.useMemo(
() =>

Check warning on line 157 in catalog/app/components/Assistant/UI/Chat/Chat.tsx

View check run for this annotation

Codecov / codecov/patch/informational

catalog/app/components/Assistant/UI/Chat/Chat.tsx#L149-L157

Added lines #L149 - L157 were not covered by tests
state === 'Idle' ? () => dispatch(Model.Conversation.Action.Discard({ id })) : null,
Expand All @@ -152,7 +161,7 @@ function Message({
return (

Check warning on line 161 in catalog/app/components/Assistant/UI/Chat/Chat.tsx

View check run for this annotation

Codecov / codecov/patch/informational

catalog/app/components/Assistant/UI/Chat/Chat.tsx#L161

Added line #L161 was not covered by tests
<MessageContainer
role={role}
actions={<>{discard && <MessageAction onClick={discard}>discard</MessageAction>}</>}
actions={discard && <MessageAction onClick={discard}>discard</MessageAction>}
timestamp={timestamp}
>
{Model.Content.MessageContentBlock.$match(content, {
Expand All @@ -164,44 +173,79 @@ function Message({
)
}

type ToolUseEvent = ReturnType<typeof Model.Conversation.Event.ToolUse>
interface ToolUseProps
extends MessageSharedProps,
Omit<ToolUseEvent, '_tag' | 'timestamp' | 'id' | 'result'> {
result?: ToolUseEvent['result']
type ToolUseEventProps = ConversationDispatchProps &
ConversationStateProps &
ReturnType<typeof Model.Conversation.Event.ToolUse>

function ToolUseEvent({
state,
id,
timestamp,
toolUseId,
name,
input,
result,
dispatch,
}: ToolUseEventProps) {
const discard = React.useMemo(
() =>

Check warning on line 191 in catalog/app/components/Assistant/UI/Chat/Chat.tsx

View check run for this annotation

Codecov / codecov/patch/informational

catalog/app/components/Assistant/UI/Chat/Chat.tsx#L181-L191

Added lines #L181 - L191 were not covered by tests
state === 'Idle' ? () => dispatch(Model.Conversation.Action.Discard({ id })) : null,
[dispatch, id, state],
)
const details = React.useMemo(
() => ({ toolUseId, input, result }),

Check warning on line 196 in catalog/app/components/Assistant/UI/Chat/Chat.tsx

View check run for this annotation

Codecov / codecov/patch/informational

catalog/app/components/Assistant/UI/Chat/Chat.tsx#L195-L196

Added lines #L195 - L196 were not covered by tests
[toolUseId, input, result],
)
// FIXME: JsonDisplay expansion doesn't work
return (

Check warning on line 200 in catalog/app/components/Assistant/UI/Chat/Chat.tsx

View check run for this annotation

Codecov / codecov/patch/informational

catalog/app/components/Assistant/UI/Chat/Chat.tsx#L200

Added line #L200 was not covered by tests
<MessageContainer
role="assistant"
timestamp={timestamp}
actions={discard && <MessageAction onClick={discard}>discard</MessageAction>}
>
<span>
Tool Use: <b>{name}</b> ({result.status})
</span>
<M.Box py={0.5}>
{/* @ts-expect-error */}
<JsonDisplay name="details" value={details} />
</M.Box>
</MessageContainer>
)
}

function ToolUse({
// id,
// timestamp,
interface ToolUseStateProps
extends ConversationDispatchProps,
Model.Conversation.ToolCall {
toolUseId: string
timestamp: Date
}

function ToolUseState({
timestamp,
toolUseId,
name,
input,
result, // dispatch,
}: ToolUseProps) {
const details = (
<>
<b>Tool Use ID:</b> {toolUseId}
<br />
<b>Tool Name:</b> {name}
<br />
<b>Input:</b>
<pre>{JSON.stringify(input, null, 2)}</pre>
{!!result && (
<>
<b>Result:</b>
<pre>{JSON.stringify(result, null, 2)}</pre>
</>
)}
</>
dispatch,
}: ToolUseStateProps) {
const abort = React.useCallback(
() => dispatch(Model.Conversation.Action.Abort()),

Check warning on line 232 in catalog/app/components/Assistant/UI/Chat/Chat.tsx

View check run for this annotation

Codecov / codecov/patch/informational

catalog/app/components/Assistant/UI/Chat/Chat.tsx#L225-L232

Added lines #L225 - L232 were not covered by tests
[dispatch],
)
const details = React.useMemo(() => ({ toolUseId, input }), [toolUseId, input])
return (

Check warning on line 236 in catalog/app/components/Assistant/UI/Chat/Chat.tsx

View check run for this annotation

Codecov / codecov/patch/informational

catalog/app/components/Assistant/UI/Chat/Chat.tsx#L235-L236

Added lines #L235 - L236 were not covered by tests
<MessageContainer role="assistant">
<M.Tooltip title={details}>
<span>
Tool Use: <b>{name}</b> ({result?.status ?? 'in progress'})
</span>
</M.Tooltip>
<MessageContainer
role="assistant"
timestamp={timestamp}
actions={<MessageAction onClick={abort}>abort</MessageAction>}
>
<span>
Tool Use: <b>{name}</b> (in progress)
</span>
<M.Box py={0.5}>
{/* @ts-expect-error */}
<JsonDisplay name="details" value={details} />
</M.Box>
</MessageContainer>
)
}
Expand Down Expand Up @@ -319,15 +363,15 @@ export default function Chat({ state, dispatch }: ChatProps) {
.map(
Model.Conversation.Event.$match({
Message: (event) => (

Check warning on line 365 in catalog/app/components/Assistant/UI/Chat/Chat.tsx

View check run for this annotation

Codecov / codecov/patch/informational

catalog/app/components/Assistant/UI/Chat/Chat.tsx#L365

Added line #L365 was not covered by tests
<Message
<MessageEvent
state={state._tag}
key={event.id}
dispatch={dispatch}
{...event}
/>
),
ToolUse: (event) => (

Check warning on line 373 in catalog/app/components/Assistant/UI/Chat/Chat.tsx

View check run for this annotation

Codecov / codecov/patch/informational

catalog/app/components/Assistant/UI/Chat/Chat.tsx#L373

Added line #L373 was not covered by tests
<ToolUse
<ToolUseEvent
state={state._tag}
key={event.id}
dispatch={dispatch}
Expand All @@ -340,7 +384,7 @@ export default function Chat({ state, dispatch }: ChatProps) {
Idle: (s) =>
Eff.Option.match(s.error, {
onSome: (e) => (

Check warning on line 386 in catalog/app/components/Assistant/UI/Chat/Chat.tsx

View check run for this annotation

Codecov / codecov/patch/informational

catalog/app/components/Assistant/UI/Chat/Chat.tsx#L384-L386

Added lines #L384 - L386 were not covered by tests
<MessageContainer role="assistant">
<MessageContainer role="assistant" timestamp={s.timestamp}>
Error occurred:
{e.message}
{e.details}
Expand All @@ -349,14 +393,17 @@ export default function Chat({ state, dispatch }: ChatProps) {
),
onNone: () => null,

Check warning on line 394 in catalog/app/components/Assistant/UI/Chat/Chat.tsx

View check run for this annotation

Codecov / codecov/patch/informational

catalog/app/components/Assistant/UI/Chat/Chat.tsx#L394

Added line #L394 was not covered by tests
}),
WaitingForAssistant: () => (
<MessageContainer role="assistant">Processing...</MessageContainer>
WaitingForAssistant: (s) => (

Check warning on line 396 in catalog/app/components/Assistant/UI/Chat/Chat.tsx

View check run for this annotation

Codecov / codecov/patch/informational

catalog/app/components/Assistant/UI/Chat/Chat.tsx#L396

Added line #L396 was not covered by tests
// TODO: abort
<MessageContainer role="assistant" timestamp={s.timestamp}>
Processing...
</MessageContainer>
),
ToolUse: ({ calls }) =>
ToolUse: ({ calls, timestamp }) =>
Object.entries(calls).map(([id, call]) => (

Check warning on line 403 in catalog/app/components/Assistant/UI/Chat/Chat.tsx

View check run for this annotation

Codecov / codecov/patch/informational

catalog/app/components/Assistant/UI/Chat/Chat.tsx#L402-L403

Added lines #L402 - L403 were not covered by tests
<ToolUse
state={state._tag}
<ToolUseState
key={id}
timestamp={timestamp}
dispatch={dispatch}
toolUseId={id}
{...call}
Expand Down

0 comments on commit fa73d75

Please sign in to comment.