openai.py 46 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350
  1. import sys
  2. import json
  3. import time
  4. from functools import wraps
  5. from collections.abc import Iterable
  6. import sentry_sdk
  7. from sentry_sdk import consts
  8. from sentry_sdk.ai.monitoring import record_token_usage
  9. from sentry_sdk.ai.utils import (
  10. set_data_normalized,
  11. normalize_message_roles,
  12. truncate_and_annotate_messages,
  13. truncate_and_annotate_embedding_inputs,
  14. )
  15. from sentry_sdk.ai._openai_completions_api import (
  16. _is_system_instruction as _is_system_instruction_completions,
  17. _get_system_instructions as _get_system_instructions_completions,
  18. _transform_system_instructions,
  19. _get_text_items,
  20. )
  21. from sentry_sdk.ai._openai_responses_api import (
  22. _is_system_instruction as _is_system_instruction_responses,
  23. _get_system_instructions as _get_system_instructions_responses,
  24. )
  25. from sentry_sdk.consts import SPANDATA
  26. from sentry_sdk.integrations import DidNotEnable, Integration
  27. from sentry_sdk.scope import should_send_default_pii
  28. from sentry_sdk.tracing_utils import set_span_errored
  29. from sentry_sdk.utils import (
  30. capture_internal_exceptions,
  31. event_from_exception,
  32. safe_serialize,
  33. reraise,
  34. )
  35. from typing import TYPE_CHECKING
  36. if TYPE_CHECKING:
  37. from typing import (
  38. Any,
  39. List,
  40. Optional,
  41. Callable,
  42. AsyncIterator,
  43. Iterator,
  44. Union,
  45. Iterable,
  46. )
  47. from sentry_sdk.tracing import Span
  48. from sentry_sdk._types import TextPart
  49. from openai.types.responses.response_usage import ResponseUsage
  50. from openai.types.responses import (
  51. ResponseInputParam,
  52. SequenceNotStr,
  53. ResponseStreamEvent,
  54. )
  55. from openai.types import CompletionUsage
  56. from openai import Omit
  57. try:
  58. try:
  59. from openai import NotGiven
  60. except ImportError:
  61. NotGiven = None
  62. try:
  63. from openai import Omit
  64. except ImportError:
  65. Omit = None
  66. from openai.resources.chat.completions import Completions, AsyncCompletions
  67. from openai.resources import Embeddings, AsyncEmbeddings
  68. from openai import Stream, AsyncStream
  69. if TYPE_CHECKING:
  70. from openai.types.chat import (
  71. ChatCompletionMessageParam,
  72. ChatCompletionChunk,
  73. )
  74. except ImportError:
  75. raise DidNotEnable("OpenAI not installed")
  76. RESPONSES_API_ENABLED = True
  77. try:
  78. # responses API support was introduced in v1.66.0
  79. from openai.resources.responses import Responses, AsyncResponses
  80. from openai.types.responses.response_completed_event import ResponseCompletedEvent
  81. except ImportError:
  82. RESPONSES_API_ENABLED = False
  83. class OpenAIIntegration(Integration):
  84. identifier = "openai"
  85. origin = f"auto.ai.{identifier}"
  86. def __init__(
  87. self: "OpenAIIntegration",
  88. include_prompts: bool = True,
  89. tiktoken_encoding_name: "Optional[str]" = None,
  90. ) -> None:
  91. self.include_prompts = include_prompts
  92. self.tiktoken_encoding = None
  93. if tiktoken_encoding_name is not None:
  94. import tiktoken # type: ignore
  95. self.tiktoken_encoding = tiktoken.get_encoding(tiktoken_encoding_name)
  96. @staticmethod
  97. def setup_once() -> None:
  98. Completions.create = _wrap_chat_completion_create(Completions.create)
  99. AsyncCompletions.create = _wrap_async_chat_completion_create(
  100. AsyncCompletions.create
  101. )
  102. Embeddings.create = _wrap_embeddings_create(Embeddings.create)
  103. AsyncEmbeddings.create = _wrap_async_embeddings_create(AsyncEmbeddings.create)
  104. if RESPONSES_API_ENABLED:
  105. Responses.create = _wrap_responses_create(Responses.create)
  106. AsyncResponses.create = _wrap_async_responses_create(AsyncResponses.create)
  107. def count_tokens(self: "OpenAIIntegration", s: str) -> int:
  108. if self.tiktoken_encoding is None:
  109. return 0
  110. try:
  111. return len(self.tiktoken_encoding.encode_ordinary(s))
  112. except Exception:
  113. return 0
  114. def _capture_exception(exc: "Any", manual_span_cleanup: bool = True) -> None:
  115. # Close an eventually open span
  116. # We need to do this by hand because we are not using the start_span context manager
  117. current_span = sentry_sdk.get_current_span()
  118. set_span_errored(current_span)
  119. if manual_span_cleanup and current_span is not None:
  120. current_span.__exit__(None, None, None)
  121. event, hint = event_from_exception(
  122. exc,
  123. client_options=sentry_sdk.get_client().options,
  124. mechanism={"type": "openai", "handled": False},
  125. )
  126. sentry_sdk.capture_event(event, hint=hint)
  127. def _has_attr_and_is_int(
  128. token_usage: "Union[CompletionUsage, ResponseUsage]", attr_name: str
  129. ) -> bool:
  130. return hasattr(token_usage, attr_name) and isinstance(
  131. getattr(token_usage, attr_name, None), int
  132. )
  133. def _calculate_completions_token_usage(
  134. messages: "Optional[Iterable[ChatCompletionMessageParam]]",
  135. response: "Any",
  136. span: "Span",
  137. streaming_message_responses: "Optional[List[str]]",
  138. streaming_message_total_token_usage: "Optional[CompletionUsage]",
  139. count_tokens: "Callable[..., Any]",
  140. ) -> None:
  141. """Extract and record token usage from a Chat Completions API response."""
  142. input_tokens: "Optional[int]" = 0
  143. input_tokens_cached: "Optional[int]" = 0
  144. output_tokens: "Optional[int]" = 0
  145. output_tokens_reasoning: "Optional[int]" = 0
  146. total_tokens: "Optional[int]" = 0
  147. usage = None
  148. if streaming_message_total_token_usage is not None:
  149. usage = streaming_message_total_token_usage
  150. elif hasattr(response, "usage"):
  151. usage = response.usage
  152. if usage is not None:
  153. if _has_attr_and_is_int(usage, "prompt_tokens"):
  154. input_tokens = usage.prompt_tokens
  155. if _has_attr_and_is_int(usage, "completion_tokens"):
  156. output_tokens = usage.completion_tokens
  157. if _has_attr_and_is_int(usage, "total_tokens"):
  158. total_tokens = usage.total_tokens
  159. if hasattr(usage, "prompt_tokens_details"):
  160. cached = getattr(usage.prompt_tokens_details, "cached_tokens", None)
  161. if isinstance(cached, int):
  162. input_tokens_cached = cached
  163. if hasattr(usage, "completion_tokens_details"):
  164. reasoning = getattr(
  165. usage.completion_tokens_details, "reasoning_tokens", None
  166. )
  167. if isinstance(reasoning, int):
  168. output_tokens_reasoning = reasoning
  169. # Manually count input tokens
  170. if input_tokens == 0:
  171. for message in messages or []:
  172. if isinstance(message, str):
  173. input_tokens += count_tokens(message)
  174. continue
  175. elif isinstance(message, dict):
  176. message_content = message.get("content")
  177. if message_content is None:
  178. continue
  179. text_items = _get_text_items(message_content)
  180. input_tokens += sum(count_tokens(text) for text in text_items)
  181. continue
  182. # Manually count output tokens
  183. if output_tokens == 0:
  184. if streaming_message_responses is not None:
  185. for message in streaming_message_responses:
  186. output_tokens += count_tokens(message)
  187. elif hasattr(response, "choices"):
  188. for choice in response.choices:
  189. if hasattr(choice, "message") and hasattr(choice.message, "content"):
  190. output_tokens += count_tokens(choice.message.content)
  191. # Do not set token data if it is 0
  192. input_tokens = input_tokens or None
  193. input_tokens_cached = input_tokens_cached or None
  194. output_tokens = output_tokens or None
  195. output_tokens_reasoning = output_tokens_reasoning or None
  196. total_tokens = total_tokens or None
  197. record_token_usage(
  198. span,
  199. input_tokens=input_tokens,
  200. input_tokens_cached=input_tokens_cached,
  201. output_tokens=output_tokens,
  202. output_tokens_reasoning=output_tokens_reasoning,
  203. total_tokens=total_tokens,
  204. )
  205. def _calculate_responses_token_usage(
  206. input: "Any",
  207. response: "Any",
  208. span: "Span",
  209. streaming_message_responses: "Optional[List[str]]",
  210. count_tokens: "Callable[..., Any]",
  211. ) -> None:
  212. """Extract and record token usage from a Responses API response."""
  213. input_tokens: "Optional[int]" = 0
  214. input_tokens_cached: "Optional[int]" = 0
  215. output_tokens: "Optional[int]" = 0
  216. output_tokens_reasoning: "Optional[int]" = 0
  217. total_tokens: "Optional[int]" = 0
  218. if hasattr(response, "usage"):
  219. usage = response.usage
  220. if _has_attr_and_is_int(usage, "input_tokens"):
  221. input_tokens = usage.input_tokens
  222. if _has_attr_and_is_int(usage, "output_tokens"):
  223. output_tokens = usage.output_tokens
  224. if _has_attr_and_is_int(usage, "total_tokens"):
  225. total_tokens = usage.total_tokens
  226. if hasattr(usage, "input_tokens_details"):
  227. cached = getattr(usage.input_tokens_details, "cached_tokens", None)
  228. if isinstance(cached, int):
  229. input_tokens_cached = cached
  230. if hasattr(usage, "output_tokens_details"):
  231. reasoning = getattr(usage.output_tokens_details, "reasoning_tokens", None)
  232. if isinstance(reasoning, int):
  233. output_tokens_reasoning = reasoning
  234. # Manually count input tokens
  235. if input_tokens == 0:
  236. for message in input or []:
  237. if isinstance(message, str):
  238. input_tokens += count_tokens(message)
  239. continue
  240. elif isinstance(message, dict):
  241. message_content = message.get("content")
  242. if message_content is None:
  243. continue
  244. # Deliberate use of Completions function for both Completions and Responses input format.
  245. text_items = _get_text_items(message_content)
  246. input_tokens += sum(count_tokens(text) for text in text_items)
  247. continue
  248. # Manually count output tokens
  249. if output_tokens == 0:
  250. if streaming_message_responses is not None:
  251. for message in streaming_message_responses:
  252. output_tokens += count_tokens(message)
  253. elif hasattr(response, "output"):
  254. for output_item in response.output:
  255. if hasattr(output_item, "content"):
  256. for content_item in output_item.content:
  257. if hasattr(content_item, "text"):
  258. output_tokens += count_tokens(content_item.text)
  259. # Do not set token data if it is 0
  260. input_tokens = input_tokens or None
  261. input_tokens_cached = input_tokens_cached or None
  262. output_tokens = output_tokens or None
  263. output_tokens_reasoning = output_tokens_reasoning or None
  264. total_tokens = total_tokens or None
  265. record_token_usage(
  266. span,
  267. input_tokens=input_tokens,
  268. input_tokens_cached=input_tokens_cached,
  269. output_tokens=output_tokens,
  270. output_tokens_reasoning=output_tokens_reasoning,
  271. total_tokens=total_tokens,
  272. )
  273. def _set_responses_api_input_data(
  274. span: "Span",
  275. kwargs: "dict[str, Any]",
  276. integration: "OpenAIIntegration",
  277. ) -> None:
  278. explicit_instructions: "Union[Optional[str], Omit]" = kwargs.get("instructions")
  279. messages: "Optional[Union[str, ResponseInputParam]]" = kwargs.get("input")
  280. tools = kwargs.get("tools")
  281. if tools is not None and _is_given(tools) and len(tools) > 0:
  282. set_data_normalized(
  283. span, SPANDATA.GEN_AI_REQUEST_AVAILABLE_TOOLS, safe_serialize(tools)
  284. )
  285. model = kwargs.get("model")
  286. if model is not None:
  287. span.set_data(SPANDATA.GEN_AI_REQUEST_MODEL, model)
  288. max_tokens = kwargs.get("max_output_tokens")
  289. if max_tokens is not None and _is_given(max_tokens):
  290. span.set_data(SPANDATA.GEN_AI_REQUEST_MAX_TOKENS, max_tokens)
  291. temperature = kwargs.get("temperature")
  292. if temperature is not None and _is_given(temperature):
  293. span.set_data(SPANDATA.GEN_AI_REQUEST_TEMPERATURE, temperature)
  294. top_p = kwargs.get("top_p")
  295. if top_p is not None and _is_given(top_p):
  296. span.set_data(SPANDATA.GEN_AI_REQUEST_TOP_P, top_p)
  297. if not should_send_default_pii() or not integration.include_prompts:
  298. set_data_normalized(span, SPANDATA.GEN_AI_OPERATION_NAME, "responses")
  299. return
  300. if (
  301. messages is None
  302. and explicit_instructions is not None
  303. and _is_given(explicit_instructions)
  304. ):
  305. span.set_data(
  306. SPANDATA.GEN_AI_SYSTEM_INSTRUCTIONS,
  307. json.dumps(
  308. [
  309. {
  310. "type": "text",
  311. "content": explicit_instructions,
  312. }
  313. ]
  314. ),
  315. )
  316. set_data_normalized(span, SPANDATA.GEN_AI_OPERATION_NAME, "responses")
  317. return
  318. if messages is None:
  319. set_data_normalized(span, SPANDATA.GEN_AI_OPERATION_NAME, "responses")
  320. return
  321. instructions_text_parts: "list[TextPart]" = []
  322. if explicit_instructions is not None and _is_given(explicit_instructions):
  323. instructions_text_parts.append(
  324. {
  325. "type": "text",
  326. "content": explicit_instructions,
  327. }
  328. )
  329. system_instructions = _get_system_instructions_responses(messages)
  330. # Deliberate use of function accepting completions API type because
  331. # of shared structure FOR THIS PURPOSE ONLY.
  332. instructions_text_parts += _transform_system_instructions(system_instructions)
  333. if len(instructions_text_parts) > 0:
  334. span.set_data(
  335. SPANDATA.GEN_AI_SYSTEM_INSTRUCTIONS,
  336. json.dumps(instructions_text_parts),
  337. )
  338. if isinstance(messages, str):
  339. normalized_messages = normalize_message_roles([messages]) # type: ignore
  340. scope = sentry_sdk.get_current_scope()
  341. messages_data = truncate_and_annotate_messages(normalized_messages, span, scope)
  342. if messages_data is not None:
  343. set_data_normalized(
  344. span, SPANDATA.GEN_AI_REQUEST_MESSAGES, messages_data, unpack=False
  345. )
  346. set_data_normalized(span, SPANDATA.GEN_AI_OPERATION_NAME, "responses")
  347. return
  348. non_system_messages = [
  349. message for message in messages if not _is_system_instruction_responses(message)
  350. ]
  351. if len(non_system_messages) > 0:
  352. normalized_messages = normalize_message_roles(non_system_messages)
  353. scope = sentry_sdk.get_current_scope()
  354. messages_data = truncate_and_annotate_messages(normalized_messages, span, scope)
  355. if messages_data is not None:
  356. set_data_normalized(
  357. span, SPANDATA.GEN_AI_REQUEST_MESSAGES, messages_data, unpack=False
  358. )
  359. set_data_normalized(span, SPANDATA.GEN_AI_OPERATION_NAME, "responses")
  360. def _set_completions_api_input_data(
  361. span: "Span",
  362. kwargs: "dict[str, Any]",
  363. integration: "OpenAIIntegration",
  364. ) -> None:
  365. messages: "Optional[Union[str, Iterable[ChatCompletionMessageParam]]]" = kwargs.get(
  366. "messages"
  367. )
  368. tools = kwargs.get("tools")
  369. if tools is not None and _is_given(tools) and len(tools) > 0:
  370. set_data_normalized(
  371. span, SPANDATA.GEN_AI_REQUEST_AVAILABLE_TOOLS, safe_serialize(tools)
  372. )
  373. model = kwargs.get("model")
  374. if model is not None:
  375. span.set_data(SPANDATA.GEN_AI_REQUEST_MODEL, model)
  376. max_tokens = kwargs.get("max_tokens")
  377. if max_tokens is not None and _is_given(max_tokens):
  378. span.set_data(SPANDATA.GEN_AI_REQUEST_MAX_TOKENS, max_tokens)
  379. presence_penalty = kwargs.get("presence_penalty")
  380. if presence_penalty is not None and _is_given(presence_penalty):
  381. span.set_data(SPANDATA.GEN_AI_REQUEST_PRESENCE_PENALTY, presence_penalty)
  382. frequency_penalty = kwargs.get("frequency_penalty")
  383. if frequency_penalty is not None and _is_given(frequency_penalty):
  384. span.set_data(SPANDATA.GEN_AI_REQUEST_FREQUENCY_PENALTY, frequency_penalty)
  385. temperature = kwargs.get("temperature")
  386. if temperature is not None and _is_given(temperature):
  387. span.set_data(SPANDATA.GEN_AI_REQUEST_TEMPERATURE, temperature)
  388. top_p = kwargs.get("top_p")
  389. if top_p is not None and _is_given(top_p):
  390. span.set_data(SPANDATA.GEN_AI_REQUEST_TOP_P, top_p)
  391. if (
  392. not should_send_default_pii()
  393. or not integration.include_prompts
  394. or messages is None
  395. ):
  396. set_data_normalized(span, SPANDATA.GEN_AI_OPERATION_NAME, "chat")
  397. return
  398. if isinstance(messages, str):
  399. normalized_messages = normalize_message_roles([messages]) # type: ignore
  400. scope = sentry_sdk.get_current_scope()
  401. messages_data = truncate_and_annotate_messages(normalized_messages, span, scope)
  402. if messages_data is not None:
  403. set_data_normalized(
  404. span, SPANDATA.GEN_AI_REQUEST_MESSAGES, messages_data, unpack=False
  405. )
  406. set_data_normalized(span, SPANDATA.GEN_AI_OPERATION_NAME, "chat")
  407. return
  408. # dict special case following https://github.com/openai/openai-python/blob/3e0c05b84a2056870abf3bd6a5e7849020209cc3/src/openai/_utils/_transform.py#L194-L197
  409. if not isinstance(messages, Iterable) or isinstance(messages, dict):
  410. set_data_normalized(span, SPANDATA.GEN_AI_OPERATION_NAME, "chat")
  411. return
  412. messages = list(messages)
  413. kwargs["messages"] = messages
  414. system_instructions = _get_system_instructions_completions(messages)
  415. if len(system_instructions) > 0:
  416. span.set_data(
  417. SPANDATA.GEN_AI_SYSTEM_INSTRUCTIONS,
  418. json.dumps(_transform_system_instructions(system_instructions)),
  419. )
  420. non_system_messages = [
  421. message
  422. for message in messages
  423. if not _is_system_instruction_completions(message)
  424. ]
  425. if len(non_system_messages) > 0:
  426. normalized_messages = normalize_message_roles(non_system_messages)
  427. scope = sentry_sdk.get_current_scope()
  428. messages_data = truncate_and_annotate_messages(normalized_messages, span, scope)
  429. if messages_data is not None:
  430. set_data_normalized(
  431. span, SPANDATA.GEN_AI_REQUEST_MESSAGES, messages_data, unpack=False
  432. )
  433. set_data_normalized(span, SPANDATA.GEN_AI_OPERATION_NAME, "chat")
  434. def _set_embeddings_input_data(
  435. span: "Span",
  436. kwargs: "dict[str, Any]",
  437. integration: "OpenAIIntegration",
  438. ) -> None:
  439. messages: "Union[str, SequenceNotStr[str], Iterable[int], Iterable[Iterable[int]]]" = kwargs.get(
  440. "input"
  441. )
  442. model = kwargs.get("model")
  443. if model is not None:
  444. span.set_data(SPANDATA.GEN_AI_REQUEST_MODEL, model)
  445. if (
  446. not should_send_default_pii()
  447. or not integration.include_prompts
  448. or messages is None
  449. ):
  450. set_data_normalized(span, SPANDATA.GEN_AI_OPERATION_NAME, "embeddings")
  451. return
  452. if isinstance(messages, str):
  453. set_data_normalized(span, SPANDATA.GEN_AI_OPERATION_NAME, "embeddings")
  454. normalized_messages = normalize_message_roles([messages]) # type: ignore
  455. scope = sentry_sdk.get_current_scope()
  456. messages_data = truncate_and_annotate_embedding_inputs(
  457. normalized_messages, span, scope
  458. )
  459. if messages_data is not None:
  460. set_data_normalized(
  461. span, SPANDATA.GEN_AI_EMBEDDINGS_INPUT, messages_data, unpack=False
  462. )
  463. return
  464. # dict special case following https://github.com/openai/openai-python/blob/3e0c05b84a2056870abf3bd6a5e7849020209cc3/src/openai/_utils/_transform.py#L194-L197
  465. if not isinstance(messages, Iterable) or isinstance(messages, dict):
  466. set_data_normalized(span, SPANDATA.GEN_AI_OPERATION_NAME, "embeddings")
  467. return
  468. messages = list(messages)
  469. kwargs["input"] = messages
  470. if len(messages) > 0:
  471. normalized_messages = normalize_message_roles(messages)
  472. scope = sentry_sdk.get_current_scope()
  473. messages_data = truncate_and_annotate_embedding_inputs(
  474. normalized_messages, span, scope
  475. )
  476. if messages_data is not None:
  477. set_data_normalized(
  478. span, SPANDATA.GEN_AI_EMBEDDINGS_INPUT, messages_data, unpack=False
  479. )
  480. set_data_normalized(span, SPANDATA.GEN_AI_OPERATION_NAME, "embeddings")
  481. def _set_common_output_data(
  482. span: "Span",
  483. response: "Any",
  484. input: "Any",
  485. integration: "OpenAIIntegration",
  486. finish_span: bool = True,
  487. ) -> None:
  488. if hasattr(response, "model"):
  489. set_data_normalized(span, SPANDATA.GEN_AI_RESPONSE_MODEL, response.model)
  490. # Chat Completions API
  491. if hasattr(response, "choices"):
  492. if should_send_default_pii() and integration.include_prompts:
  493. response_text = [
  494. choice.message.model_dump()
  495. for choice in response.choices
  496. if choice.message is not None
  497. ]
  498. if len(response_text) > 0:
  499. set_data_normalized(span, SPANDATA.GEN_AI_RESPONSE_TEXT, response_text)
  500. _calculate_completions_token_usage(
  501. messages=input,
  502. response=response,
  503. span=span,
  504. streaming_message_responses=None,
  505. streaming_message_total_token_usage=None,
  506. count_tokens=integration.count_tokens,
  507. )
  508. if finish_span:
  509. span.__exit__(None, None, None)
  510. # Responses API
  511. elif hasattr(response, "output"):
  512. if should_send_default_pii() and integration.include_prompts:
  513. output_messages: "dict[str, list[Any]]" = {
  514. "response": [],
  515. "tool": [],
  516. }
  517. for output in response.output:
  518. if output.type == "function_call":
  519. output_messages["tool"].append(output.dict())
  520. elif output.type == "message":
  521. for output_message in output.content:
  522. try:
  523. output_messages["response"].append(output_message.text)
  524. except AttributeError:
  525. # Unknown output message type, just return the json
  526. output_messages["response"].append(output_message.dict())
  527. if len(output_messages["tool"]) > 0:
  528. set_data_normalized(
  529. span,
  530. SPANDATA.GEN_AI_RESPONSE_TOOL_CALLS,
  531. output_messages["tool"],
  532. unpack=False,
  533. )
  534. if len(output_messages["response"]) > 0:
  535. set_data_normalized(
  536. span, SPANDATA.GEN_AI_RESPONSE_TEXT, output_messages["response"]
  537. )
  538. _calculate_responses_token_usage(
  539. input=input,
  540. response=response,
  541. span=span,
  542. streaming_message_responses=None,
  543. count_tokens=integration.count_tokens,
  544. )
  545. if finish_span:
  546. span.__exit__(None, None, None)
  547. # Embeddings API (fallback for responses with neither choices nor output)
  548. else:
  549. _calculate_completions_token_usage(
  550. messages=input,
  551. response=response,
  552. span=span,
  553. streaming_message_responses=None,
  554. streaming_message_total_token_usage=None,
  555. count_tokens=integration.count_tokens,
  556. )
  557. if finish_span:
  558. span.__exit__(None, None, None)
  559. def _new_chat_completion_common(f: "Any", *args: "Any", **kwargs: "Any") -> "Any":
  560. integration = sentry_sdk.get_client().get_integration(OpenAIIntegration)
  561. if integration is None:
  562. return f(*args, **kwargs)
  563. if "messages" not in kwargs:
  564. # invalid call (in all versions of openai), let it return error
  565. return f(*args, **kwargs)
  566. try:
  567. iter(kwargs["messages"])
  568. except TypeError:
  569. # invalid call (in all versions), messages must be iterable
  570. return f(*args, **kwargs)
  571. model = kwargs.get("model")
  572. span = sentry_sdk.start_span(
  573. op=consts.OP.GEN_AI_CHAT,
  574. name=f"chat {model}",
  575. origin=OpenAIIntegration.origin,
  576. )
  577. span.__enter__()
  578. span.set_data(SPANDATA.GEN_AI_SYSTEM, "openai")
  579. # Same bool handling as in https://github.com/openai/openai-python/blob/acd0c54d8a68efeedde0e5b4e6c310eef1ce7867/src/openai/resources/completions.py#L585
  580. is_streaming_response = kwargs.get("stream", False) or False
  581. span.set_data(SPANDATA.GEN_AI_RESPONSE_STREAMING, is_streaming_response)
  582. _set_completions_api_input_data(span, kwargs, integration)
  583. start_time = time.perf_counter()
  584. response = yield f, args, kwargs
  585. # Attribute check to fail gracefully if the attribute is not present in future `openai` versions.
  586. if isinstance(response, Stream) and hasattr(response, "_iterator"):
  587. messages = kwargs.get("messages")
  588. if messages is not None and isinstance(messages, str):
  589. messages = [messages]
  590. response._iterator = _wrap_synchronous_completions_chunk_iterator(
  591. span=span,
  592. integration=integration,
  593. start_time=start_time,
  594. messages=messages,
  595. response=response,
  596. old_iterator=response._iterator,
  597. finish_span=True,
  598. )
  599. # Attribute check to fail gracefully if the attribute is not present in future `openai` versions.
  600. elif isinstance(response, AsyncStream) and hasattr(response, "_iterator"):
  601. messages = kwargs.get("messages")
  602. if messages is not None and isinstance(messages, str):
  603. messages = [messages]
  604. response._iterator = _wrap_asynchronous_completions_chunk_iterator(
  605. span=span,
  606. integration=integration,
  607. start_time=start_time,
  608. messages=messages,
  609. response=response,
  610. old_iterator=response._iterator,
  611. finish_span=True,
  612. )
  613. else:
  614. _set_completions_api_output_data(
  615. span, response, kwargs, integration, finish_span=True
  616. )
  617. return response
  618. def _set_completions_api_output_data(
  619. span: "Span",
  620. response: "Any",
  621. kwargs: "dict[str, Any]",
  622. integration: "OpenAIIntegration",
  623. finish_span: bool = True,
  624. ) -> None:
  625. messages = kwargs.get("messages")
  626. if messages is not None and isinstance(messages, str):
  627. messages = [messages]
  628. _set_common_output_data(
  629. span,
  630. response,
  631. messages,
  632. integration,
  633. finish_span,
  634. )
  635. def _wrap_synchronous_completions_chunk_iterator(
  636. span: "Span",
  637. integration: "OpenAIIntegration",
  638. start_time: "Optional[float]",
  639. messages: "Optional[Iterable[ChatCompletionMessageParam]]",
  640. response: "Stream[ChatCompletionChunk]",
  641. old_iterator: "Iterator[ChatCompletionChunk]",
  642. finish_span: "bool",
  643. ) -> "Iterator[ChatCompletionChunk]":
  644. """
  645. Sets information received while iterating the response stream on the AI Client Span.
  646. Compute token count based on inputs and outputs using tiktoken if token counts are not in the model response.
  647. Responsible for closing the AI Client Span if instructed to by the `finish_span` argument.
  648. """
  649. ttft = None
  650. data_buf: "list[list[str]]" = [] # one for each choice
  651. streaming_message_total_token_usage = None
  652. for x in old_iterator:
  653. span.set_data(SPANDATA.GEN_AI_RESPONSE_MODEL, x.model)
  654. with capture_internal_exceptions():
  655. if hasattr(x, "choices"):
  656. choice_index = 0
  657. for choice in x.choices:
  658. if hasattr(choice, "delta") and hasattr(choice.delta, "content"):
  659. if start_time is not None and ttft is None:
  660. ttft = time.perf_counter() - start_time
  661. content = choice.delta.content
  662. if len(data_buf) <= choice_index:
  663. data_buf.append([])
  664. data_buf[choice_index].append(content or "")
  665. choice_index += 1
  666. if hasattr(x, "usage"):
  667. streaming_message_total_token_usage = x.usage
  668. yield x
  669. with capture_internal_exceptions():
  670. if ttft is not None:
  671. set_data_normalized(
  672. span, SPANDATA.GEN_AI_RESPONSE_TIME_TO_FIRST_TOKEN, ttft
  673. )
  674. all_responses = None
  675. if len(data_buf) > 0:
  676. all_responses = ["".join(chunk) for chunk in data_buf]
  677. if should_send_default_pii() and integration.include_prompts:
  678. set_data_normalized(span, SPANDATA.GEN_AI_RESPONSE_TEXT, all_responses)
  679. _calculate_completions_token_usage(
  680. messages=messages,
  681. response=response,
  682. span=span,
  683. streaming_message_responses=all_responses,
  684. streaming_message_total_token_usage=streaming_message_total_token_usage,
  685. count_tokens=integration.count_tokens,
  686. )
  687. if finish_span:
  688. span.__exit__(None, None, None)
  689. async def _wrap_asynchronous_completions_chunk_iterator(
  690. span: "Span",
  691. integration: "OpenAIIntegration",
  692. start_time: "Optional[float]",
  693. messages: "Optional[Iterable[ChatCompletionMessageParam]]",
  694. response: "AsyncStream[ChatCompletionChunk]",
  695. old_iterator: "AsyncIterator[ChatCompletionChunk]",
  696. finish_span: "bool",
  697. ) -> "AsyncIterator[ChatCompletionChunk]":
  698. """
  699. Sets information received while iterating the response stream on the AI Client Span.
  700. Compute token count based on inputs and outputs using tiktoken if token counts are not in the model response.
  701. Responsible for closing the AI Client Span if instructed to by the `finish_span` argument.
  702. """
  703. ttft = None
  704. data_buf: "list[list[str]]" = [] # one for each choice
  705. streaming_message_total_token_usage = None
  706. async for x in old_iterator:
  707. span.set_data(SPANDATA.GEN_AI_RESPONSE_MODEL, x.model)
  708. with capture_internal_exceptions():
  709. if hasattr(x, "choices"):
  710. choice_index = 0
  711. for choice in x.choices:
  712. if hasattr(choice, "delta") and hasattr(choice.delta, "content"):
  713. if start_time is not None and ttft is None:
  714. ttft = time.perf_counter() - start_time
  715. content = choice.delta.content
  716. if len(data_buf) <= choice_index:
  717. data_buf.append([])
  718. data_buf[choice_index].append(content or "")
  719. choice_index += 1
  720. if hasattr(x, "usage"):
  721. streaming_message_total_token_usage = x.usage
  722. yield x
  723. with capture_internal_exceptions():
  724. if ttft is not None:
  725. set_data_normalized(
  726. span, SPANDATA.GEN_AI_RESPONSE_TIME_TO_FIRST_TOKEN, ttft
  727. )
  728. all_responses = None
  729. if len(data_buf) > 0:
  730. all_responses = ["".join(chunk) for chunk in data_buf]
  731. if should_send_default_pii() and integration.include_prompts:
  732. set_data_normalized(span, SPANDATA.GEN_AI_RESPONSE_TEXT, all_responses)
  733. _calculate_completions_token_usage(
  734. messages=messages,
  735. response=response,
  736. span=span,
  737. streaming_message_responses=all_responses,
  738. streaming_message_total_token_usage=streaming_message_total_token_usage,
  739. count_tokens=integration.count_tokens,
  740. )
  741. if finish_span:
  742. span.__exit__(None, None, None)
  743. def _wrap_synchronous_responses_event_iterator(
  744. span: "Span",
  745. integration: "OpenAIIntegration",
  746. start_time: "Optional[float]",
  747. input: "Optional[Union[str, ResponseInputParam]]",
  748. response: "Stream[ResponseStreamEvent]",
  749. old_iterator: "Iterator[ResponseStreamEvent]",
  750. finish_span: "bool",
  751. ) -> "Iterator[ResponseStreamEvent]":
  752. """
  753. Sets information received while iterating the response stream on the AI Client Span.
  754. Compute token count based on inputs and outputs using tiktoken if token counts are not in the model response.
  755. Responsible for closing the AI Client Span if instructed to by the `finish_span` argument.
  756. """
  757. ttft = None
  758. data_buf: "list[list[str]]" = [] # one for each choice
  759. count_tokens_manually = True
  760. for x in old_iterator:
  761. with capture_internal_exceptions():
  762. if hasattr(x, "delta"):
  763. if start_time is not None and ttft is None:
  764. ttft = time.perf_counter() - start_time
  765. if len(data_buf) == 0:
  766. data_buf.append([])
  767. data_buf[0].append(x.delta or "")
  768. if isinstance(x, ResponseCompletedEvent):
  769. span.set_data(SPANDATA.GEN_AI_RESPONSE_MODEL, x.response.model)
  770. _calculate_responses_token_usage(
  771. input=input,
  772. response=x.response,
  773. span=span,
  774. streaming_message_responses=None,
  775. count_tokens=integration.count_tokens,
  776. )
  777. count_tokens_manually = False
  778. yield x
  779. with capture_internal_exceptions():
  780. if ttft is not None:
  781. set_data_normalized(
  782. span, SPANDATA.GEN_AI_RESPONSE_TIME_TO_FIRST_TOKEN, ttft
  783. )
  784. if len(data_buf) > 0:
  785. all_responses = ["".join(chunk) for chunk in data_buf]
  786. if should_send_default_pii() and integration.include_prompts:
  787. set_data_normalized(span, SPANDATA.GEN_AI_RESPONSE_TEXT, all_responses)
  788. if count_tokens_manually:
  789. _calculate_responses_token_usage(
  790. input=input,
  791. response=response,
  792. span=span,
  793. streaming_message_responses=all_responses,
  794. count_tokens=integration.count_tokens,
  795. )
  796. if finish_span:
  797. span.__exit__(None, None, None)
  798. async def _wrap_asynchronous_responses_event_iterator(
  799. span: "Span",
  800. integration: "OpenAIIntegration",
  801. start_time: "Optional[float]",
  802. input: "Optional[Union[str, ResponseInputParam]]",
  803. response: "AsyncStream[ResponseStreamEvent]",
  804. old_iterator: "AsyncIterator[ResponseStreamEvent]",
  805. finish_span: "bool",
  806. ) -> "AsyncIterator[ResponseStreamEvent]":
  807. """
  808. Sets information received while iterating the response stream on the AI Client Span.
  809. Compute token count based on inputs and outputs using tiktoken if token counts are not in the model response.
  810. Responsible for closing the AI Client Span if instructed to by the `finish_span` argument.
  811. """
  812. ttft: "Optional[float]" = None
  813. data_buf: "list[list[str]]" = [] # one for each choice
  814. count_tokens_manually = True
  815. async for x in old_iterator:
  816. with capture_internal_exceptions():
  817. if hasattr(x, "delta"):
  818. if start_time is not None and ttft is None:
  819. ttft = time.perf_counter() - start_time
  820. if len(data_buf) == 0:
  821. data_buf.append([])
  822. data_buf[0].append(x.delta or "")
  823. if isinstance(x, ResponseCompletedEvent):
  824. span.set_data(SPANDATA.GEN_AI_RESPONSE_MODEL, x.response.model)
  825. _calculate_responses_token_usage(
  826. input=input,
  827. response=x.response,
  828. span=span,
  829. streaming_message_responses=None,
  830. count_tokens=integration.count_tokens,
  831. )
  832. count_tokens_manually = False
  833. yield x
  834. with capture_internal_exceptions():
  835. if ttft is not None:
  836. set_data_normalized(
  837. span, SPANDATA.GEN_AI_RESPONSE_TIME_TO_FIRST_TOKEN, ttft
  838. )
  839. if len(data_buf) > 0:
  840. all_responses = ["".join(chunk) for chunk in data_buf]
  841. if should_send_default_pii() and integration.include_prompts:
  842. set_data_normalized(span, SPANDATA.GEN_AI_RESPONSE_TEXT, all_responses)
  843. if count_tokens_manually:
  844. _calculate_responses_token_usage(
  845. input=input,
  846. response=response,
  847. span=span,
  848. streaming_message_responses=all_responses,
  849. count_tokens=integration.count_tokens,
  850. )
  851. if finish_span:
  852. span.__exit__(None, None, None)
  853. def _set_responses_api_output_data(
  854. span: "Span",
  855. response: "Any",
  856. kwargs: "dict[str, Any]",
  857. integration: "OpenAIIntegration",
  858. finish_span: bool = True,
  859. ) -> None:
  860. input = kwargs.get("input")
  861. if input is not None and isinstance(input, str):
  862. input = [input]
  863. _set_common_output_data(
  864. span,
  865. response,
  866. input,
  867. integration,
  868. finish_span,
  869. )
  870. def _set_embeddings_output_data(
  871. span: "Span",
  872. response: "Any",
  873. kwargs: "dict[str, Any]",
  874. integration: "OpenAIIntegration",
  875. finish_span: bool = True,
  876. ) -> None:
  877. input = kwargs.get("input")
  878. if input is not None and isinstance(input, str):
  879. input = [input]
  880. _set_common_output_data(
  881. span,
  882. response,
  883. input,
  884. integration,
  885. finish_span,
  886. )
  887. def _wrap_chat_completion_create(f: "Callable[..., Any]") -> "Callable[..., Any]":
  888. def _execute_sync(f: "Any", *args: "Any", **kwargs: "Any") -> "Any":
  889. gen = _new_chat_completion_common(f, *args, **kwargs)
  890. try:
  891. f, args, kwargs = next(gen)
  892. except StopIteration as e:
  893. return e.value
  894. try:
  895. try:
  896. result = f(*args, **kwargs)
  897. except Exception as e:
  898. exc_info = sys.exc_info()
  899. with capture_internal_exceptions():
  900. _capture_exception(e)
  901. reraise(*exc_info)
  902. return gen.send(result)
  903. except StopIteration as e:
  904. return e.value
  905. @wraps(f)
  906. def _sentry_patched_create_sync(*args: "Any", **kwargs: "Any") -> "Any":
  907. integration = sentry_sdk.get_client().get_integration(OpenAIIntegration)
  908. if integration is None or "messages" not in kwargs:
  909. # no "messages" means invalid call (in all versions of openai), let it return error
  910. return f(*args, **kwargs)
  911. return _execute_sync(f, *args, **kwargs)
  912. return _sentry_patched_create_sync
  913. def _wrap_async_chat_completion_create(f: "Callable[..., Any]") -> "Callable[..., Any]":
  914. async def _execute_async(f: "Any", *args: "Any", **kwargs: "Any") -> "Any":
  915. gen = _new_chat_completion_common(f, *args, **kwargs)
  916. try:
  917. f, args, kwargs = next(gen)
  918. except StopIteration as e:
  919. return await e.value
  920. try:
  921. try:
  922. result = await f(*args, **kwargs)
  923. except Exception as e:
  924. exc_info = sys.exc_info()
  925. with capture_internal_exceptions():
  926. _capture_exception(e)
  927. reraise(*exc_info)
  928. return gen.send(result)
  929. except StopIteration as e:
  930. return e.value
  931. @wraps(f)
  932. async def _sentry_patched_create_async(*args: "Any", **kwargs: "Any") -> "Any":
  933. integration = sentry_sdk.get_client().get_integration(OpenAIIntegration)
  934. if integration is None or "messages" not in kwargs:
  935. # no "messages" means invalid call (in all versions of openai), let it return error
  936. return await f(*args, **kwargs)
  937. return await _execute_async(f, *args, **kwargs)
  938. return _sentry_patched_create_async
  939. def _new_embeddings_create_common(f: "Any", *args: "Any", **kwargs: "Any") -> "Any":
  940. integration = sentry_sdk.get_client().get_integration(OpenAIIntegration)
  941. if integration is None:
  942. return f(*args, **kwargs)
  943. model = kwargs.get("model")
  944. with sentry_sdk.start_span(
  945. op=consts.OP.GEN_AI_EMBEDDINGS,
  946. name=f"embeddings {model}",
  947. origin=OpenAIIntegration.origin,
  948. ) as span:
  949. span.set_data(SPANDATA.GEN_AI_SYSTEM, "openai")
  950. _set_embeddings_input_data(span, kwargs, integration)
  951. response = yield f, args, kwargs
  952. _set_embeddings_output_data(
  953. span, response, kwargs, integration, finish_span=False
  954. )
  955. return response
  956. def _wrap_embeddings_create(f: "Any") -> "Any":
  957. def _execute_sync(f: "Any", *args: "Any", **kwargs: "Any") -> "Any":
  958. gen = _new_embeddings_create_common(f, *args, **kwargs)
  959. try:
  960. f, args, kwargs = next(gen)
  961. except StopIteration as e:
  962. return e.value
  963. try:
  964. try:
  965. result = f(*args, **kwargs)
  966. except Exception as e:
  967. exc_info = sys.exc_info()
  968. with capture_internal_exceptions():
  969. _capture_exception(e, manual_span_cleanup=False)
  970. reraise(*exc_info)
  971. return gen.send(result)
  972. except StopIteration as e:
  973. return e.value
  974. @wraps(f)
  975. def _sentry_patched_create_sync(*args: "Any", **kwargs: "Any") -> "Any":
  976. integration = sentry_sdk.get_client().get_integration(OpenAIIntegration)
  977. if integration is None:
  978. return f(*args, **kwargs)
  979. return _execute_sync(f, *args, **kwargs)
  980. return _sentry_patched_create_sync
  981. def _wrap_async_embeddings_create(f: "Any") -> "Any":
  982. async def _execute_async(f: "Any", *args: "Any", **kwargs: "Any") -> "Any":
  983. gen = _new_embeddings_create_common(f, *args, **kwargs)
  984. try:
  985. f, args, kwargs = next(gen)
  986. except StopIteration as e:
  987. return await e.value
  988. try:
  989. try:
  990. result = await f(*args, **kwargs)
  991. except Exception as e:
  992. exc_info = sys.exc_info()
  993. with capture_internal_exceptions():
  994. _capture_exception(e, manual_span_cleanup=False)
  995. reraise(*exc_info)
  996. return gen.send(result)
  997. except StopIteration as e:
  998. return e.value
  999. @wraps(f)
  1000. async def _sentry_patched_create_async(*args: "Any", **kwargs: "Any") -> "Any":
  1001. integration = sentry_sdk.get_client().get_integration(OpenAIIntegration)
  1002. if integration is None:
  1003. return await f(*args, **kwargs)
  1004. return await _execute_async(f, *args, **kwargs)
  1005. return _sentry_patched_create_async
  1006. def _new_responses_create_common(f: "Any", *args: "Any", **kwargs: "Any") -> "Any":
  1007. integration = sentry_sdk.get_client().get_integration(OpenAIIntegration)
  1008. if integration is None:
  1009. return f(*args, **kwargs)
  1010. model = kwargs.get("model")
  1011. span = sentry_sdk.start_span(
  1012. op=consts.OP.GEN_AI_RESPONSES,
  1013. name=f"responses {model}",
  1014. origin=OpenAIIntegration.origin,
  1015. )
  1016. span.__enter__()
  1017. span.set_data(SPANDATA.GEN_AI_SYSTEM, "openai")
  1018. # Same bool handling as in https://github.com/openai/openai-python/blob/acd0c54d8a68efeedde0e5b4e6c310eef1ce7867/src/openai/resources/responses/responses.py#L940
  1019. is_streaming_response = kwargs.get("stream", False) or False
  1020. span.set_data(SPANDATA.GEN_AI_RESPONSE_STREAMING, is_streaming_response)
  1021. _set_responses_api_input_data(span, kwargs, integration)
  1022. start_time = time.perf_counter()
  1023. response = yield f, args, kwargs
  1024. # Attribute check to fail gracefully if the attribute is not present in future `openai` versions.
  1025. if isinstance(response, Stream) and hasattr(response, "_iterator"):
  1026. input = kwargs.get("input")
  1027. if input is not None and isinstance(input, str):
  1028. input = [input]
  1029. response._iterator = _wrap_synchronous_responses_event_iterator(
  1030. span=span,
  1031. integration=integration,
  1032. start_time=start_time,
  1033. input=input,
  1034. response=response,
  1035. old_iterator=response._iterator,
  1036. finish_span=True,
  1037. )
  1038. # Attribute check to fail gracefully if the attribute is not present in future `openai` versions.
  1039. elif isinstance(response, AsyncStream) and hasattr(response, "_iterator"):
  1040. input = kwargs.get("input")
  1041. if input is not None and isinstance(input, str):
  1042. input = [input]
  1043. response._iterator = _wrap_asynchronous_responses_event_iterator(
  1044. span=span,
  1045. integration=integration,
  1046. start_time=start_time,
  1047. input=input,
  1048. response=response,
  1049. old_iterator=response._iterator,
  1050. finish_span=True,
  1051. )
  1052. else:
  1053. _set_responses_api_output_data(
  1054. span, response, kwargs, integration, finish_span=True
  1055. )
  1056. return response
  1057. def _wrap_responses_create(f: "Any") -> "Any":
  1058. def _execute_sync(f: "Any", *args: "Any", **kwargs: "Any") -> "Any":
  1059. gen = _new_responses_create_common(f, *args, **kwargs)
  1060. try:
  1061. f, args, kwargs = next(gen)
  1062. except StopIteration as e:
  1063. return e.value
  1064. try:
  1065. try:
  1066. result = f(*args, **kwargs)
  1067. except Exception as e:
  1068. exc_info = sys.exc_info()
  1069. with capture_internal_exceptions():
  1070. _capture_exception(e)
  1071. reraise(*exc_info)
  1072. return gen.send(result)
  1073. except StopIteration as e:
  1074. return e.value
  1075. @wraps(f)
  1076. def _sentry_patched_create_sync(*args: "Any", **kwargs: "Any") -> "Any":
  1077. integration = sentry_sdk.get_client().get_integration(OpenAIIntegration)
  1078. if integration is None:
  1079. return f(*args, **kwargs)
  1080. return _execute_sync(f, *args, **kwargs)
  1081. return _sentry_patched_create_sync
  1082. def _wrap_async_responses_create(f: "Any") -> "Any":
  1083. async def _execute_async(f: "Any", *args: "Any", **kwargs: "Any") -> "Any":
  1084. gen = _new_responses_create_common(f, *args, **kwargs)
  1085. try:
  1086. f, args, kwargs = next(gen)
  1087. except StopIteration as e:
  1088. return await e.value
  1089. try:
  1090. try:
  1091. result = await f(*args, **kwargs)
  1092. except Exception as e:
  1093. exc_info = sys.exc_info()
  1094. with capture_internal_exceptions():
  1095. _capture_exception(e)
  1096. reraise(*exc_info)
  1097. return gen.send(result)
  1098. except StopIteration as e:
  1099. return e.value
  1100. @wraps(f)
  1101. async def _sentry_patched_responses_async(*args: "Any", **kwargs: "Any") -> "Any":
  1102. integration = sentry_sdk.get_client().get_integration(OpenAIIntegration)
  1103. if integration is None:
  1104. return await f(*args, **kwargs)
  1105. return await _execute_async(f, *args, **kwargs)
  1106. return _sentry_patched_responses_async
  1107. def _is_given(obj: "Any") -> bool:
  1108. """
  1109. Check for givenness safely across different openai versions.
  1110. """
  1111. if NotGiven is not None and isinstance(obj, NotGiven):
  1112. return False
  1113. if Omit is not None and isinstance(obj, Omit):
  1114. return False
  1115. return True