bedrock.js 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. import {
  2. BedrockRuntimeClient,
  3. ConverseCommand,
  4. ConverseStreamCommand
  5. } from '@aws-sdk/client-bedrock-runtime';
  6. /**
  7. * Amazon Bedrock provider — translates OpenAI chat completion requests
  8. * to AWS Bedrock Converse API calls.
  9. */
  10. export function createProvider(config) {
  11. const { id, region, credentials, models } = config;
  12. // Singleton BedrockRuntimeClient per provider config
  13. const client = new BedrockRuntimeClient({
  14. region,
  15. credentials
  16. });
  17. /**
  18. * Translate OpenAI messages[] to Bedrock Converse messages[].
  19. * Extracts system messages into a top-level `system` array.
  20. */
  21. function translateMessages(messages) {
  22. const systemPrompts = [];
  23. const bedrockMessages = [];
  24. for (const msg of messages) {
  25. if (msg.role === 'system') {
  26. systemPrompts.push({ text: msg.content });
  27. } else {
  28. bedrockMessages.push({
  29. role: msg.role,
  30. content: [{ text: msg.content }]
  31. });
  32. }
  33. }
  34. return {
  35. system: systemPrompts.length > 0 ? systemPrompts : undefined,
  36. messages: bedrockMessages
  37. };
  38. }
  39. /**
  40. * Build inferenceConfig from options, only including defined values.
  41. */
  42. function buildInferenceConfig(options) {
  43. const inferenceConfig = {};
  44. if (options.temperature !== undefined) inferenceConfig.temperature = options.temperature;
  45. if (options.max_tokens !== undefined) inferenceConfig.maxTokens = options.max_tokens;
  46. if (options.top_p !== undefined) inferenceConfig.topP = options.top_p;
  47. return Object.keys(inferenceConfig).length > 0 ? inferenceConfig : undefined;
  48. }
  49. /**
  50. * Generate a unique OpenAI-style chat completion ID.
  51. */
  52. function generateId() {
  53. return `chatcmpl-${Date.now()}`;
  54. }
  55. /**
  56. * Build common OpenAI response metadata.
  57. */
  58. function buildMetadata(modelId) {
  59. return {
  60. id: generateId(),
  61. object: 'chat.completion',
  62. created: Math.floor(Date.now() / 1000),
  63. model: modelId
  64. };
  65. }
  66. /**
  67. * Non-streaming chat completion.
  68. */
  69. async function chat(messages, modelId, options = {}) {
  70. const providerModelId = models[modelId];
  71. const { system, messages: bedrockMessages } = translateMessages(messages);
  72. const inferenceConfig = buildInferenceConfig(options);
  73. const command = new ConverseCommand({
  74. modelId: providerModelId,
  75. messages: bedrockMessages,
  76. system,
  77. inferenceConfig
  78. });
  79. let response;
  80. try {
  81. response = await client.send(command);
  82. } catch (error) {
  83. throw new Error(`Bedrock provider request failed: ${error.message}`);
  84. }
  85. const text = response.output?.message?.content?.[0]?.text ?? '';
  86. const usage = response.usage || {};
  87. return {
  88. ...buildMetadata(modelId),
  89. choices: [{
  90. index: 0,
  91. message: {
  92. role: 'assistant',
  93. content: text
  94. },
  95. finish_reason: 'stop'
  96. }],
  97. usage: {
  98. prompt_tokens: usage.inputTokens || 0,
  99. completion_tokens: usage.outputTokens || 0,
  100. total_tokens: usage.totalTokens || 0
  101. }
  102. };
  103. }
  104. /**
  105. * Streaming chat completion — returns an async iterator of OpenAI chunks.
  106. */
  107. async function* chatStream(messages, modelId, options = {}) {
  108. const providerModelId = models[modelId];
  109. const { system, messages: bedrockMessages } = translateMessages(messages);
  110. const inferenceConfig = buildInferenceConfig(options);
  111. const command = new ConverseStreamCommand({
  112. modelId: providerModelId,
  113. messages: bedrockMessages,
  114. system,
  115. inferenceConfig
  116. });
  117. let streamResponse;
  118. try {
  119. streamResponse = await client.send(command);
  120. } catch (error) {
  121. throw new Error(`Bedrock provider stream request failed: ${error.message}`);
  122. }
  123. const metadata = {
  124. id: generateId(),
  125. object: 'chat.completion.chunk',
  126. created: Math.floor(Date.now() / 1000),
  127. model: modelId
  128. };
  129. for await (const event of streamResponse.stream) {
  130. if (event.contentBlockDelta?.delta?.text) {
  131. yield {
  132. ...metadata,
  133. choices: [{
  134. index: 0,
  135. delta: { content: event.contentBlockDelta.delta.text },
  136. finish_reason: null
  137. }]
  138. };
  139. }
  140. if (event.messageStop?.stopReason) {
  141. yield {
  142. ...metadata,
  143. choices: [{
  144. index: 0,
  145. delta: {},
  146. finish_reason: 'stop'
  147. }]
  148. };
  149. }
  150. }
  151. }
  152. return { id, chat, chatStream };
  153. }