Skip to content
Open
Show file tree
Hide file tree
Changes from 4 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
5 changes: 3 additions & 2 deletions .github/workflows/deploy-catalog.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ on:
push:
branches:
- master
- qurator-more-devtools
paths:
- '.github/workflows/deploy-catalog.yaml'
- 'catalog/**'
Expand Down Expand Up @@ -64,5 +65,5 @@ jobs:
-t $ECR_REGISTRY_MP/$ECR_REPOSITORY_MP:$IMAGE_TAG \
.
docker push $ECR_REGISTRY_PROD/$ECR_REPOSITORY:$IMAGE_TAG
docker push $ECR_REGISTRY_GOVCLOUD/$ECR_REPOSITORY:$IMAGE_TAG
docker push $ECR_REGISTRY_MP/$ECR_REPOSITORY_MP:$IMAGE_TAG
# docker push $ECR_REGISTRY_GOVCLOUD/$ECR_REPOSITORY:$IMAGE_TAG
# docker push $ECR_REGISTRY_MP/$ECR_REPOSITORY_MP:$IMAGE_TAG
60 changes: 59 additions & 1 deletion catalog/app/components/Assistant/Model/Assistant.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,74 @@ function usePassThru<T>(val: T) {
return ref
}

export const DEFAULT_MODEL_ID = 'us.anthropic.claude-3-7-sonnet-20250219-v1:0'
const MODEL_ID_KEY = 'QUILT_BEDROCK_MODEL_ID'

function useModelIdOverride() {
const [value, setValue] = React.useState(
() =>
(typeof localStorage !== 'undefined' && localStorage.getItem(MODEL_ID_KEY)) || '',
)

React.useEffect(() => {
if (typeof localStorage !== 'undefined') {
if (value) {
localStorage.setItem(MODEL_ID_KEY, value)
} else {
localStorage.removeItem(MODEL_ID_KEY)
}
}
}, [value])

const modelIdPassThru = usePassThru(value)
const modelIdEff = React.useMemo(
() => Eff.Effect.sync(() => modelIdPassThru.current || DEFAULT_MODEL_ID),
[modelIdPassThru],
)

return [
modelIdEff,
React.useMemo(() => ({ value, setValue }), [value, setValue]),
] as const
}

function useRecording() {
const [enabled, enable] = React.useState(false)
const [log, setLog] = React.useState<string[]>([])

const clear = React.useCallback(() => setLog([]), [])

const enabledPassThru = usePassThru(enabled)
const record = React.useCallback(
(entry: string) =>
Eff.Effect.sync(() => {
if (enabledPassThru.current) setLog((l) => l.concat(entry))
}),
[enabledPassThru],
)

return [
record,
React.useMemo(() => ({ enabled, log, enable, clear }), [enabled, log, enable, clear]),
] as const
}

function useConstructAssistantAPI() {
const [modelId, modelIdOverride] = useModelIdOverride()
const [record, recording] = useRecording()

const passThru = usePassThru({
bedrock: AWS.Bedrock.useClient(),
context: Context.useLayer(),
})

const layerEff = Eff.Effect.sync(() =>
Eff.Layer.merge(
Bedrock.LLMBedrock(passThru.current.bedrock),
Bedrock.LLMBedrock(passThru.current.bedrock, { modelId, record }),
passThru.current.context,
),
)

const [state, dispatch] = Actor.useActorLayer(
Conversation.ConversationActor,
Conversation.init,
Expand Down Expand Up @@ -59,6 +116,7 @@ function useConstructAssistantAPI() {
assist,
state,
dispatch,
devTools: { recording, modelIdOverride },
}
}

Expand Down
78 changes: 44 additions & 34 deletions catalog/app/components/Assistant/Model/Bedrock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ import * as LLM from './LLM'

const MODULE = 'Bedrock'

const MODEL_ID = 'us.anthropic.claude-3-7-sonnet-20250219-v1:0'
interface BedrockOptions {
modelId: Eff.Effect.Effect<string>
record?: (r: string) => Eff.Effect.Effect<void>
}

const mapContent = (contentBlocks: BedrockRuntime.ContentBlocks | undefined) =>
Eff.pipe(
Expand Down Expand Up @@ -116,43 +119,50 @@ function isAWSError(e: any): e is AWSSDK.AWSError {
}

// a layer providing the service over aws.bedrock
export function LLMBedrock(bedrock: BedrockRuntime) {
export function LLMBedrock(bedrock: BedrockRuntime, options: BedrockOptions) {
const converse = (prompt: LLM.Prompt, opts?: LLM.Options) =>
Log.scoped({
name: `${MODULE}.converse`,
enter: [
Log.br,
'model id:',
MODEL_ID,
Log.br,
'prompt:',
prompt,
Log.br,
'opts:',
opts,
],
enter: [Log.br, 'prompt:', prompt, Log.br, 'opts:', opts],
})(
Eff.Effect.tryPromise({
try: () =>
bedrock
.converse({
modelId: MODEL_ID,
system: [{ text: prompt.system }],
messages: messagesToBedrock(prompt.messages),
toolConfig: prompt.toolConfig && toolConfigToBedrock(prompt.toolConfig),
...opts,
})
.promise()
.then((backendResponse) => ({
backendResponse,
content: mapContent(backendResponse.output.message?.content),
})),
catch: (e) =>
new LLM.LLMError({
message: isAWSError(e)
? `Bedrock error (${e.code}): ${e.message}`
: `Unexpected error: ${e}`,
}),
Eff.Effect.gen(function* () {
const requestTimestamp = new Date(yield* Eff.Clock.currentTimeMillis)
const modelId = yield* options.modelId
const requestBody = {
modelId,
system: [{ text: prompt.system }],
messages: messagesToBedrock(prompt.messages),
toolConfig: prompt.toolConfig && toolConfigToBedrock(prompt.toolConfig),
...opts,
}
const backendResponse = yield* Eff.Effect.tryPromise({
try: () => bedrock.converse(requestBody).promise(),
catch: (e) =>
new LLM.LLMError({
message: isAWSError(e)
? `Bedrock error (${e.code}): ${e.message}`
: `Unexpected error: ${e}`,
}),
})
const responseTimestamp = new Date(yield* Eff.Clock.currentTimeMillis)
if (options.record) {
const entry = JSON.stringify(
{
requestTimestamp,
responseTimestamp,
modelId,
request: requestBody,
response: backendResponse,
},
null,
2,
)
yield* options.record(entry)
}
return {
backendResponse,
content: mapContent(backendResponse.output.message?.content),
}
}),
)

Expand Down
5 changes: 3 additions & 2 deletions catalog/app/components/Assistant/UI/Chat/Chat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -387,9 +387,10 @@ const useStyles = M.makeStyles((t) => ({
interface ChatProps {
state: Model.Assistant.API['state']
dispatch: Model.Assistant.API['dispatch']
devTools: Model.Assistant.API['devTools']
}

export default function Chat({ state, dispatch }: ChatProps) {
export default function Chat({ state, dispatch, devTools }: ChatProps) {
const classes = useStyles()
const scrollRef = React.useRef<HTMLDivElement>(null)

Expand Down Expand Up @@ -431,7 +432,7 @@ export default function Chat({ state, dispatch }: ChatProps) {
/>
<M.Slide direction="down" mountOnEnter unmountOnExit in={devToolsOpen}>
<M.Paper square className={classes.devTools}>
<DevTools state={state} />
<DevTools state={state} {...devTools} />
</M.Paper>
</M.Slide>
<div className={classes.historyContainer}>
Expand Down
Loading
Loading