payi 0.1.0a76__tar.gz → 0.1.0a78__tar.gz

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.

Potentially problematic release.


This version of payi might be problematic. Click here for more details.

Files changed (205) hide show
  1. payi-0.1.0a78/.release-please-manifest.json +3 -0
  2. {payi-0.1.0a76 → payi-0.1.0a78}/CHANGELOG.md +22 -0
  3. {payi-0.1.0a76 → payi-0.1.0a78}/CONTRIBUTING.md +1 -2
  4. {payi-0.1.0a76 → payi-0.1.0a78}/PKG-INFO +2 -7
  5. {payi-0.1.0a76 → payi-0.1.0a78}/README.md +1 -6
  6. {payi-0.1.0a76 → payi-0.1.0a78}/pyproject.toml +1 -1
  7. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/_base_client.py +6 -0
  8. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/_models.py +2 -0
  9. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/_types.py +2 -0
  10. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/_version.py +1 -1
  11. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/lib/AnthropicInstrumentor.py +43 -9
  12. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/lib/BedrockInstrumentor.py +6 -2
  13. payi-0.1.0a78/src/payi/lib/GoogleGenAiInstrumentor.py +370 -0
  14. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/lib/OpenAIInstrumentor.py +6 -2
  15. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/lib/VertexInstrumentor.py +8 -5
  16. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/lib/instrument.py +109 -41
  17. {payi-0.1.0a76 → payi-0.1.0a78}/tests/test_client.py +54 -0
  18. payi-0.1.0a76/.release-please-manifest.json +0 -3
  19. {payi-0.1.0a76 → payi-0.1.0a78}/.gitignore +0 -0
  20. {payi-0.1.0a76 → payi-0.1.0a78}/LICENSE +0 -0
  21. {payi-0.1.0a76 → payi-0.1.0a78}/SECURITY.md +0 -0
  22. {payi-0.1.0a76 → payi-0.1.0a78}/api.md +0 -0
  23. {payi-0.1.0a76 → payi-0.1.0a78}/bin/check-release-environment +0 -0
  24. {payi-0.1.0a76 → payi-0.1.0a78}/bin/publish-pypi +0 -0
  25. {payi-0.1.0a76 → payi-0.1.0a78}/examples/.keep +0 -0
  26. {payi-0.1.0a76 → payi-0.1.0a78}/mypy.ini +0 -0
  27. {payi-0.1.0a76 → payi-0.1.0a78}/noxfile.py +0 -0
  28. {payi-0.1.0a76 → payi-0.1.0a78}/release-please-config.json +0 -0
  29. {payi-0.1.0a76 → payi-0.1.0a78}/requirements-dev.lock +0 -0
  30. {payi-0.1.0a76 → payi-0.1.0a78}/requirements.lock +0 -0
  31. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/__init__.py +0 -0
  32. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/_client.py +0 -0
  33. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/_compat.py +0 -0
  34. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/_constants.py +0 -0
  35. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/_exceptions.py +0 -0
  36. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/_files.py +0 -0
  37. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/_qs.py +0 -0
  38. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/_resource.py +0 -0
  39. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/_response.py +0 -0
  40. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/_streaming.py +0 -0
  41. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/_utils/__init__.py +0 -0
  42. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/_utils/_logs.py +0 -0
  43. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/_utils/_proxy.py +0 -0
  44. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/_utils/_reflection.py +0 -0
  45. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/_utils/_resources_proxy.py +0 -0
  46. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/_utils/_streams.py +0 -0
  47. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/_utils/_sync.py +0 -0
  48. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/_utils/_transform.py +0 -0
  49. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/_utils/_typing.py +0 -0
  50. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/_utils/_utils.py +0 -0
  51. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/lib/.keep +0 -0
  52. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/lib/Stopwatch.py +0 -0
  53. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/lib/helpers.py +0 -0
  54. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/pagination.py +0 -0
  55. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/py.typed +0 -0
  56. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/resources/__init__.py +0 -0
  57. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/resources/categories/__init__.py +0 -0
  58. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/resources/categories/categories.py +0 -0
  59. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/resources/categories/resources.py +0 -0
  60. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/resources/experiences/__init__.py +0 -0
  61. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/resources/experiences/experiences.py +0 -0
  62. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/resources/experiences/properties.py +0 -0
  63. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/resources/experiences/types/__init__.py +0 -0
  64. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/resources/experiences/types/limit_config.py +0 -0
  65. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/resources/experiences/types/types.py +0 -0
  66. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/resources/ingest.py +0 -0
  67. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/resources/limits/__init__.py +0 -0
  68. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/resources/limits/limits.py +0 -0
  69. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/resources/limits/tags.py +0 -0
  70. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/resources/requests/__init__.py +0 -0
  71. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/resources/requests/properties.py +0 -0
  72. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/resources/requests/requests.py +0 -0
  73. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/resources/requests/result.py +0 -0
  74. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/resources/use_cases/__init__.py +0 -0
  75. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/resources/use_cases/definitions/__init__.py +0 -0
  76. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/resources/use_cases/definitions/definitions.py +0 -0
  77. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/resources/use_cases/definitions/kpis.py +0 -0
  78. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/resources/use_cases/definitions/limit_config.py +0 -0
  79. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/resources/use_cases/definitions/version.py +0 -0
  80. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/resources/use_cases/kpis.py +0 -0
  81. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/resources/use_cases/properties.py +0 -0
  82. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/resources/use_cases/use_cases.py +0 -0
  83. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/types/__init__.py +0 -0
  84. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/types/bulk_ingest_response.py +0 -0
  85. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/types/categories/__init__.py +0 -0
  86. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/types/categories/resource_create_params.py +0 -0
  87. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/types/categories/resource_list_params.py +0 -0
  88. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/types/category_delete_resource_response.py +0 -0
  89. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/types/category_delete_response.py +0 -0
  90. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/types/category_list_params.py +0 -0
  91. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/types/category_list_resources_params.py +0 -0
  92. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/types/category_resource_response.py +0 -0
  93. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/types/category_response.py +0 -0
  94. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/types/cost_data.py +0 -0
  95. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/types/cost_details.py +0 -0
  96. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/types/default_response.py +0 -0
  97. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/types/experience_instance_response.py +0 -0
  98. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/types/experiences/__init__.py +0 -0
  99. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/types/experiences/experience_type.py +0 -0
  100. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/types/experiences/property_create_params.py +0 -0
  101. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/types/experiences/type_create_params.py +0 -0
  102. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/types/experiences/type_list_params.py +0 -0
  103. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/types/experiences/type_update_params.py +0 -0
  104. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/types/experiences/types/__init__.py +0 -0
  105. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/types/experiences/types/limit_config_create_params.py +0 -0
  106. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/types/ingest_bulk_params.py +0 -0
  107. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/types/ingest_event_param.py +0 -0
  108. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/types/ingest_response.py +0 -0
  109. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/types/ingest_units_params.py +0 -0
  110. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/types/limit_create_params.py +0 -0
  111. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/types/limit_history_response.py +0 -0
  112. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/types/limit_list_params.py +0 -0
  113. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/types/limit_list_response.py +0 -0
  114. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/types/limit_reset_params.py +0 -0
  115. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/types/limit_response.py +0 -0
  116. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/types/limit_update_params.py +0 -0
  117. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/types/limits/__init__.py +0 -0
  118. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/types/limits/limit_tags.py +0 -0
  119. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/types/limits/tag_create_params.py +0 -0
  120. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/types/limits/tag_create_response.py +0 -0
  121. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/types/limits/tag_delete_response.py +0 -0
  122. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/types/limits/tag_list_response.py +0 -0
  123. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/types/limits/tag_remove_params.py +0 -0
  124. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/types/limits/tag_remove_response.py +0 -0
  125. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/types/limits/tag_update_params.py +0 -0
  126. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/types/limits/tag_update_response.py +0 -0
  127. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/types/pay_i_common_models_api_router_header_info_param.py +0 -0
  128. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/types/requests/__init__.py +0 -0
  129. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/types/requests/property_create_params.py +0 -0
  130. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/types/requests/request_result.py +0 -0
  131. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/types/requests_data.py +0 -0
  132. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/types/shared/__init__.py +0 -0
  133. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/types/shared/evaluation_response.py +0 -0
  134. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/types/shared/ingest_units.py +0 -0
  135. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/types/shared/pay_i_common_models_budget_management_cost_details_base.py +0 -0
  136. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/types/shared/pay_i_common_models_budget_management_create_limit_base.py +0 -0
  137. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/types/shared/properties_response.py +0 -0
  138. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/types/shared/xproxy_error.py +0 -0
  139. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/types/shared/xproxy_result.py +0 -0
  140. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/types/shared_params/__init__.py +0 -0
  141. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/types/shared_params/ingest_units.py +0 -0
  142. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/types/shared_params/pay_i_common_models_budget_management_create_limit_base.py +0 -0
  143. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/types/total_cost_data.py +0 -0
  144. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/types/use_case_instance_response.py +0 -0
  145. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/types/use_cases/__init__.py +0 -0
  146. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/types/use_cases/definition_create_params.py +0 -0
  147. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/types/use_cases/definition_list_params.py +0 -0
  148. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/types/use_cases/definition_update_params.py +0 -0
  149. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/types/use_cases/definitions/__init__.py +0 -0
  150. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/types/use_cases/definitions/kpi_create_params.py +0 -0
  151. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/types/use_cases/definitions/kpi_create_response.py +0 -0
  152. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/types/use_cases/definitions/kpi_delete_response.py +0 -0
  153. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/types/use_cases/definitions/kpi_list_params.py +0 -0
  154. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/types/use_cases/definitions/kpi_list_response.py +0 -0
  155. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/types/use_cases/definitions/kpi_retrieve_response.py +0 -0
  156. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/types/use_cases/definitions/kpi_update_params.py +0 -0
  157. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/types/use_cases/definitions/kpi_update_response.py +0 -0
  158. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/types/use_cases/definitions/limit_config_create_params.py +0 -0
  159. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/types/use_cases/kpi_create_params.py +0 -0
  160. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/types/use_cases/kpi_list_params.py +0 -0
  161. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/types/use_cases/kpi_list_response.py +0 -0
  162. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/types/use_cases/kpi_update_params.py +0 -0
  163. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/types/use_cases/property_create_params.py +0 -0
  164. {payi-0.1.0a76 → payi-0.1.0a78}/src/payi/types/use_cases/use_case_definition.py +0 -0
  165. {payi-0.1.0a76 → payi-0.1.0a78}/tests/__init__.py +0 -0
  166. {payi-0.1.0a76 → payi-0.1.0a78}/tests/api_resources/__init__.py +0 -0
  167. {payi-0.1.0a76 → payi-0.1.0a78}/tests/api_resources/categories/__init__.py +0 -0
  168. {payi-0.1.0a76 → payi-0.1.0a78}/tests/api_resources/categories/test_resources.py +0 -0
  169. {payi-0.1.0a76 → payi-0.1.0a78}/tests/api_resources/experiences/__init__.py +0 -0
  170. {payi-0.1.0a76 → payi-0.1.0a78}/tests/api_resources/experiences/test_properties.py +0 -0
  171. {payi-0.1.0a76 → payi-0.1.0a78}/tests/api_resources/experiences/test_types.py +0 -0
  172. {payi-0.1.0a76 → payi-0.1.0a78}/tests/api_resources/experiences/types/__init__.py +0 -0
  173. {payi-0.1.0a76 → payi-0.1.0a78}/tests/api_resources/experiences/types/test_limit_config.py +0 -0
  174. {payi-0.1.0a76 → payi-0.1.0a78}/tests/api_resources/limits/__init__.py +0 -0
  175. {payi-0.1.0a76 → payi-0.1.0a78}/tests/api_resources/limits/test_tags.py +0 -0
  176. {payi-0.1.0a76 → payi-0.1.0a78}/tests/api_resources/requests/__init__.py +0 -0
  177. {payi-0.1.0a76 → payi-0.1.0a78}/tests/api_resources/requests/test_properties.py +0 -0
  178. {payi-0.1.0a76 → payi-0.1.0a78}/tests/api_resources/requests/test_result.py +0 -0
  179. {payi-0.1.0a76 → payi-0.1.0a78}/tests/api_resources/test_categories.py +0 -0
  180. {payi-0.1.0a76 → payi-0.1.0a78}/tests/api_resources/test_experiences.py +0 -0
  181. {payi-0.1.0a76 → payi-0.1.0a78}/tests/api_resources/test_ingest.py +0 -0
  182. {payi-0.1.0a76 → payi-0.1.0a78}/tests/api_resources/test_limits.py +0 -0
  183. {payi-0.1.0a76 → payi-0.1.0a78}/tests/api_resources/test_use_cases.py +0 -0
  184. {payi-0.1.0a76 → payi-0.1.0a78}/tests/api_resources/use_cases/__init__.py +0 -0
  185. {payi-0.1.0a76 → payi-0.1.0a78}/tests/api_resources/use_cases/definitions/__init__.py +0 -0
  186. {payi-0.1.0a76 → payi-0.1.0a78}/tests/api_resources/use_cases/definitions/test_kpis.py +0 -0
  187. {payi-0.1.0a76 → payi-0.1.0a78}/tests/api_resources/use_cases/definitions/test_limit_config.py +0 -0
  188. {payi-0.1.0a76 → payi-0.1.0a78}/tests/api_resources/use_cases/definitions/test_version.py +0 -0
  189. {payi-0.1.0a76 → payi-0.1.0a78}/tests/api_resources/use_cases/test_definitions.py +0 -0
  190. {payi-0.1.0a76 → payi-0.1.0a78}/tests/api_resources/use_cases/test_kpis.py +0 -0
  191. {payi-0.1.0a76 → payi-0.1.0a78}/tests/api_resources/use_cases/test_properties.py +0 -0
  192. {payi-0.1.0a76 → payi-0.1.0a78}/tests/conftest.py +0 -0
  193. {payi-0.1.0a76 → payi-0.1.0a78}/tests/sample_file.txt +0 -0
  194. {payi-0.1.0a76 → payi-0.1.0a78}/tests/test_deepcopy.py +0 -0
  195. {payi-0.1.0a76 → payi-0.1.0a78}/tests/test_extract_files.py +0 -0
  196. {payi-0.1.0a76 → payi-0.1.0a78}/tests/test_files.py +0 -0
  197. {payi-0.1.0a76 → payi-0.1.0a78}/tests/test_models.py +0 -0
  198. {payi-0.1.0a76 → payi-0.1.0a78}/tests/test_qs.py +0 -0
  199. {payi-0.1.0a76 → payi-0.1.0a78}/tests/test_required_args.py +0 -0
  200. {payi-0.1.0a76 → payi-0.1.0a78}/tests/test_response.py +0 -0
  201. {payi-0.1.0a76 → payi-0.1.0a78}/tests/test_streaming.py +0 -0
  202. {payi-0.1.0a76 → payi-0.1.0a78}/tests/test_transform.py +0 -0
  203. {payi-0.1.0a76 → payi-0.1.0a78}/tests/test_utils/test_proxy.py +0 -0
  204. {payi-0.1.0a76 → payi-0.1.0a78}/tests/test_utils/test_typing.py +0 -0
  205. {payi-0.1.0a76 → payi-0.1.0a78}/tests/utils.py +0 -0
@@ -0,0 +1,3 @@
1
+ {
2
+ ".": "0.1.0-alpha.78"
3
+ }
@@ -1,5 +1,27 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.0-alpha.78 (2025-06-03)
4
+
5
+ Full Changelog: [v0.1.0-alpha.77...v0.1.0-alpha.78](https://github.com/Pay-i/pay-i-python/compare/v0.1.0-alpha.77...v0.1.0-alpha.78)
6
+
7
+ ### Features
8
+
9
+ * **api:** google genai, anthropic stream() ([#306](https://github.com/Pay-i/pay-i-python/issues/306)) ([341da40](https://github.com/Pay-i/pay-i-python/commit/341da40fb228cb83994bec12e7cda457f5452692))
10
+
11
+ ## 0.1.0-alpha.77 (2025-06-03)
12
+
13
+ Full Changelog: [v0.1.0-alpha.76...v0.1.0-alpha.77](https://github.com/Pay-i/pay-i-python/compare/v0.1.0-alpha.76...v0.1.0-alpha.77)
14
+
15
+ ### Features
16
+
17
+ * **client:** add follow_redirects request option ([3308085](https://github.com/Pay-i/pay-i-python/commit/3308085121c320d80931d6749f36ecee049be669))
18
+
19
+
20
+ ### Chores
21
+
22
+ * **docs:** remove reference to rye shell ([eb25599](https://github.com/Pay-i/pay-i-python/commit/eb255999808df39a9eaaf5b91825c8fe150dbf55))
23
+ * **docs:** remove unnecessary param examples ([4d34a34](https://github.com/Pay-i/pay-i-python/commit/4d34a343cd19fc091fb836d411034d9ad62bf564))
24
+
3
25
  ## 0.1.0-alpha.76 (2025-05-31)
4
26
 
5
27
  Full Changelog: [v0.1.0-alpha.75...v0.1.0-alpha.76](https://github.com/Pay-i/pay-i-python/compare/v0.1.0-alpha.75...v0.1.0-alpha.76)
@@ -17,8 +17,7 @@ $ rye sync --all-features
17
17
  You can then run scripts using `rye run python script.py` or by activating the virtual environment:
18
18
 
19
19
  ```sh
20
- $ rye shell
21
- # or manually activate - https://docs.python.org/3/library/venv.html#how-venvs-work
20
+ # Activate the virtual environment - https://docs.python.org/3/library/venv.html#how-venvs-work
22
21
  $ source .venv/bin/activate
23
22
 
24
23
  # now you can omit the `rye run` prefix
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: payi
3
- Version: 0.1.0a76
3
+ Version: 0.1.0a78
4
4
  Summary: The official Python library for the payi API
5
5
  Project-URL: Homepage, https://github.com/Pay-i/pay-i-python
6
6
  Project-URL: Repository, https://github.com/Pay-i/pay-i-python
@@ -188,12 +188,7 @@ client = Payi()
188
188
  use_case_definition = client.use_cases.definitions.create(
189
189
  description="x",
190
190
  name="x",
191
- limit_config={
192
- "max": 0,
193
- "limit_tags": ["tag1", "tag2"],
194
- "limit_type": "block",
195
- "threshold": 0,
196
- },
191
+ limit_config={"max": 0},
197
192
  )
198
193
  print(use_case_definition.limit_config)
199
194
  ```
@@ -154,12 +154,7 @@ client = Payi()
154
154
  use_case_definition = client.use_cases.definitions.create(
155
155
  description="x",
156
156
  name="x",
157
- limit_config={
158
- "max": 0,
159
- "limit_tags": ["tag1", "tag2"],
160
- "limit_type": "block",
161
- "threshold": 0,
162
- },
157
+ limit_config={"max": 0},
163
158
  )
164
159
  print(use_case_definition.limit_config)
165
160
  ```
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "payi"
3
- version = "0.1.0-alpha.76"
3
+ version = "0.1.0-alpha.78"
4
4
  description = "The official Python library for the payi API"
5
5
  dynamic = ["readme"]
6
6
  license = "Apache-2.0"
@@ -960,6 +960,9 @@ class SyncAPIClient(BaseClient[httpx.Client, Stream[Any]]):
960
960
  if self.custom_auth is not None:
961
961
  kwargs["auth"] = self.custom_auth
962
962
 
963
+ if options.follow_redirects is not None:
964
+ kwargs["follow_redirects"] = options.follow_redirects
965
+
963
966
  log.debug("Sending HTTP Request: %s %s", request.method, request.url)
964
967
 
965
968
  response = None
@@ -1460,6 +1463,9 @@ class AsyncAPIClient(BaseClient[httpx.AsyncClient, AsyncStream[Any]]):
1460
1463
  if self.custom_auth is not None:
1461
1464
  kwargs["auth"] = self.custom_auth
1462
1465
 
1466
+ if options.follow_redirects is not None:
1467
+ kwargs["follow_redirects"] = options.follow_redirects
1468
+
1463
1469
  log.debug("Sending HTTP Request: %s %s", request.method, request.url)
1464
1470
 
1465
1471
  response = None
@@ -737,6 +737,7 @@ class FinalRequestOptionsInput(TypedDict, total=False):
737
737
  idempotency_key: str
738
738
  json_data: Body
739
739
  extra_json: AnyMapping
740
+ follow_redirects: bool
740
741
 
741
742
 
742
743
  @final
@@ -750,6 +751,7 @@ class FinalRequestOptions(pydantic.BaseModel):
750
751
  files: Union[HttpxRequestFiles, None] = None
751
752
  idempotency_key: Union[str, None] = None
752
753
  post_parser: Union[Callable[[Any], Any], NotGiven] = NotGiven()
754
+ follow_redirects: Union[bool, None] = None
753
755
 
754
756
  # It should be noted that we cannot use `json` here as that would override
755
757
  # a BaseModel method in an incompatible fashion.
@@ -100,6 +100,7 @@ class RequestOptions(TypedDict, total=False):
100
100
  params: Query
101
101
  extra_json: AnyMapping
102
102
  idempotency_key: str
103
+ follow_redirects: bool
103
104
 
104
105
 
105
106
  # Sentinel class used until PEP 0661 is accepted
@@ -215,3 +216,4 @@ class _GenericAlias(Protocol):
215
216
 
216
217
  class HttpxSendArgs(TypedDict, total=False):
217
218
  auth: httpx.Auth
219
+ follow_redirects: bool
@@ -1,4 +1,4 @@
1
1
  # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
2
2
 
3
3
  __title__ = "payi"
4
- __version__ = "0.1.0-alpha.76" # x-release-please-version
4
+ __version__ = "0.1.0-alpha.78" # x-release-please-version
@@ -8,7 +8,7 @@ from wrapt import wrap_function_wrapper # type: ignore
8
8
  from payi.lib.helpers import PayiCategories
9
9
  from payi.types.ingest_units_params import Units
10
10
 
11
- from .instrument import _IsStreaming, _ProviderRequest, _PayiInstrumentor
11
+ from .instrument import _IsStreaming, _StreamingType, _ProviderRequest, _PayiInstrumentor
12
12
 
13
13
 
14
14
  class AnthropicInstrumentor:
@@ -32,7 +32,7 @@ class AnthropicInstrumentor:
32
32
  wrap_function_wrapper(
33
33
  "anthropic.resources.messages",
34
34
  "Messages.stream",
35
- messages_wrapper(instrumentor),
35
+ stream_messages_wrapper(instrumentor),
36
36
  )
37
37
 
38
38
  wrap_function_wrapper(
@@ -44,7 +44,7 @@ class AnthropicInstrumentor:
44
44
  wrap_function_wrapper(
45
45
  "anthropic.resources.messages",
46
46
  "AsyncMessages.stream",
47
- amessages_wrapper(instrumentor),
47
+ astream_messages_wrapper(instrumentor),
48
48
  )
49
49
 
50
50
  except Exception as e:
@@ -61,7 +61,7 @@ def messages_wrapper(
61
61
  **kwargs: Any,
62
62
  ) -> Any:
63
63
  return instrumentor.invoke_wrapper(
64
- _AnthropicProviderRequest(instrumentor, instance),
64
+ _AnthropicProviderRequest(instrumentor=instrumentor, streaming_type=_StreamingType.iterator, instance=instance),
65
65
  _IsStreaming.kwargs,
66
66
  wrapped,
67
67
  instance,
@@ -69,6 +69,23 @@ def messages_wrapper(
69
69
  kwargs,
70
70
  )
71
71
 
72
+ @_PayiInstrumentor.payi_wrapper
73
+ def stream_messages_wrapper(
74
+ instrumentor: _PayiInstrumentor,
75
+ wrapped: Any,
76
+ instance: Any,
77
+ *args: Any,
78
+ **kwargs: Any,
79
+ ) -> Any:
80
+ return instrumentor.invoke_wrapper(
81
+ _AnthropicProviderRequest(instrumentor=instrumentor, streaming_type=_StreamingType.stream_manager, instance=instance),
82
+ _IsStreaming.true,
83
+ wrapped,
84
+ instance,
85
+ args,
86
+ kwargs,
87
+ )
88
+
72
89
  @_PayiInstrumentor.payi_awrapper
73
90
  async def amessages_wrapper(
74
91
  instrumentor: _PayiInstrumentor,
@@ -78,7 +95,7 @@ async def amessages_wrapper(
78
95
  **kwargs: Any,
79
96
  ) -> Any:
80
97
  return await instrumentor.async_invoke_wrapper(
81
- _AnthropicProviderRequest(instrumentor, instance),
98
+ _AnthropicProviderRequest(instrumentor=instrumentor, streaming_type=_StreamingType.iterator, instance=instance),
82
99
  _IsStreaming.kwargs,
83
100
  wrapped,
84
101
  instance,
@@ -86,12 +103,30 @@ async def amessages_wrapper(
86
103
  kwargs,
87
104
  )
88
105
 
106
+ @_PayiInstrumentor.payi_awrapper
107
+ async def astream_messages_wrapper(
108
+ instrumentor: _PayiInstrumentor,
109
+ wrapped: Any,
110
+ instance: Any,
111
+ *args: Any,
112
+ **kwargs: Any,
113
+ ) -> Any:
114
+ return await instrumentor.async_invoke_wrapper(
115
+ _AnthropicProviderRequest(instrumentor=instrumentor, streaming_type=_StreamingType.stream_manager, instance=instance),
116
+ _IsStreaming.true,
117
+ wrapped,
118
+ instance,
119
+ args,
120
+ kwargs,
121
+ )
122
+
89
123
  class _AnthropicProviderRequest(_ProviderRequest):
90
- def __init__(self, instrumentor: _PayiInstrumentor, instance: Any = None) -> None:
124
+ def __init__(self, instrumentor: _PayiInstrumentor, streaming_type: _StreamingType, instance: Any = None) -> None:
91
125
  self._vertex: bool = AnthropicInstrumentor.is_vertex(instance)
92
126
  super().__init__(
93
127
  instrumentor=instrumentor,
94
- category=PayiCategories.google_vertex if self._vertex else PayiCategories.anthropic
128
+ category=PayiCategories.google_vertex if self._vertex else PayiCategories.anthropic,
129
+ streaming_type=streaming_type,
95
130
  )
96
131
 
97
132
  @override
@@ -209,5 +244,4 @@ def has_image_and_get_texts(encoding: tiktoken.Encoding, content: Union[str, 'li
209
244
  token_count = sum(len(encoding.encode(item.get("text", ""))) for item in content if item.get("type") == "text")
210
245
  return has_image, token_count
211
246
 
212
- return False, 0
213
-
247
+ return False, 0
@@ -11,7 +11,7 @@ from payi.lib.helpers import PayiCategories, PayiHeaderNames, payi_aws_bedrock_u
11
11
  from payi.types.ingest_units_params import Units, IngestUnitsParams
12
12
  from payi.types.pay_i_common_models_api_router_header_info_param import PayICommonModelsAPIRouterHeaderInfoParam
13
13
 
14
- from .instrument import _IsStreaming, _ProviderRequest, _PayiInstrumentor
14
+ from .instrument import _IsStreaming, _StreamingType, _ProviderRequest, _PayiInstrumentor
15
15
 
16
16
  _supported_model_prefixes = ["meta.llama3", "anthropic.", "amazon.nova-pro", "amazon.nova-lite", "amazon.nova-micro"]
17
17
 
@@ -216,7 +216,11 @@ def wrap_converse_stream(instrumentor: _PayiInstrumentor, wrapped: Any) -> Any:
216
216
 
217
217
  class _BedrockProviderRequest(_ProviderRequest):
218
218
  def __init__(self, instrumentor: _PayiInstrumentor):
219
- super().__init__(instrumentor=instrumentor, category=PayiCategories.aws_bedrock)
219
+ super().__init__(
220
+ instrumentor=instrumentor,
221
+ category=PayiCategories.aws_bedrock,
222
+ streaming_type=_StreamingType.iterator,
223
+ )
220
224
 
221
225
  @override
222
226
  def process_request(self, instance: Any, extra_headers: 'dict[str, str]', args: Sequence[Any], kwargs: Any) -> bool:
@@ -0,0 +1,370 @@
1
+ import json
2
+ import math
3
+ import logging
4
+ from typing import Any, List, Union, Optional, Sequence
5
+ from typing_extensions import override
6
+
7
+ from wrapt import wrap_function_wrapper # type: ignore
8
+
9
+ from payi.lib.helpers import PayiCategories
10
+ from payi.types.ingest_units_params import Units
11
+
12
+ from .instrument import _IsStreaming, _StreamingType, _ProviderRequest, _PayiInstrumentor
13
+
14
+
15
+ class GoogleGenAiInstrumentor:
16
+ @staticmethod
17
+ def instrument(instrumentor: _PayiInstrumentor) -> None:
18
+ try:
19
+ wrap_function_wrapper(
20
+ "google.genai.models",
21
+ "Models.generate_content",
22
+ generate_wrapper(instrumentor),
23
+ )
24
+
25
+ wrap_function_wrapper(
26
+ "google.genai.models",
27
+ "Models.generate_content_stream",
28
+ generate_stream_wrapper(instrumentor),
29
+ )
30
+
31
+ wrap_function_wrapper(
32
+ "google.genai.models",
33
+ "AsyncModels.generate_content",
34
+ agenerate_wrapper(instrumentor),
35
+ )
36
+
37
+ wrap_function_wrapper(
38
+ "google.genai.models",
39
+ "AsyncModels.generate_content_stream",
40
+ agenerate_stream_wrapper(instrumentor),
41
+ )
42
+
43
+ except Exception as e:
44
+ logging.debug(f"Error instrumenting vertex: {e}")
45
+ return
46
+
47
+ @_PayiInstrumentor.payi_wrapper
48
+ def generate_wrapper(
49
+ instrumentor: _PayiInstrumentor,
50
+ wrapped: Any,
51
+ instance: Any,
52
+ *args: Any,
53
+ **kwargs: Any,
54
+ ) -> Any:
55
+ return instrumentor.invoke_wrapper(
56
+ _GoogleGenAiRequest(instrumentor),
57
+ _IsStreaming.false,
58
+ wrapped,
59
+ instance,
60
+ args,
61
+ kwargs,
62
+ )
63
+
64
+ @_PayiInstrumentor.payi_wrapper
65
+ def generate_stream_wrapper(
66
+ instrumentor: _PayiInstrumentor,
67
+ wrapped: Any,
68
+ instance: Any,
69
+ *args: Any,
70
+ **kwargs: Any,
71
+ ) -> Any:
72
+ return instrumentor.invoke_wrapper(
73
+ _GoogleGenAiRequest(instrumentor),
74
+ _IsStreaming.true,
75
+ wrapped,
76
+ instance,
77
+ args,
78
+ kwargs,
79
+ )
80
+
81
+ @_PayiInstrumentor.payi_awrapper
82
+ async def agenerate_wrapper(
83
+ instrumentor: _PayiInstrumentor,
84
+ wrapped: Any,
85
+ instance: Any,
86
+ *args: Any,
87
+ **kwargs: Any,
88
+ ) -> Any:
89
+ return await instrumentor.async_invoke_wrapper(
90
+ _GoogleGenAiRequest(instrumentor),
91
+ _IsStreaming.false,
92
+ wrapped,
93
+ instance,
94
+ args,
95
+ kwargs,
96
+ )
97
+
98
+ @_PayiInstrumentor.payi_wrapper
99
+ async def agenerate_stream_wrapper(
100
+ instrumentor: _PayiInstrumentor,
101
+ wrapped: Any,
102
+ instance: Any,
103
+ *args: Any,
104
+ **kwargs: Any,
105
+ ) -> Any:
106
+ return await instrumentor.async_invoke_wrapper(
107
+ _GoogleGenAiRequest(instrumentor),
108
+ _IsStreaming.true,
109
+ wrapped,
110
+ instance,
111
+ args,
112
+ kwargs,
113
+ )
114
+
115
+ def count_chars_skip_spaces(text: str) -> int:
116
+ return sum(1 for c in text if not c.isspace())
117
+
118
+ class _GoogleGenAiRequest(_ProviderRequest):
119
+ def __init__(self, instrumentor: _PayiInstrumentor):
120
+ super().__init__(
121
+ instrumentor=instrumentor,
122
+ category=PayiCategories.google_vertex,
123
+ streaming_type=_StreamingType.generator,
124
+ )
125
+ self._prompt_character_count = 0
126
+ self._candiates_character_count = 0
127
+
128
+ @override
129
+ def process_request(self, instance: Any, extra_headers: 'dict[str, str]', args: Sequence[Any], kwargs: Any) -> bool:
130
+ from google.genai.types import Content, PIL_Image, Part # type: ignore # noqa: F401 I001
131
+
132
+ if not kwargs:
133
+ return True
134
+
135
+ model: str = kwargs.get("model", "")
136
+ self._ingest["resource"] = "google." + model
137
+
138
+ value: Union[ # type: ignore
139
+ Content,
140
+ str,
141
+ PIL_Image,
142
+ Part,
143
+ List[Union[str, PIL_Image, Part]],
144
+ ] = kwargs.get("contents", None) # type: ignore
145
+
146
+ items: List[Union[str, Image, Part]] = [] # type: ignore # noqa: F401 I001
147
+
148
+ if not value:
149
+ raise TypeError("value must not be empty")
150
+
151
+ if isinstance(value, Content):
152
+ items = value.parts # type: ignore
153
+ if isinstance(value, (str, PIL_Image, Part)):
154
+ items = [value] # type: ignore
155
+ if isinstance(value, list):
156
+ items = value # type: ignore
157
+
158
+ for item in items: # type: ignore
159
+ text = ""
160
+ if isinstance(item, Part):
161
+ d = item.to_json_dict() # type: ignore
162
+ if "text" in d:
163
+ text = d["text"] # type: ignore
164
+ elif isinstance(item, str):
165
+ text = item
166
+
167
+ if text != "":
168
+ self._prompt_character_count += count_chars_skip_spaces(text) # type: ignore
169
+
170
+ return True
171
+
172
+ @override
173
+ def process_request_prompt(self, prompt: 'dict[str, Any]', args: Sequence[Any], kwargs: 'dict[str, Any]') -> None:
174
+ from google.genai.types import Content, PIL_Image, Part, Tool, GenerateContentConfig, GenerateContentConfigDict, ToolConfig # type: ignore # noqa: F401 I001
175
+
176
+ key = "contents"
177
+
178
+ if not kwargs:
179
+ return
180
+
181
+ value: Union[ # type: ignore
182
+ Content,
183
+ str,
184
+ PIL_Image,
185
+ Part,
186
+ List[Union[str, PIL_Image, Part]],
187
+ ] = kwargs.get("contents", None) # type: ignore
188
+
189
+ items: List[Union[str, Image, Part]] = [] # type: ignore # noqa: F401 I001
190
+
191
+ if not value:
192
+ return
193
+
194
+ if isinstance(value, str):
195
+ prompt[key] = Content(parts=[Part.from_text(text=value)]).to_json_dict() # type: ignore
196
+ elif isinstance(value, (PIL_Image, Part)):
197
+ prompt[key] = Content(parts=[value]).to_json_dict() # type: ignore
198
+ elif isinstance(value, Content):
199
+ prompt[key] = value.to_json_dict() # type: ignore
200
+ elif isinstance(value, list):
201
+ items = value # type: ignore
202
+ parts = []
203
+
204
+ for item in items: # type: ignore
205
+ if isinstance(item, str):
206
+ parts.append(Part.from_text(text=item)) # type: ignore
207
+ elif isinstance(item, Part):
208
+ parts.append(item) # type: ignore
209
+ # elif isinstance(item, PIL_Image): TODO
210
+ # parts.append(Part.from_image(item)) # type: ignore
211
+
212
+ prompt[key] = Content(parts=parts).to_json_dict() # type: ignore
213
+
214
+ # tools: Optional[list[Tool]] = kwargs.get("tools", None) # type: ignore
215
+ # if tools:
216
+ # t: list[dict[Any, Any]] = []
217
+ # for tool in tools: # type: ignore
218
+ # if isinstance(tool, Tool):
219
+ # t.append(tool.text=()) # type: ignore
220
+ # if t:
221
+ # prompt["tools"] = t
222
+ config_kwarg = kwargs.get("config", None) # type: ignore
223
+ if config_kwarg is None:
224
+ return
225
+
226
+ config: GenerateContentConfigDict = {}
227
+ if isinstance(config_kwarg, GenerateContentConfig):
228
+ config = config_kwarg.to_json_dict() # type: ignore
229
+ else:
230
+ config = config_kwarg
231
+
232
+ tools = config.get("tools", None) # type: ignore
233
+ if isinstance(tools, list):
234
+ t: list[dict[str, object]] = []
235
+ for tool in tools: # type: ignore
236
+ if isinstance(tool, Tool):
237
+ t.append(tool.to_json_dict()) # type: ignore
238
+ if t:
239
+ prompt["tools"] = t
240
+
241
+ tool_config = config.get("tool_config", None) # type: ignore
242
+ if isinstance(tool_config, ToolConfig):
243
+ prompt["tool_config"] = tool_config.to_json_dict() # type: ignore
244
+ elif isinstance(tool_config, dict):
245
+ prompt["tool_config"] = tool_config
246
+
247
+ @override
248
+ def process_chunk(self, chunk: Any) -> bool:
249
+ response_dict: dict[str, Any] = chunk.to_json_dict()
250
+ if "provider_response_id" not in self._ingest:
251
+ id = response_dict.get("response_id", None)
252
+ if id:
253
+ self._ingest["provider_response_id"] = id
254
+
255
+ model: str = response_dict.get("model_version", "")
256
+
257
+ self._ingest["resource"] = "google." + model
258
+
259
+ for candidate in response_dict.get("candidates", []):
260
+ parts = candidate.get("content", {}).get("parts", [])
261
+ for part in parts:
262
+ self._candiates_character_count += count_chars_skip_spaces(part.get("text", ""))
263
+
264
+ usage = response_dict.get("usage_metadata", {})
265
+ if usage and "prompt_token_count" in usage and "candidates_token_count" in usage:
266
+ self._compute_usage(response_dict, streaming_candidates_characters=self._candiates_character_count)
267
+
268
+ return True
269
+
270
+ @staticmethod
271
+ def _is_character_billing_model(model: str) -> bool:
272
+ return model.startswith("gemini-1.")
273
+
274
+ @override
275
+ def process_synchronous_response(
276
+ self,
277
+ response: Any,
278
+ log_prompt_and_response: bool,
279
+ kwargs: Any) -> Any:
280
+ response_dict = response.to_json_dict()
281
+
282
+ self._ingest["provider_response_id"] = response_dict["response_id"]
283
+ self._ingest["resource"] = "google." + response_dict["model_version"]
284
+
285
+ self._compute_usage(response_dict)
286
+
287
+ if log_prompt_and_response:
288
+ self._ingest["provider_response_json"] = [json.dumps(response_dict)]
289
+
290
+ return None
291
+
292
+ def add_units(self, key: str, input: Optional[int] = None, output: Optional[int] = None) -> None:
293
+ if key not in self._ingest["units"]:
294
+ self._ingest["units"][key] = {}
295
+ if input is not None:
296
+ self._ingest["units"][key]["input"] = input
297
+ if output is not None:
298
+ self._ingest["units"][key]["output"] = output
299
+
300
+ def _compute_usage(self, response_dict: 'dict[str, Any]', streaming_candidates_characters: Optional[int] = None) -> None:
301
+ usage = response_dict.get("usage_metadata", {})
302
+ input = usage.get("prompt_token_count", 0)
303
+
304
+ prompt_tokens_details: list[dict[str, Any]] = usage.get("prompt_tokens_details")
305
+ candidates_tokens_details: list[dict[str, Any]] = usage.get("candidates_tokens_details")
306
+
307
+ model: str = response_dict.get("model_version", "")
308
+
309
+ if self._is_character_billing_model(model):
310
+ # gemini 1.0 and 1.5 units are reported in characters, per second, per image, etc...
311
+ large_context = "" if input < 128000 else "_large_context"
312
+
313
+ for details in prompt_tokens_details:
314
+ modality = details.get("modality", "")
315
+ if not modality:
316
+ continue
317
+
318
+ modality_token_count = details.get("token_count", 0)
319
+ if modality == "TEXT":
320
+ input = self._prompt_character_count
321
+ if input == 0:
322
+ # back up calc if nothing was calculated from the prompt
323
+ input = response_dict["usage_metadata"]["prompt_token_count"] * 4
324
+
325
+ output = 0
326
+ if streaming_candidates_characters is None:
327
+ for candidate in response_dict.get("candidates", []):
328
+ parts = candidate.get("content", {}).get("parts", [])
329
+ for part in parts:
330
+ output += count_chars_skip_spaces(part.get("text", ""))
331
+
332
+ if output == 0:
333
+ # back up calc if no parts
334
+ output = response_dict["usage_metadata"]["candidates_token_count"] * 4
335
+ else:
336
+ output = streaming_candidates_characters
337
+
338
+ self._ingest["units"]["text"+large_context] = Units(input=input, output=output)
339
+
340
+ elif modality == "IMAGE":
341
+ num_images = math.ceil(modality_token_count / 258)
342
+ self.add_units("vision"+large_context, input=num_images)
343
+
344
+ elif modality == "VIDEO":
345
+ video_seconds = math.ceil(modality_token_count / 285)
346
+ self.add_units("video"+large_context, input=video_seconds)
347
+
348
+ elif modality == "AUDIO":
349
+ audio_seconds = math.ceil(modality_token_count / 25)
350
+ self.add_units("audio"+large_context, input=audio_seconds)
351
+
352
+ elif model.startswith("gemini-2.0"):
353
+ for details in prompt_tokens_details:
354
+ modality = details.get("modality", "")
355
+ if not modality:
356
+ continue
357
+
358
+ modality_token_count = details.get("token_count", 0)
359
+ if modality == "IMAGE":
360
+ self.add_units("vision", input=modality_token_count)
361
+ elif modality in ("VIDEO", "AUDIO", "TEXT"):
362
+ self.add_units(modality.lower(), input=modality_token_count)
363
+ for details in candidates_tokens_details:
364
+ modality = details.get("modality", "")
365
+ if not modality:
366
+ continue
367
+
368
+ modality_token_count = details.get("token_count", 0)
369
+ if modality in ("VIDEO", "AUDIO", "TEXT", "IMAGE"):
370
+ self.add_units(modality.lower(), output=modality_token_count)
@@ -10,7 +10,7 @@ from wrapt import wrap_function_wrapper # type: ignore
10
10
  from payi.lib.helpers import PayiCategories, PayiHeaderNames
11
11
  from payi.types.ingest_units_params import Units
12
12
 
13
- from .instrument import _IsStreaming, _ProviderRequest, _PayiInstrumentor
13
+ from .instrument import _IsStreaming, _StreamingType, _ProviderRequest, _PayiInstrumentor
14
14
 
15
15
 
16
16
  class OpenAiInstrumentor:
@@ -178,7 +178,11 @@ class _OpenAiProviderRequest(_ProviderRequest):
178
178
  responses_input_tokens_details_key: str = "input_tokens_details"
179
179
 
180
180
  def __init__(self, instrumentor: _PayiInstrumentor, input_tokens_key: str, output_tokens_key: str, input_tokens_details_key: str) -> None:
181
- super().__init__(instrumentor=instrumentor, category=PayiCategories.openai)
181
+ super().__init__(
182
+ instrumentor=instrumentor,
183
+ category=PayiCategories.openai,
184
+ streaming_type=_StreamingType.iterator,
185
+ )
182
186
  self._input_tokens_key = input_tokens_key
183
187
  self._output_tokens_key = output_tokens_key
184
188
  self._input_tokens_details_key = input_tokens_details_key