openai.py 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729
  1. import sys
  2. from functools import wraps
  3. import sentry_sdk
  4. from sentry_sdk import consts
  5. from sentry_sdk.ai.monitoring import record_token_usage
  6. from sentry_sdk.ai.utils import (
  7. set_data_normalized,
  8. normalize_message_roles,
  9. truncate_and_annotate_messages,
  10. )
  11. from sentry_sdk.consts import SPANDATA
  12. from sentry_sdk.integrations import DidNotEnable, Integration
  13. from sentry_sdk.scope import should_send_default_pii
  14. from sentry_sdk.tracing_utils import set_span_errored
  15. from sentry_sdk.utils import (
  16. capture_internal_exceptions,
  17. event_from_exception,
  18. safe_serialize,
  19. reraise,
  20. )
  21. from typing import TYPE_CHECKING
  22. if TYPE_CHECKING:
  23. from typing import Any, Iterable, List, Optional, Callable, AsyncIterator, Iterator
  24. from sentry_sdk.tracing import Span
  25. try:
  26. try:
  27. from openai import NotGiven
  28. except ImportError:
  29. NotGiven = None
  30. try:
  31. from openai import Omit
  32. except ImportError:
  33. Omit = None
  34. from openai.resources.chat.completions import Completions, AsyncCompletions
  35. from openai.resources import Embeddings, AsyncEmbeddings
  36. if TYPE_CHECKING:
  37. from openai.types.chat import ChatCompletionMessageParam, ChatCompletionChunk
  38. except ImportError:
  39. raise DidNotEnable("OpenAI not installed")
  40. RESPONSES_API_ENABLED = True
  41. try:
  42. # responses API support was introduced in v1.66.0
  43. from openai.resources.responses import Responses, AsyncResponses
  44. from openai.types.responses.response_completed_event import ResponseCompletedEvent
  45. except ImportError:
  46. RESPONSES_API_ENABLED = False
  47. class OpenAIIntegration(Integration):
  48. identifier = "openai"
  49. origin = f"auto.ai.{identifier}"
  50. def __init__(
  51. self: "OpenAIIntegration",
  52. include_prompts: bool = True,
  53. tiktoken_encoding_name: "Optional[str]" = None,
  54. ) -> None:
  55. self.include_prompts = include_prompts
  56. self.tiktoken_encoding = None
  57. if tiktoken_encoding_name is not None:
  58. import tiktoken # type: ignore
  59. self.tiktoken_encoding = tiktoken.get_encoding(tiktoken_encoding_name)
  60. @staticmethod
  61. def setup_once() -> None:
  62. Completions.create = _wrap_chat_completion_create(Completions.create)
  63. AsyncCompletions.create = _wrap_async_chat_completion_create(
  64. AsyncCompletions.create
  65. )
  66. Embeddings.create = _wrap_embeddings_create(Embeddings.create)
  67. AsyncEmbeddings.create = _wrap_async_embeddings_create(AsyncEmbeddings.create)
  68. if RESPONSES_API_ENABLED:
  69. Responses.create = _wrap_responses_create(Responses.create)
  70. AsyncResponses.create = _wrap_async_responses_create(AsyncResponses.create)
  71. def count_tokens(self: "OpenAIIntegration", s: str) -> int:
  72. if self.tiktoken_encoding is None:
  73. return 0
  74. try:
  75. return len(self.tiktoken_encoding.encode_ordinary(s))
  76. except Exception:
  77. return 0
  78. def _capture_exception(exc: "Any", manual_span_cleanup: bool = True) -> None:
  79. # Close an eventually open span
  80. # We need to do this by hand because we are not using the start_span context manager
  81. current_span = sentry_sdk.get_current_span()
  82. set_span_errored(current_span)
  83. if manual_span_cleanup and current_span is not None:
  84. current_span.__exit__(None, None, None)
  85. event, hint = event_from_exception(
  86. exc,
  87. client_options=sentry_sdk.get_client().options,
  88. mechanism={"type": "openai", "handled": False},
  89. )
  90. sentry_sdk.capture_event(event, hint=hint)
  91. def _get_usage(usage: "Any", names: "List[str]") -> int:
  92. for name in names:
  93. if hasattr(usage, name) and isinstance(getattr(usage, name), int):
  94. return getattr(usage, name)
  95. return 0
  96. def _calculate_token_usage(
  97. messages: "Optional[Iterable[ChatCompletionMessageParam]]",
  98. response: "Any",
  99. span: "Span",
  100. streaming_message_responses: "Optional[List[str]]",
  101. count_tokens: "Callable[..., Any]",
  102. ) -> None:
  103. input_tokens: "Optional[int]" = 0
  104. input_tokens_cached: "Optional[int]" = 0
  105. output_tokens: "Optional[int]" = 0
  106. output_tokens_reasoning: "Optional[int]" = 0
  107. total_tokens: "Optional[int]" = 0
  108. if hasattr(response, "usage"):
  109. input_tokens = _get_usage(response.usage, ["input_tokens", "prompt_tokens"])
  110. if hasattr(response.usage, "input_tokens_details"):
  111. input_tokens_cached = _get_usage(
  112. response.usage.input_tokens_details, ["cached_tokens"]
  113. )
  114. output_tokens = _get_usage(
  115. response.usage, ["output_tokens", "completion_tokens"]
  116. )
  117. if hasattr(response.usage, "output_tokens_details"):
  118. output_tokens_reasoning = _get_usage(
  119. response.usage.output_tokens_details, ["reasoning_tokens"]
  120. )
  121. total_tokens = _get_usage(response.usage, ["total_tokens"])
  122. # Manually count tokens
  123. if input_tokens == 0:
  124. for message in messages or []:
  125. if isinstance(message, dict) and "content" in message:
  126. input_tokens += count_tokens(message["content"])
  127. elif isinstance(message, str):
  128. input_tokens += count_tokens(message)
  129. if output_tokens == 0:
  130. if streaming_message_responses is not None:
  131. for message in streaming_message_responses:
  132. output_tokens += count_tokens(message)
  133. elif hasattr(response, "choices"):
  134. for choice in response.choices:
  135. if hasattr(choice, "message") and hasattr(choice.message, "content"):
  136. output_tokens += count_tokens(choice.message.content)
  137. # Do not set token data if it is 0
  138. input_tokens = input_tokens or None
  139. input_tokens_cached = input_tokens_cached or None
  140. output_tokens = output_tokens or None
  141. output_tokens_reasoning = output_tokens_reasoning or None
  142. total_tokens = total_tokens or None
  143. record_token_usage(
  144. span,
  145. input_tokens=input_tokens,
  146. input_tokens_cached=input_tokens_cached,
  147. output_tokens=output_tokens,
  148. output_tokens_reasoning=output_tokens_reasoning,
  149. total_tokens=total_tokens,
  150. )
  151. def _set_input_data(
  152. span: "Span",
  153. kwargs: "dict[str, Any]",
  154. operation: str,
  155. integration: "OpenAIIntegration",
  156. ) -> None:
  157. # Input messages (the prompt or data sent to the model)
  158. messages = kwargs.get("messages")
  159. if messages is None:
  160. messages = kwargs.get("input")
  161. if isinstance(messages, str):
  162. messages = [messages]
  163. if (
  164. messages is not None
  165. and len(messages) > 0
  166. and should_send_default_pii()
  167. and integration.include_prompts
  168. ):
  169. normalized_messages = normalize_message_roles(messages)
  170. scope = sentry_sdk.get_current_scope()
  171. messages_data = truncate_and_annotate_messages(normalized_messages, span, scope)
  172. if messages_data is not None:
  173. # Use appropriate field based on operation type
  174. if operation == "embeddings":
  175. set_data_normalized(
  176. span, SPANDATA.GEN_AI_EMBEDDINGS_INPUT, messages_data, unpack=False
  177. )
  178. else:
  179. set_data_normalized(
  180. span, SPANDATA.GEN_AI_REQUEST_MESSAGES, messages_data, unpack=False
  181. )
  182. # Input attributes: Common
  183. set_data_normalized(span, SPANDATA.GEN_AI_SYSTEM, "openai")
  184. set_data_normalized(span, SPANDATA.GEN_AI_OPERATION_NAME, operation)
  185. # Input attributes: Optional
  186. kwargs_keys_to_attributes = {
  187. "model": SPANDATA.GEN_AI_REQUEST_MODEL,
  188. "stream": SPANDATA.GEN_AI_RESPONSE_STREAMING,
  189. "max_tokens": SPANDATA.GEN_AI_REQUEST_MAX_TOKENS,
  190. "presence_penalty": SPANDATA.GEN_AI_REQUEST_PRESENCE_PENALTY,
  191. "frequency_penalty": SPANDATA.GEN_AI_REQUEST_FREQUENCY_PENALTY,
  192. "temperature": SPANDATA.GEN_AI_REQUEST_TEMPERATURE,
  193. "top_p": SPANDATA.GEN_AI_REQUEST_TOP_P,
  194. }
  195. for key, attribute in kwargs_keys_to_attributes.items():
  196. value = kwargs.get(key)
  197. if value is not None and _is_given(value):
  198. set_data_normalized(span, attribute, value)
  199. # Input attributes: Tools
  200. tools = kwargs.get("tools")
  201. if tools is not None and _is_given(tools) and len(tools) > 0:
  202. set_data_normalized(
  203. span, SPANDATA.GEN_AI_REQUEST_AVAILABLE_TOOLS, safe_serialize(tools)
  204. )
  205. def _set_output_data(
  206. span: "Span",
  207. response: "Any",
  208. kwargs: "dict[str, Any]",
  209. integration: "OpenAIIntegration",
  210. finish_span: bool = True,
  211. ) -> None:
  212. if hasattr(response, "model"):
  213. set_data_normalized(span, SPANDATA.GEN_AI_RESPONSE_MODEL, response.model)
  214. # Input messages (the prompt or data sent to the model)
  215. # used for the token usage calculation
  216. messages = kwargs.get("messages")
  217. if messages is None:
  218. messages = kwargs.get("input")
  219. if messages is not None and isinstance(messages, str):
  220. messages = [messages]
  221. if hasattr(response, "choices"):
  222. if should_send_default_pii() and integration.include_prompts:
  223. response_text = [
  224. choice.message.model_dump()
  225. for choice in response.choices
  226. if choice.message is not None
  227. ]
  228. if len(response_text) > 0:
  229. set_data_normalized(span, SPANDATA.GEN_AI_RESPONSE_TEXT, response_text)
  230. _calculate_token_usage(messages, response, span, None, integration.count_tokens)
  231. if finish_span:
  232. span.__exit__(None, None, None)
  233. elif hasattr(response, "output"):
  234. if should_send_default_pii() and integration.include_prompts:
  235. output_messages: "dict[str, list[Any]]" = {
  236. "response": [],
  237. "tool": [],
  238. }
  239. for output in response.output:
  240. if output.type == "function_call":
  241. output_messages["tool"].append(output.dict())
  242. elif output.type == "message":
  243. for output_message in output.content:
  244. try:
  245. output_messages["response"].append(output_message.text)
  246. except AttributeError:
  247. # Unknown output message type, just return the json
  248. output_messages["response"].append(output_message.dict())
  249. if len(output_messages["tool"]) > 0:
  250. set_data_normalized(
  251. span,
  252. SPANDATA.GEN_AI_RESPONSE_TOOL_CALLS,
  253. output_messages["tool"],
  254. unpack=False,
  255. )
  256. if len(output_messages["response"]) > 0:
  257. set_data_normalized(
  258. span, SPANDATA.GEN_AI_RESPONSE_TEXT, output_messages["response"]
  259. )
  260. _calculate_token_usage(messages, response, span, None, integration.count_tokens)
  261. if finish_span:
  262. span.__exit__(None, None, None)
  263. elif hasattr(response, "_iterator"):
  264. data_buf: "list[list[str]]" = [] # one for each choice
  265. old_iterator = response._iterator
  266. def new_iterator() -> "Iterator[ChatCompletionChunk]":
  267. count_tokens_manually = True
  268. for x in old_iterator:
  269. with capture_internal_exceptions():
  270. # OpenAI chat completion API
  271. if hasattr(x, "choices"):
  272. choice_index = 0
  273. for choice in x.choices:
  274. if hasattr(choice, "delta") and hasattr(
  275. choice.delta, "content"
  276. ):
  277. content = choice.delta.content
  278. if len(data_buf) <= choice_index:
  279. data_buf.append([])
  280. data_buf[choice_index].append(content or "")
  281. choice_index += 1
  282. # OpenAI responses API
  283. elif hasattr(x, "delta"):
  284. if len(data_buf) == 0:
  285. data_buf.append([])
  286. data_buf[0].append(x.delta or "")
  287. # OpenAI responses API end of streaming response
  288. if RESPONSES_API_ENABLED and isinstance(x, ResponseCompletedEvent):
  289. _calculate_token_usage(
  290. messages,
  291. x.response,
  292. span,
  293. None,
  294. integration.count_tokens,
  295. )
  296. count_tokens_manually = False
  297. yield x
  298. with capture_internal_exceptions():
  299. if len(data_buf) > 0:
  300. all_responses = ["".join(chunk) for chunk in data_buf]
  301. if should_send_default_pii() and integration.include_prompts:
  302. set_data_normalized(
  303. span, SPANDATA.GEN_AI_RESPONSE_TEXT, all_responses
  304. )
  305. if count_tokens_manually:
  306. _calculate_token_usage(
  307. messages,
  308. response,
  309. span,
  310. all_responses,
  311. integration.count_tokens,
  312. )
  313. if finish_span:
  314. span.__exit__(None, None, None)
  315. async def new_iterator_async() -> "AsyncIterator[ChatCompletionChunk]":
  316. count_tokens_manually = True
  317. async for x in old_iterator:
  318. with capture_internal_exceptions():
  319. # OpenAI chat completion API
  320. if hasattr(x, "choices"):
  321. choice_index = 0
  322. for choice in x.choices:
  323. if hasattr(choice, "delta") and hasattr(
  324. choice.delta, "content"
  325. ):
  326. content = choice.delta.content
  327. if len(data_buf) <= choice_index:
  328. data_buf.append([])
  329. data_buf[choice_index].append(content or "")
  330. choice_index += 1
  331. # OpenAI responses API
  332. elif hasattr(x, "delta"):
  333. if len(data_buf) == 0:
  334. data_buf.append([])
  335. data_buf[0].append(x.delta or "")
  336. # OpenAI responses API end of streaming response
  337. if RESPONSES_API_ENABLED and isinstance(x, ResponseCompletedEvent):
  338. _calculate_token_usage(
  339. messages,
  340. x.response,
  341. span,
  342. None,
  343. integration.count_tokens,
  344. )
  345. count_tokens_manually = False
  346. yield x
  347. with capture_internal_exceptions():
  348. if len(data_buf) > 0:
  349. all_responses = ["".join(chunk) for chunk in data_buf]
  350. if should_send_default_pii() and integration.include_prompts:
  351. set_data_normalized(
  352. span, SPANDATA.GEN_AI_RESPONSE_TEXT, all_responses
  353. )
  354. if count_tokens_manually:
  355. _calculate_token_usage(
  356. messages,
  357. response,
  358. span,
  359. all_responses,
  360. integration.count_tokens,
  361. )
  362. if finish_span:
  363. span.__exit__(None, None, None)
  364. if str(type(response._iterator)) == "<class 'async_generator'>":
  365. response._iterator = new_iterator_async()
  366. else:
  367. response._iterator = new_iterator()
  368. else:
  369. _calculate_token_usage(messages, response, span, None, integration.count_tokens)
  370. if finish_span:
  371. span.__exit__(None, None, None)
  372. def _new_chat_completion_common(f: "Any", *args: "Any", **kwargs: "Any") -> "Any":
  373. integration = sentry_sdk.get_client().get_integration(OpenAIIntegration)
  374. if integration is None:
  375. return f(*args, **kwargs)
  376. if "messages" not in kwargs:
  377. # invalid call (in all versions of openai), let it return error
  378. return f(*args, **kwargs)
  379. try:
  380. iter(kwargs["messages"])
  381. except TypeError:
  382. # invalid call (in all versions), messages must be iterable
  383. return f(*args, **kwargs)
  384. model = kwargs.get("model")
  385. operation = "chat"
  386. span = sentry_sdk.start_span(
  387. op=consts.OP.GEN_AI_CHAT,
  388. name=f"{operation} {model}",
  389. origin=OpenAIIntegration.origin,
  390. )
  391. span.__enter__()
  392. _set_input_data(span, kwargs, operation, integration)
  393. response = yield f, args, kwargs
  394. _set_output_data(span, response, kwargs, integration, finish_span=True)
  395. return response
  396. def _wrap_chat_completion_create(f: "Callable[..., Any]") -> "Callable[..., Any]":
  397. def _execute_sync(f: "Any", *args: "Any", **kwargs: "Any") -> "Any":
  398. gen = _new_chat_completion_common(f, *args, **kwargs)
  399. try:
  400. f, args, kwargs = next(gen)
  401. except StopIteration as e:
  402. return e.value
  403. try:
  404. try:
  405. result = f(*args, **kwargs)
  406. except Exception as e:
  407. exc_info = sys.exc_info()
  408. with capture_internal_exceptions():
  409. _capture_exception(e)
  410. reraise(*exc_info)
  411. return gen.send(result)
  412. except StopIteration as e:
  413. return e.value
  414. @wraps(f)
  415. def _sentry_patched_create_sync(*args: "Any", **kwargs: "Any") -> "Any":
  416. integration = sentry_sdk.get_client().get_integration(OpenAIIntegration)
  417. if integration is None or "messages" not in kwargs:
  418. # no "messages" means invalid call (in all versions of openai), let it return error
  419. return f(*args, **kwargs)
  420. return _execute_sync(f, *args, **kwargs)
  421. return _sentry_patched_create_sync
  422. def _wrap_async_chat_completion_create(f: "Callable[..., Any]") -> "Callable[..., Any]":
  423. async def _execute_async(f: "Any", *args: "Any", **kwargs: "Any") -> "Any":
  424. gen = _new_chat_completion_common(f, *args, **kwargs)
  425. try:
  426. f, args, kwargs = next(gen)
  427. except StopIteration as e:
  428. return await e.value
  429. try:
  430. try:
  431. result = await f(*args, **kwargs)
  432. except Exception as e:
  433. exc_info = sys.exc_info()
  434. with capture_internal_exceptions():
  435. _capture_exception(e)
  436. reraise(*exc_info)
  437. return gen.send(result)
  438. except StopIteration as e:
  439. return e.value
  440. @wraps(f)
  441. async def _sentry_patched_create_async(*args: "Any", **kwargs: "Any") -> "Any":
  442. integration = sentry_sdk.get_client().get_integration(OpenAIIntegration)
  443. if integration is None or "messages" not in kwargs:
  444. # no "messages" means invalid call (in all versions of openai), let it return error
  445. return await f(*args, **kwargs)
  446. return await _execute_async(f, *args, **kwargs)
  447. return _sentry_patched_create_async
  448. def _new_embeddings_create_common(f: "Any", *args: "Any", **kwargs: "Any") -> "Any":
  449. integration = sentry_sdk.get_client().get_integration(OpenAIIntegration)
  450. if integration is None:
  451. return f(*args, **kwargs)
  452. model = kwargs.get("model")
  453. operation = "embeddings"
  454. with sentry_sdk.start_span(
  455. op=consts.OP.GEN_AI_EMBEDDINGS,
  456. name=f"{operation} {model}",
  457. origin=OpenAIIntegration.origin,
  458. ) as span:
  459. _set_input_data(span, kwargs, operation, integration)
  460. response = yield f, args, kwargs
  461. _set_output_data(span, response, kwargs, integration, finish_span=False)
  462. return response
  463. def _wrap_embeddings_create(f: "Any") -> "Any":
  464. def _execute_sync(f: "Any", *args: "Any", **kwargs: "Any") -> "Any":
  465. gen = _new_embeddings_create_common(f, *args, **kwargs)
  466. try:
  467. f, args, kwargs = next(gen)
  468. except StopIteration as e:
  469. return e.value
  470. try:
  471. try:
  472. result = f(*args, **kwargs)
  473. except Exception as e:
  474. exc_info = sys.exc_info()
  475. with capture_internal_exceptions():
  476. _capture_exception(e, manual_span_cleanup=False)
  477. reraise(*exc_info)
  478. return gen.send(result)
  479. except StopIteration as e:
  480. return e.value
  481. @wraps(f)
  482. def _sentry_patched_create_sync(*args: "Any", **kwargs: "Any") -> "Any":
  483. integration = sentry_sdk.get_client().get_integration(OpenAIIntegration)
  484. if integration is None:
  485. return f(*args, **kwargs)
  486. return _execute_sync(f, *args, **kwargs)
  487. return _sentry_patched_create_sync
  488. def _wrap_async_embeddings_create(f: "Any") -> "Any":
  489. async def _execute_async(f: "Any", *args: "Any", **kwargs: "Any") -> "Any":
  490. gen = _new_embeddings_create_common(f, *args, **kwargs)
  491. try:
  492. f, args, kwargs = next(gen)
  493. except StopIteration as e:
  494. return await e.value
  495. try:
  496. try:
  497. result = await f(*args, **kwargs)
  498. except Exception as e:
  499. exc_info = sys.exc_info()
  500. with capture_internal_exceptions():
  501. _capture_exception(e, manual_span_cleanup=False)
  502. reraise(*exc_info)
  503. return gen.send(result)
  504. except StopIteration as e:
  505. return e.value
  506. @wraps(f)
  507. async def _sentry_patched_create_async(*args: "Any", **kwargs: "Any") -> "Any":
  508. integration = sentry_sdk.get_client().get_integration(OpenAIIntegration)
  509. if integration is None:
  510. return await f(*args, **kwargs)
  511. return await _execute_async(f, *args, **kwargs)
  512. return _sentry_patched_create_async
  513. def _new_responses_create_common(f: "Any", *args: "Any", **kwargs: "Any") -> "Any":
  514. integration = sentry_sdk.get_client().get_integration(OpenAIIntegration)
  515. if integration is None:
  516. return f(*args, **kwargs)
  517. model = kwargs.get("model")
  518. operation = "responses"
  519. span = sentry_sdk.start_span(
  520. op=consts.OP.GEN_AI_RESPONSES,
  521. name=f"{operation} {model}",
  522. origin=OpenAIIntegration.origin,
  523. )
  524. span.__enter__()
  525. _set_input_data(span, kwargs, operation, integration)
  526. response = yield f, args, kwargs
  527. _set_output_data(span, response, kwargs, integration, finish_span=True)
  528. return response
  529. def _wrap_responses_create(f: "Any") -> "Any":
  530. def _execute_sync(f: "Any", *args: "Any", **kwargs: "Any") -> "Any":
  531. gen = _new_responses_create_common(f, *args, **kwargs)
  532. try:
  533. f, args, kwargs = next(gen)
  534. except StopIteration as e:
  535. return e.value
  536. try:
  537. try:
  538. result = f(*args, **kwargs)
  539. except Exception as e:
  540. exc_info = sys.exc_info()
  541. with capture_internal_exceptions():
  542. _capture_exception(e)
  543. reraise(*exc_info)
  544. return gen.send(result)
  545. except StopIteration as e:
  546. return e.value
  547. @wraps(f)
  548. def _sentry_patched_create_sync(*args: "Any", **kwargs: "Any") -> "Any":
  549. integration = sentry_sdk.get_client().get_integration(OpenAIIntegration)
  550. if integration is None:
  551. return f(*args, **kwargs)
  552. return _execute_sync(f, *args, **kwargs)
  553. return _sentry_patched_create_sync
  554. def _wrap_async_responses_create(f: "Any") -> "Any":
  555. async def _execute_async(f: "Any", *args: "Any", **kwargs: "Any") -> "Any":
  556. gen = _new_responses_create_common(f, *args, **kwargs)
  557. try:
  558. f, args, kwargs = next(gen)
  559. except StopIteration as e:
  560. return await e.value
  561. try:
  562. try:
  563. result = await f(*args, **kwargs)
  564. except Exception as e:
  565. exc_info = sys.exc_info()
  566. with capture_internal_exceptions():
  567. _capture_exception(e)
  568. reraise(*exc_info)
  569. return gen.send(result)
  570. except StopIteration as e:
  571. return e.value
  572. @wraps(f)
  573. async def _sentry_patched_responses_async(*args: "Any", **kwargs: "Any") -> "Any":
  574. integration = sentry_sdk.get_client().get_integration(OpenAIIntegration)
  575. if integration is None:
  576. return await f(*args, **kwargs)
  577. return await _execute_async(f, *args, **kwargs)
  578. return _sentry_patched_responses_async
  579. def _is_given(obj: "Any") -> bool:
  580. """
  581. Check for givenness safely across different openai versions.
  582. """
  583. if NotGiven is not None and isinstance(obj, NotGiven):
  584. return False
  585. if Omit is not None and isinstance(obj, Omit):
  586. return False
  587. return True