@wmaurer/effect-ai-amazon-bedrock 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +11 -0
- package/README.md +79 -3
- package/dist/AmazonBedrockClient.d.ts +2 -2
- package/dist/AmazonBedrockClient.d.ts.map +1 -1
- package/dist/AmazonBedrockLanguageModel.d.ts +188 -25
- package/dist/AmazonBedrockLanguageModel.d.ts.map +1 -1
- package/dist/AmazonBedrockLanguageModel.js +133 -16
- package/dist/AmazonBedrockLanguageModel.js.map +1 -1
- package/dist/AmazonBedrockSchema.d.ts +216 -4
- package/dist/AmazonBedrockSchema.d.ts.map +1 -1
- package/dist/AmazonBedrockSchema.js +175 -9
- package/dist/AmazonBedrockSchema.js.map +1 -1
- package/dist/internal/errors.d.ts.map +1 -1
- package/dist/internal/errors.js +8 -0
- package/dist/internal/errors.js.map +1 -1
- package/dist/internal/guardrails.d.ts +15 -0
- package/dist/internal/guardrails.d.ts.map +1 -0
- package/dist/internal/guardrails.js +77 -0
- package/dist/internal/guardrails.js.map +1 -0
- package/package.json +1 -1
- package/src/AmazonBedrockClient.ts +2 -2
- package/src/AmazonBedrockLanguageModel.ts +309 -35
- package/src/AmazonBedrockSchema.ts +193 -9
- package/src/internal/errors.ts +14 -0
- package/src/internal/guardrails.ts +91 -0
|
@@ -377,6 +377,79 @@ export class CitationsContentBlock extends Schema.Class<CitationsContentBlock>(
|
|
|
377
377
|
citations: Schema.optional(Schema.Array(Citation))
|
|
378
378
|
}) {}
|
|
379
379
|
|
|
380
|
+
/**
|
|
381
|
+
* The role a guarded text block plays in a contextual grounding check.
|
|
382
|
+
*
|
|
383
|
+
* **Details**
|
|
384
|
+
*
|
|
385
|
+
* `grounding_source` marks the documents an answer must be grounded in,
|
|
386
|
+
* `query` marks the question, and `guard_content` marks text the guardrail
|
|
387
|
+
* evaluates without a grounding role.
|
|
388
|
+
*
|
|
389
|
+
* @stability unstable
|
|
390
|
+
* @category schemas
|
|
391
|
+
* @since 0.4.0
|
|
392
|
+
*/
|
|
393
|
+
export const GuardrailConverseContentQualifier = Schema.Literals(["grounding_source", "query", "guard_content"])
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* Text that the guardrail evaluates.
|
|
397
|
+
*
|
|
398
|
+
* **Details**
|
|
399
|
+
*
|
|
400
|
+
* `qualifiers` uses `optionalKey` for the same reason as
|
|
401
|
+
* `CachePointBlock.ttl`: the block is built from `Prompt` provider options,
|
|
402
|
+
* whose values must satisfy `Schema.Json`.
|
|
403
|
+
*
|
|
404
|
+
* @stability unstable
|
|
405
|
+
* @category schemas
|
|
406
|
+
* @since 0.4.0
|
|
407
|
+
*/
|
|
408
|
+
export class GuardrailConverseTextBlock extends Schema.Class<GuardrailConverseTextBlock>(
|
|
409
|
+
makeIdentifier("GuardrailConverseTextBlock")
|
|
410
|
+
)({
|
|
411
|
+
text: Schema.String,
|
|
412
|
+
qualifiers: Schema.optionalKey(Schema.Array(GuardrailConverseContentQualifier))
|
|
413
|
+
}) {}
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* An image that the guardrail evaluates.
|
|
417
|
+
*
|
|
418
|
+
* **Details**
|
|
419
|
+
*
|
|
420
|
+
* The Smithy model allows only png and jpeg, and only `bytes` as the source.
|
|
421
|
+
* `bytes` is a Smithy blob, which the JSON protocol carries as a base64
|
|
422
|
+
* string.
|
|
423
|
+
*
|
|
424
|
+
* @stability unstable
|
|
425
|
+
* @category schemas
|
|
426
|
+
* @since 0.4.0
|
|
427
|
+
*/
|
|
428
|
+
export class GuardrailConverseImageBlock extends Schema.Class<GuardrailConverseImageBlock>(
|
|
429
|
+
makeIdentifier("GuardrailConverseImageBlock")
|
|
430
|
+
)({
|
|
431
|
+
format: Schema.Literals(["png", "jpeg"]),
|
|
432
|
+
source: Schema.Struct({ bytes: Schema.String })
|
|
433
|
+
}) {}
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* Content that the guardrail evaluates, in a message or in the system prompt.
|
|
437
|
+
*
|
|
438
|
+
* **Details**
|
|
439
|
+
*
|
|
440
|
+
* AWS models `GuardrailConverseContentBlock` as a UNION, so both members are
|
|
441
|
+
* optional here. Once a request holds one `guardContent` block, the guardrail
|
|
442
|
+
* evaluates only the guarded blocks. The model still reads every block.
|
|
443
|
+
*
|
|
444
|
+
* @stability unstable
|
|
445
|
+
* @category schemas
|
|
446
|
+
* @since 0.4.0
|
|
447
|
+
*/
|
|
448
|
+
export const GuardrailConverseContentBlock = Schema.Struct({
|
|
449
|
+
text: Schema.optional(GuardrailConverseTextBlock),
|
|
450
|
+
image: Schema.optional(GuardrailConverseImageBlock)
|
|
451
|
+
})
|
|
452
|
+
|
|
380
453
|
/**
|
|
381
454
|
* A text content block within a Converse message.
|
|
382
455
|
*
|
|
@@ -401,7 +474,8 @@ export const ContentBlock = Schema.Struct({
|
|
|
401
474
|
image: Schema.optional(ImageBlock),
|
|
402
475
|
document: Schema.optional(DocumentBlock),
|
|
403
476
|
cachePoint: Schema.optional(CachePointBlock),
|
|
404
|
-
citationsContent: Schema.optional(CitationsContentBlock)
|
|
477
|
+
citationsContent: Schema.optional(CitationsContentBlock),
|
|
478
|
+
guardContent: Schema.optional(GuardrailConverseContentBlock)
|
|
405
479
|
})
|
|
406
480
|
|
|
407
481
|
/**
|
|
@@ -421,8 +495,9 @@ export class Message extends Schema.Class<Message>(makeIdentifier("Message"))({
|
|
|
421
495
|
*
|
|
422
496
|
* **Details**
|
|
423
497
|
*
|
|
424
|
-
* AWS models `SystemContentBlock` as a UNION, so
|
|
425
|
-
* block carries
|
|
498
|
+
* AWS models `SystemContentBlock` as a UNION, so every member is optional; a
|
|
499
|
+
* block carries the system text, guarded system text, or a cache point. The
|
|
500
|
+
* guardrail evaluates the system prompt only where it is guarded.
|
|
426
501
|
*
|
|
427
502
|
* @stability unstable
|
|
428
503
|
* @category schemas
|
|
@@ -430,7 +505,8 @@ export class Message extends Schema.Class<Message>(makeIdentifier("Message"))({
|
|
|
430
505
|
*/
|
|
431
506
|
export const SystemContentBlock = Schema.Struct({
|
|
432
507
|
text: Schema.optional(Schema.String),
|
|
433
|
-
cachePoint: Schema.optional(CachePointBlock)
|
|
508
|
+
cachePoint: Schema.optional(CachePointBlock),
|
|
509
|
+
guardContent: Schema.optional(GuardrailConverseContentBlock)
|
|
434
510
|
})
|
|
435
511
|
|
|
436
512
|
/**
|
|
@@ -578,7 +654,80 @@ export const OutputConfig = Schema.Struct({
|
|
|
578
654
|
})
|
|
579
655
|
|
|
580
656
|
/**
|
|
581
|
-
*
|
|
657
|
+
* Whether Bedrock returns the guardrail trace.
|
|
658
|
+
*
|
|
659
|
+
* **Details**
|
|
660
|
+
*
|
|
661
|
+
* Bedrock's default is `disabled`. The trace holds the text that a policy
|
|
662
|
+
* matched, unmasked. `enabled_full` is in the Smithy model, and AWS does not
|
|
663
|
+
* document it.
|
|
664
|
+
*
|
|
665
|
+
* @stability unstable
|
|
666
|
+
* @category schemas
|
|
667
|
+
* @since 0.4.0
|
|
668
|
+
*/
|
|
669
|
+
export const GuardrailTrace = Schema.Literals(["enabled", "disabled", "enabled_full"])
|
|
670
|
+
|
|
671
|
+
/**
|
|
672
|
+
* How ConverseStream applies a guardrail to the streamed output.
|
|
673
|
+
*
|
|
674
|
+
* **Details**
|
|
675
|
+
*
|
|
676
|
+
* `sync`, Bedrock's default, holds back each chunk until the guardrail has
|
|
677
|
+
* evaluated it. `async` sends chunks at once, so text that the guardrail
|
|
678
|
+
* later blocks can reach the caller, and masking does not apply.
|
|
679
|
+
*
|
|
680
|
+
* @stability unstable
|
|
681
|
+
* @category schemas
|
|
682
|
+
* @since 0.4.0
|
|
683
|
+
*/
|
|
684
|
+
export const GuardrailStreamProcessingMode = Schema.Literals(["sync", "async"])
|
|
685
|
+
|
|
686
|
+
// Smithy gives the identifier and the version `default: ""`, and Bedrock cannot
|
|
687
|
+
// apply a guardrail without them, so both are required here.
|
|
688
|
+
const guardrailFields = {
|
|
689
|
+
guardrailIdentifier: Schema.String,
|
|
690
|
+
guardrailVersion: Schema.String,
|
|
691
|
+
trace: Schema.optionalKey(GuardrailTrace)
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
/**
|
|
695
|
+
* The guardrail of a Converse request.
|
|
696
|
+
*
|
|
697
|
+
* **Details**
|
|
698
|
+
*
|
|
699
|
+
* `guardrailIdentifier` is the guardrail's ID or ARN, and `guardrailVersion`
|
|
700
|
+
* is a version number or `DRAFT`. Converse has no `streamProcessingMode`, so
|
|
701
|
+
* the member is typed `never`: a ConverseStream request does not fit Converse
|
|
702
|
+
* until the mode is removed.
|
|
703
|
+
*
|
|
704
|
+
* @stability unstable
|
|
705
|
+
* @category schemas
|
|
706
|
+
* @since 0.4.0
|
|
707
|
+
*/
|
|
708
|
+
export class GuardrailConfiguration extends Schema.Class<GuardrailConfiguration>(
|
|
709
|
+
makeIdentifier("GuardrailConfiguration")
|
|
710
|
+
)({
|
|
711
|
+
...guardrailFields,
|
|
712
|
+
streamProcessingMode: Schema.optionalKey(Schema.Never)
|
|
713
|
+
}) {}
|
|
714
|
+
|
|
715
|
+
/**
|
|
716
|
+
* The guardrail of a ConverseStream request.
|
|
717
|
+
*
|
|
718
|
+
* @stability unstable
|
|
719
|
+
* @category schemas
|
|
720
|
+
* @since 0.4.0
|
|
721
|
+
*/
|
|
722
|
+
export class GuardrailStreamConfiguration extends Schema.Class<GuardrailStreamConfiguration>(
|
|
723
|
+
makeIdentifier("GuardrailStreamConfiguration")
|
|
724
|
+
)({
|
|
725
|
+
...guardrailFields,
|
|
726
|
+
streamProcessingMode: Schema.optionalKey(GuardrailStreamProcessingMode)
|
|
727
|
+
}) {}
|
|
728
|
+
|
|
729
|
+
/**
|
|
730
|
+
* The request payload for the Converse operation.
|
|
582
731
|
*
|
|
583
732
|
* **Details**
|
|
584
733
|
*
|
|
@@ -602,9 +751,30 @@ export class ConverseRequest extends Schema.Class<ConverseRequest>(makeIdentifie
|
|
|
602
751
|
toolConfig: Schema.optionalKey(ToolConfiguration),
|
|
603
752
|
inferenceConfig: Schema.optional(InferenceConfiguration),
|
|
604
753
|
outputConfig: Schema.optionalKey(OutputConfig),
|
|
605
|
-
additionalModelRequestFields: Schema.optional(Schema.Record(Schema.String, Schema.Unknown))
|
|
754
|
+
additionalModelRequestFields: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)),
|
|
755
|
+
guardrailConfig: Schema.optional(GuardrailConfiguration)
|
|
606
756
|
}) {}
|
|
607
757
|
|
|
758
|
+
/**
|
|
759
|
+
* The request payload for the ConverseStream operation.
|
|
760
|
+
*
|
|
761
|
+
* **Details**
|
|
762
|
+
*
|
|
763
|
+
* The members of `ConverseRequest`, with the guardrail of a stream, which
|
|
764
|
+
* adds `streamProcessingMode`. That member is the only difference between the
|
|
765
|
+
* two operations.
|
|
766
|
+
*
|
|
767
|
+
* @stability unstable
|
|
768
|
+
* @category schemas
|
|
769
|
+
* @since 0.4.0
|
|
770
|
+
*/
|
|
771
|
+
export class ConverseStreamRequest
|
|
772
|
+
extends Schema.Class<ConverseStreamRequest>(makeIdentifier("ConverseStreamRequest"))({
|
|
773
|
+
...ConverseRequest.fields,
|
|
774
|
+
guardrailConfig: Schema.optional(GuardrailStreamConfiguration)
|
|
775
|
+
})
|
|
776
|
+
{}
|
|
777
|
+
|
|
608
778
|
// =============================================================================
|
|
609
779
|
// Response
|
|
610
780
|
// =============================================================================
|
|
@@ -634,6 +804,12 @@ export class ConverseMetrics extends Schema.Class<ConverseMetrics>(makeIdentifie
|
|
|
634
804
|
/**
|
|
635
805
|
* The response from a successful Converse call.
|
|
636
806
|
*
|
|
807
|
+
* **Details**
|
|
808
|
+
*
|
|
809
|
+
* `trace` is kept as the JSON Bedrock sent. Its Smithy tree has about 90
|
|
810
|
+
* shapes that AWS still extends, and a struct would drop every member added
|
|
811
|
+
* later.
|
|
812
|
+
*
|
|
637
813
|
* @stability unstable
|
|
638
814
|
* @category schemas
|
|
639
815
|
* @since 0.1.0
|
|
@@ -642,7 +818,8 @@ export class ConverseResponse extends Schema.Class<ConverseResponse>(makeIdentif
|
|
|
642
818
|
output: ConverseOutput,
|
|
643
819
|
usage: TokenUsage,
|
|
644
820
|
stopReason: StopReason,
|
|
645
|
-
metrics: Schema.optional(ConverseMetrics)
|
|
821
|
+
metrics: Schema.optional(ConverseMetrics),
|
|
822
|
+
trace: Schema.optional(Schema.MutableJson)
|
|
646
823
|
}) {}
|
|
647
824
|
|
|
648
825
|
// =============================================================================
|
|
@@ -823,7 +1000,13 @@ export class MessageStopEvent extends Schema.Class<MessageStopEvent>(
|
|
|
823
1000
|
}) {}
|
|
824
1001
|
|
|
825
1002
|
/**
|
|
826
|
-
* The trailing metadata event for a Converse stream, carrying usage
|
|
1003
|
+
* The trailing metadata event for a Converse stream, carrying usage, metrics,
|
|
1004
|
+
* and the trace.
|
|
1005
|
+
*
|
|
1006
|
+
* **Details**
|
|
1007
|
+
*
|
|
1008
|
+
* This is the only stream event that carries the trace. It is kept as JSON,
|
|
1009
|
+
* as in `ConverseResponse`.
|
|
827
1010
|
*
|
|
828
1011
|
* @stability unstable
|
|
829
1012
|
* @category schemas
|
|
@@ -833,7 +1016,8 @@ export class ConverseStreamMetadataEvent extends Schema.Class<ConverseStreamMeta
|
|
|
833
1016
|
makeIdentifier("ConverseStreamMetadataEvent")
|
|
834
1017
|
)({
|
|
835
1018
|
usage: TokenUsage,
|
|
836
|
-
metrics: Schema.optional(ConverseMetrics)
|
|
1019
|
+
metrics: Schema.optional(ConverseMetrics),
|
|
1020
|
+
trace: Schema.optional(Schema.MutableJson)
|
|
837
1021
|
}) {}
|
|
838
1022
|
|
|
839
1023
|
const ExceptionBody = Schema.Struct({
|
package/src/internal/errors.ts
CHANGED
|
@@ -220,9 +220,23 @@ const buildInvalidRequestDescription = (params: {
|
|
|
220
220
|
parts.push(`Response: ${truncated}`)
|
|
221
221
|
}
|
|
222
222
|
|
|
223
|
+
if (params.message && dataRetentionRejection.test(params.message)) {
|
|
224
|
+
parts.push(dataRetentionHint)
|
|
225
|
+
}
|
|
226
|
+
|
|
223
227
|
return parts.join(" ")
|
|
224
228
|
}
|
|
225
229
|
|
|
230
|
+
// Converse has no request field for the data retention mode, so the AWS message
|
|
231
|
+
// alone leaves the reader looking for one. Only an account-level setting in each
|
|
232
|
+
// region, which the control plane sets, makes such a model callable.
|
|
233
|
+
const dataRetentionRejection = /data retention mode '[^']*' is not available for this model/
|
|
234
|
+
|
|
235
|
+
const dataRetentionHint = "The account's data retention mode in this region does not allow this model." +
|
|
236
|
+
" Claude Fable 5 and 5.1 need `aws_review`." +
|
|
237
|
+
" Set the mode with the Bedrock PutAccountDataRetention operation:" +
|
|
238
|
+
" https://docs.aws.amazon.com/bedrock/latest/userguide/data-retention.html"
|
|
239
|
+
|
|
226
240
|
/**
|
|
227
241
|
* AWS sends the exception shape as `Name:http://internal.amazon.com/coral/...`.
|
|
228
242
|
* Only the part before the colon identifies it.
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import type * as Schema from "effect/Schema"
|
|
2
|
+
import type { FinishMetadata, GuardrailAction, GuardrailOutcome } from "../AmazonBedrockLanguageModel.ts"
|
|
3
|
+
|
|
4
|
+
const strength: Record<GuardrailAction, number> = { none: 0, masked: 1, blocked: 2 }
|
|
5
|
+
|
|
6
|
+
const stronger = (a: GuardrailAction, b: GuardrailAction): GuardrailAction => strength[b] > strength[a] ? b : a
|
|
7
|
+
|
|
8
|
+
const isObject = (value: unknown): value is Schema.MutableJsonObject =>
|
|
9
|
+
typeof value === "object" && value !== null && !Array.isArray(value)
|
|
10
|
+
|
|
11
|
+
const actionOf = (value: string): GuardrailAction =>
|
|
12
|
+
value === "BLOCKED" ? "blocked" : value === "ANONYMIZED" ? "masked" : "none"
|
|
13
|
+
|
|
14
|
+
const strongestAction = (value: Schema.MutableJson): GuardrailAction => {
|
|
15
|
+
let action: GuardrailAction = "none"
|
|
16
|
+
if (Array.isArray(value)) {
|
|
17
|
+
for (const item of value) {
|
|
18
|
+
action = stronger(action, strongestAction(item))
|
|
19
|
+
}
|
|
20
|
+
} else if (isObject(value)) {
|
|
21
|
+
for (const [key, member] of Object.entries(value)) {
|
|
22
|
+
action = stronger(
|
|
23
|
+
action,
|
|
24
|
+
key === "action" && typeof member === "string" ? actionOf(member) : strongestAction(member)
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return action
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const assessSide = (assessments: ReadonlyArray<Schema.MutableJson>, policies: Array<string>): GuardrailAction => {
|
|
32
|
+
let side: GuardrailAction = "none"
|
|
33
|
+
for (const assessment of assessments) {
|
|
34
|
+
if (!isObject(assessment)) {
|
|
35
|
+
continue
|
|
36
|
+
}
|
|
37
|
+
for (const [policy, member] of Object.entries(assessment)) {
|
|
38
|
+
const action = strongestAction(member)
|
|
39
|
+
if (action !== "none" && !policies.includes(policy)) {
|
|
40
|
+
policies.push(policy)
|
|
41
|
+
}
|
|
42
|
+
side = stronger(side, action)
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return side
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** @internal */
|
|
49
|
+
export const guardrailOutcome = (trace: Schema.MutableJson | undefined): GuardrailOutcome | null => {
|
|
50
|
+
const guardrail = isObject(trace) ? trace.guardrail : undefined
|
|
51
|
+
if (!isObject(guardrail)) {
|
|
52
|
+
return null
|
|
53
|
+
}
|
|
54
|
+
const inputAssessment = guardrail.inputAssessment
|
|
55
|
+
const outputAssessments = guardrail.outputAssessments
|
|
56
|
+
const policies: Array<string> = []
|
|
57
|
+
const input = assessSide(isObject(inputAssessment) ? Object.values(inputAssessment) : [], policies)
|
|
58
|
+
const output = assessSide(
|
|
59
|
+
isObject(outputAssessments)
|
|
60
|
+
? Object.values(outputAssessments).flatMap((chunks) => Array.isArray(chunks) ? chunks : [])
|
|
61
|
+
: [],
|
|
62
|
+
policies
|
|
63
|
+
)
|
|
64
|
+
return { input, output, policies }
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** @internal */
|
|
68
|
+
export const finishMetadata = (stopReason: string | null, trace: Schema.MutableJson | undefined): FinishMetadata => ({
|
|
69
|
+
stopReason,
|
|
70
|
+
guardrail: guardrailOutcome(trace),
|
|
71
|
+
trace: trace ?? null
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
const isJson = (text: string): boolean => {
|
|
75
|
+
try {
|
|
76
|
+
JSON.parse(text)
|
|
77
|
+
return true
|
|
78
|
+
} catch {
|
|
79
|
+
return false
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Bedrock stops with `guardrail_intervened` both when a guardrail blocks,
|
|
85
|
+
* replacing the text with the guardrail's blocked message, and when it masks,
|
|
86
|
+
* leaving the object JSON.
|
|
87
|
+
*
|
|
88
|
+
* @internal
|
|
89
|
+
*/
|
|
90
|
+
export const blocksObject = (stopReason: string, text: string): boolean =>
|
|
91
|
+
stopReason === "guardrail_intervened" && !isJson(text)
|