ResponsesParser.mjs 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. import { OpenAIError } from "../error.mjs";
  2. import { isAutoParsableResponseFormat } from "../lib/parser.mjs";
  3. export function maybeParseResponse(response, params) {
  4. if (!params || !hasAutoParseableInput(params)) {
  5. return {
  6. ...response,
  7. output_parsed: null,
  8. output: response.output.map((item) => {
  9. if (item.type === 'function_call') {
  10. return {
  11. ...item,
  12. parsed_arguments: null,
  13. };
  14. }
  15. if (item.type === 'message') {
  16. return {
  17. ...item,
  18. content: item.content.map((content) => ({
  19. ...content,
  20. parsed: null,
  21. })),
  22. };
  23. }
  24. else {
  25. return item;
  26. }
  27. }),
  28. };
  29. }
  30. return parseResponse(response, params);
  31. }
  32. export function parseResponse(response, params) {
  33. const output = response.output.map((item) => {
  34. if (item.type === 'function_call') {
  35. return {
  36. ...item,
  37. parsed_arguments: parseToolCall(params, item),
  38. };
  39. }
  40. if (item.type === 'message') {
  41. const content = item.content.map((content) => {
  42. if (content.type === 'output_text') {
  43. return {
  44. ...content,
  45. parsed: parseTextFormat(params, content.text),
  46. };
  47. }
  48. return content;
  49. });
  50. return {
  51. ...item,
  52. content,
  53. };
  54. }
  55. return item;
  56. });
  57. const parsed = Object.assign({}, response, { output });
  58. if (!Object.getOwnPropertyDescriptor(response, 'output_text')) {
  59. addOutputText(parsed);
  60. }
  61. Object.defineProperty(parsed, 'output_parsed', {
  62. enumerable: true,
  63. get() {
  64. for (const output of parsed.output) {
  65. if (output.type !== 'message') {
  66. continue;
  67. }
  68. for (const content of output.content) {
  69. if (content.type === 'output_text' && content.parsed !== null) {
  70. return content.parsed;
  71. }
  72. }
  73. }
  74. return null;
  75. },
  76. });
  77. return parsed;
  78. }
  79. function parseTextFormat(params, content) {
  80. if (params.text?.format?.type !== 'json_schema') {
  81. return null;
  82. }
  83. if ('$parseRaw' in params.text?.format) {
  84. const text_format = params.text?.format;
  85. return text_format.$parseRaw(content);
  86. }
  87. return JSON.parse(content);
  88. }
  89. export function hasAutoParseableInput(params) {
  90. if (isAutoParsableResponseFormat(params.text?.format)) {
  91. return true;
  92. }
  93. return false;
  94. }
  95. export function makeParseableResponseTool(tool, { parser, callback, }) {
  96. const obj = { ...tool };
  97. Object.defineProperties(obj, {
  98. $brand: {
  99. value: 'auto-parseable-tool',
  100. enumerable: false,
  101. },
  102. $parseRaw: {
  103. value: parser,
  104. enumerable: false,
  105. },
  106. $callback: {
  107. value: callback,
  108. enumerable: false,
  109. },
  110. });
  111. return obj;
  112. }
  113. export function isAutoParsableTool(tool) {
  114. return tool?.['$brand'] === 'auto-parseable-tool';
  115. }
  116. function getInputToolByName(input_tools, name) {
  117. return input_tools.find((tool) => tool.type === 'function' && tool.name === name);
  118. }
  119. function parseToolCall(params, toolCall) {
  120. const inputTool = getInputToolByName(params.tools ?? [], toolCall.name);
  121. return {
  122. ...toolCall,
  123. ...toolCall,
  124. parsed_arguments: isAutoParsableTool(inputTool) ? inputTool.$parseRaw(toolCall.arguments)
  125. : inputTool?.strict ? JSON.parse(toolCall.arguments)
  126. : null,
  127. };
  128. }
  129. export function shouldParseToolCall(params, toolCall) {
  130. if (!params) {
  131. return false;
  132. }
  133. const inputTool = getInputToolByName(params.tools ?? [], toolCall.name);
  134. return isAutoParsableTool(inputTool) || inputTool?.strict || false;
  135. }
  136. export function validateInputTools(tools) {
  137. for (const tool of tools ?? []) {
  138. if (tool.type !== 'function') {
  139. throw new OpenAIError(`Currently only \`function\` tool types support auto-parsing; Received \`${tool.type}\``);
  140. }
  141. if (tool.function.strict !== true) {
  142. throw new OpenAIError(`The \`${tool.function.name}\` tool is not marked with \`strict: true\`. Only strict function tools can be auto-parsed`);
  143. }
  144. }
  145. }
  146. export function addOutputText(rsp) {
  147. const texts = [];
  148. for (const output of rsp.output) {
  149. if (output.type !== 'message') {
  150. continue;
  151. }
  152. for (const content of output.content) {
  153. if (content.type === 'output_text') {
  154. texts.push(content.text);
  155. }
  156. }
  157. }
  158. rsp.output_text = texts.join('');
  159. }
  160. //# sourceMappingURL=ResponsesParser.mjs.map