gatewaysdk 0.3.2__py3-none-any.whl

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.
Files changed (376) hide show
  1. gatewaysdk/__init__.py +540 -0
  2. gatewaysdk/adapter/__init__.py +15 -0
  3. gatewaysdk/adapter/base.py +94 -0
  4. gatewaysdk/adapter/messages.py +270 -0
  5. gatewaysdk/adapter/triplet.py +1026 -0
  6. gatewaysdk/algorithm/__init__.py +39 -0
  7. gatewaysdk/algorithm/apo/__init__.py +5 -0
  8. gatewaysdk/algorithm/apo/apo.py +898 -0
  9. gatewaysdk/algorithm/apo/prompts/apply_edit_variant01.poml +22 -0
  10. gatewaysdk/algorithm/apo/prompts/apply_edit_variant02.poml +18 -0
  11. gatewaysdk/algorithm/apo/prompts/text_gradient_variant01.poml +18 -0
  12. gatewaysdk/algorithm/apo/prompts/text_gradient_variant02.poml +16 -0
  13. gatewaysdk/algorithm/apo/prompts/text_gradient_variant03.poml +107 -0
  14. gatewaysdk/algorithm/base.py +262 -0
  15. gatewaysdk/algorithm/decorator.py +264 -0
  16. gatewaysdk/algorithm/evals/__init__.py +7 -0
  17. gatewaysdk/algorithm/evals/evals.py +217 -0
  18. gatewaysdk/algorithm/fast.py +250 -0
  19. gatewaysdk/algorithm/gepa/__init__.py +61 -0
  20. gatewaysdk/algorithm/gepa/adapter.py +495 -0
  21. gatewaysdk/algorithm/gepa/gepa.py +570 -0
  22. gatewaysdk/algorithm/gepa/lib/__init__.py +18 -0
  23. gatewaysdk/algorithm/gepa/lib/adapters/README.md +12 -0
  24. gatewaysdk/algorithm/gepa/lib/adapters/__init__.py +0 -0
  25. gatewaysdk/algorithm/gepa/lib/adapters/anymaths_adapter/README.md +341 -0
  26. gatewaysdk/algorithm/gepa/lib/adapters/anymaths_adapter/__init__.py +1 -0
  27. gatewaysdk/algorithm/gepa/lib/adapters/anymaths_adapter/anymaths_adapter.py +174 -0
  28. gatewaysdk/algorithm/gepa/lib/adapters/anymaths_adapter/requirements.txt +1 -0
  29. gatewaysdk/algorithm/gepa/lib/adapters/default_adapter/README.md +0 -0
  30. gatewaysdk/algorithm/gepa/lib/adapters/default_adapter/__init__.py +0 -0
  31. gatewaysdk/algorithm/gepa/lib/adapters/default_adapter/default_adapter.py +209 -0
  32. gatewaysdk/algorithm/gepa/lib/adapters/dspy_adapter/README.md +7 -0
  33. gatewaysdk/algorithm/gepa/lib/adapters/dspy_adapter/__init__.py +0 -0
  34. gatewaysdk/algorithm/gepa/lib/adapters/dspy_adapter/dspy_adapter.py +307 -0
  35. gatewaysdk/algorithm/gepa/lib/adapters/dspy_full_program_adapter/README.md +99 -0
  36. gatewaysdk/algorithm/gepa/lib/adapters/dspy_full_program_adapter/dspy_program_proposal_signature.py +137 -0
  37. gatewaysdk/algorithm/gepa/lib/adapters/dspy_full_program_adapter/full_program_adapter.py +268 -0
  38. gatewaysdk/algorithm/gepa/lib/adapters/generic_rag_adapter/GEPA_RAG.md +621 -0
  39. gatewaysdk/algorithm/gepa/lib/adapters/generic_rag_adapter/__init__.py +56 -0
  40. gatewaysdk/algorithm/gepa/lib/adapters/generic_rag_adapter/evaluation_metrics.py +226 -0
  41. gatewaysdk/algorithm/gepa/lib/adapters/generic_rag_adapter/generic_rag_adapter.py +496 -0
  42. gatewaysdk/algorithm/gepa/lib/adapters/generic_rag_adapter/rag_pipeline.py +238 -0
  43. gatewaysdk/algorithm/gepa/lib/adapters/generic_rag_adapter/vector_store_interface.py +212 -0
  44. gatewaysdk/algorithm/gepa/lib/adapters/generic_rag_adapter/vector_stores/__init__.py +2 -0
  45. gatewaysdk/algorithm/gepa/lib/adapters/generic_rag_adapter/vector_stores/chroma_store.py +196 -0
  46. gatewaysdk/algorithm/gepa/lib/adapters/generic_rag_adapter/vector_stores/lancedb_store.py +422 -0
  47. gatewaysdk/algorithm/gepa/lib/adapters/generic_rag_adapter/vector_stores/milvus_store.py +409 -0
  48. gatewaysdk/algorithm/gepa/lib/adapters/generic_rag_adapter/vector_stores/qdrant_store.py +368 -0
  49. gatewaysdk/algorithm/gepa/lib/adapters/generic_rag_adapter/vector_stores/weaviate_store.py +418 -0
  50. gatewaysdk/algorithm/gepa/lib/adapters/mcp_adapter/README.md +552 -0
  51. gatewaysdk/algorithm/gepa/lib/adapters/mcp_adapter/__init__.py +37 -0
  52. gatewaysdk/algorithm/gepa/lib/adapters/mcp_adapter/mcp_adapter.py +699 -0
  53. gatewaysdk/algorithm/gepa/lib/adapters/mcp_adapter/mcp_client.py +364 -0
  54. gatewaysdk/algorithm/gepa/lib/adapters/terminal_bench_adapter/README.md +9 -0
  55. gatewaysdk/algorithm/gepa/lib/adapters/terminal_bench_adapter/__init__.py +0 -0
  56. gatewaysdk/algorithm/gepa/lib/adapters/terminal_bench_adapter/terminal_bench_adapter.py +217 -0
  57. gatewaysdk/algorithm/gepa/lib/api.py +382 -0
  58. gatewaysdk/algorithm/gepa/lib/core/__init__.py +0 -0
  59. gatewaysdk/algorithm/gepa/lib/core/adapter.py +180 -0
  60. gatewaysdk/algorithm/gepa/lib/core/data_loader.py +74 -0
  61. gatewaysdk/algorithm/gepa/lib/core/engine.py +379 -0
  62. gatewaysdk/algorithm/gepa/lib/core/result.py +233 -0
  63. gatewaysdk/algorithm/gepa/lib/core/state.py +636 -0
  64. gatewaysdk/algorithm/gepa/lib/examples/__init__.py +0 -0
  65. gatewaysdk/algorithm/gepa/lib/examples/aime.py +24 -0
  66. gatewaysdk/algorithm/gepa/lib/examples/anymaths-bench/eval_default.py +111 -0
  67. gatewaysdk/algorithm/gepa/lib/examples/anymaths-bench/prompt-templates/instruction_prompt.txt +9 -0
  68. gatewaysdk/algorithm/gepa/lib/examples/anymaths-bench/prompt-templates/optimal_prompt.txt +24 -0
  69. gatewaysdk/algorithm/gepa/lib/examples/anymaths-bench/train_anymaths.py +177 -0
  70. gatewaysdk/algorithm/gepa/lib/examples/dspy_full_program_evolution/arc_agi.ipynb +25705 -0
  71. gatewaysdk/algorithm/gepa/lib/examples/dspy_full_program_evolution/example.ipynb +348 -0
  72. gatewaysdk/algorithm/gepa/lib/examples/mcp_adapter/__init__.py +4 -0
  73. gatewaysdk/algorithm/gepa/lib/examples/mcp_adapter/mcp_optimization_example.py +456 -0
  74. gatewaysdk/algorithm/gepa/lib/examples/rag_adapter/RAG_GUIDE.md +613 -0
  75. gatewaysdk/algorithm/gepa/lib/examples/rag_adapter/__init__.py +9 -0
  76. gatewaysdk/algorithm/gepa/lib/examples/rag_adapter/rag_optimization.py +820 -0
  77. gatewaysdk/algorithm/gepa/lib/examples/rag_adapter/requirements-rag.txt +29 -0
  78. gatewaysdk/algorithm/gepa/lib/examples/terminal-bench/prompt-templates/instruction_prompt.txt +16 -0
  79. gatewaysdk/algorithm/gepa/lib/examples/terminal-bench/prompt-templates/terminus.txt +9 -0
  80. gatewaysdk/algorithm/gepa/lib/examples/terminal-bench/train_terminus.py +161 -0
  81. gatewaysdk/algorithm/gepa/lib/gepa_utils.py +117 -0
  82. gatewaysdk/algorithm/gepa/lib/logging/__init__.py +0 -0
  83. gatewaysdk/algorithm/gepa/lib/logging/experiment_tracker.py +187 -0
  84. gatewaysdk/algorithm/gepa/lib/logging/logger.py +75 -0
  85. gatewaysdk/algorithm/gepa/lib/logging/utils.py +103 -0
  86. gatewaysdk/algorithm/gepa/lib/proposer/__init__.py +0 -0
  87. gatewaysdk/algorithm/gepa/lib/proposer/base.py +31 -0
  88. gatewaysdk/algorithm/gepa/lib/proposer/merge.py +357 -0
  89. gatewaysdk/algorithm/gepa/lib/proposer/reflective_mutation/__init__.py +0 -0
  90. gatewaysdk/algorithm/gepa/lib/proposer/reflective_mutation/base.py +49 -0
  91. gatewaysdk/algorithm/gepa/lib/proposer/reflective_mutation/reflective_mutation.py +176 -0
  92. gatewaysdk/algorithm/gepa/lib/py.typed +0 -0
  93. gatewaysdk/algorithm/gepa/lib/strategies/__init__.py +0 -0
  94. gatewaysdk/algorithm/gepa/lib/strategies/batch_sampler.py +77 -0
  95. gatewaysdk/algorithm/gepa/lib/strategies/candidate_selector.py +50 -0
  96. gatewaysdk/algorithm/gepa/lib/strategies/component_selector.py +36 -0
  97. gatewaysdk/algorithm/gepa/lib/strategies/eval_policy.py +64 -0
  98. gatewaysdk/algorithm/gepa/lib/strategies/instruction_proposal.py +126 -0
  99. gatewaysdk/algorithm/gepa/lib/utils/__init__.py +10 -0
  100. gatewaysdk/algorithm/gepa/lib/utils/stop_condition.py +196 -0
  101. gatewaysdk/algorithm/gepa/tracing.py +105 -0
  102. gatewaysdk/algorithm/utils.py +177 -0
  103. gatewaysdk/algorithm/verl/__init__.py +5 -0
  104. gatewaysdk/algorithm/verl/interface.py +202 -0
  105. gatewaysdk/automations.py +111 -0
  106. gatewaysdk/benchmark_hub/README.md +88 -0
  107. gatewaysdk/benchmark_hub/__init__.py +119 -0
  108. gatewaysdk/benchmark_hub/_archive.py +189 -0
  109. gatewaysdk/benchmark_hub/_tar.py +6 -0
  110. gatewaysdk/benchmark_hub/client.py +992 -0
  111. gatewaysdk/benchmark_hub/dispatch_shard.py +34 -0
  112. gatewaysdk/benchmark_hub/eval_config.py +20 -0
  113. gatewaysdk/benchmark_hub/evals.py +351 -0
  114. gatewaysdk/benchmark_hub/harbor_adapter.py +673 -0
  115. gatewaysdk/benchmark_hub/path_utils.py +81 -0
  116. gatewaysdk/benchmark_hub/save_utils.py +101 -0
  117. gatewaysdk/benchmark_hub/verifiers_adapter.py +327 -0
  118. gatewaysdk/benchmark_hub/versioning.py +72 -0
  119. gatewaysdk/build.py +515 -0
  120. gatewaysdk/cli/__init__.py +58 -0
  121. gatewaysdk/cli/agent_runner.py +132 -0
  122. gatewaysdk/cli/http_client.py +115 -0
  123. gatewaysdk/cli/platform.py +4086 -0
  124. gatewaysdk/cli/prometheus.py +115 -0
  125. gatewaysdk/cli/release_gate.py +215 -0
  126. gatewaysdk/cli/store.py +131 -0
  127. gatewaysdk/cli/vllm.py +29 -0
  128. gatewaysdk/client.py +406 -0
  129. gatewaysdk/config.py +348 -0
  130. gatewaysdk/connectors/__init__.py +25 -0
  131. gatewaysdk/connectors/client.py +203 -0
  132. gatewaysdk/connectors/skill.py +25 -0
  133. gatewaysdk/connectors/template.py +106 -0
  134. gatewaysdk/context.py +606 -0
  135. gatewaysdk/emitter/__init__.py +43 -0
  136. gatewaysdk/emitter/annotation.py +370 -0
  137. gatewaysdk/emitter/exception.py +54 -0
  138. gatewaysdk/emitter/message.py +61 -0
  139. gatewaysdk/emitter/object.py +117 -0
  140. gatewaysdk/emitter/reward.py +320 -0
  141. gatewaysdk/env_var.py +156 -0
  142. gatewaysdk/environment/__init__.py +108 -0
  143. gatewaysdk/environment/_bundle.py +281 -0
  144. gatewaysdk/environment/_harbor.py +276 -0
  145. gatewaysdk/environment/_materialize.py +97 -0
  146. gatewaysdk/environment/_tar.py +33 -0
  147. gatewaysdk/environment/_world.py +346 -0
  148. gatewaysdk/environment/core.py +527 -0
  149. gatewaysdk/environment/errors.py +58 -0
  150. gatewaysdk/environment/runtime.py +150 -0
  151. gatewaysdk/environment/schema/__init__.py +35 -0
  152. gatewaysdk/environment/schema/__main__.py +225 -0
  153. gatewaysdk/environment/schema/_slack_fidelity.py +116 -0
  154. gatewaysdk/environment/schema/_slack_scenario.py +183 -0
  155. gatewaysdk/environment/schema/api.py +1628 -0
  156. gatewaysdk/environment/schema/batch.py +755 -0
  157. gatewaysdk/environment/schema/compiler.py +615 -0
  158. gatewaysdk/environment/schema/conform.py +428 -0
  159. gatewaysdk/environment/schema/connector.py +777 -0
  160. gatewaysdk/environment/schema/connectors/apple-business-manager/handlers.py +17 -0
  161. gatewaysdk/environment/schema/connectors/apple-business-manager/parity.json +28 -0
  162. gatewaysdk/environment/schema/connectors/apple-business-manager/provenance.json +233 -0
  163. gatewaysdk/environment/schema/connectors/apple-business-manager/scope.toml +112 -0
  164. gatewaysdk/environment/schema/connectors/apple-business-manager/world.json +1380 -0
  165. gatewaysdk/environment/schema/connectors/base.json +28 -0
  166. gatewaysdk/environment/schema/connectors/custom/handlers.py +18 -0
  167. gatewaysdk/environment/schema/connectors/custom/world.json +123 -0
  168. gatewaysdk/environment/schema/connectors/github/handlers.py +81 -0
  169. gatewaysdk/environment/schema/connectors/github/parity.json +37 -0
  170. gatewaysdk/environment/schema/connectors/github/provenance.json +134 -0
  171. gatewaysdk/environment/schema/connectors/github/scope.toml +158 -0
  172. gatewaysdk/environment/schema/connectors/github/world.json +5869 -0
  173. gatewaysdk/environment/schema/connectors/google-calendar/handlers.py +23 -0
  174. gatewaysdk/environment/schema/connectors/google-calendar/world.json +209 -0
  175. gatewaysdk/environment/schema/connectors/google-drive/handlers.py +18 -0
  176. gatewaysdk/environment/schema/connectors/google-drive/world.json +286 -0
  177. gatewaysdk/environment/schema/connectors/jamf/handlers.py +34 -0
  178. gatewaysdk/environment/schema/connectors/jamf/parity.json +49 -0
  179. gatewaysdk/environment/schema/connectors/jamf/provenance.json +181 -0
  180. gatewaysdk/environment/schema/connectors/jamf/scope.toml +128 -0
  181. gatewaysdk/environment/schema/connectors/jamf/world.json +2218 -0
  182. gatewaysdk/environment/schema/connectors/jira/handlers.py +28 -0
  183. gatewaysdk/environment/schema/connectors/jira/world.json +389 -0
  184. gatewaysdk/environment/schema/connectors/kandji/handlers.py +38 -0
  185. gatewaysdk/environment/schema/connectors/kandji/parity.json +29 -0
  186. gatewaysdk/environment/schema/connectors/kandji/provenance.json +152 -0
  187. gatewaysdk/environment/schema/connectors/kandji/scope.toml +120 -0
  188. gatewaysdk/environment/schema/connectors/kandji/world.json +765 -0
  189. gatewaysdk/environment/schema/connectors/linear/handlers.py +20 -0
  190. gatewaysdk/environment/schema/connectors/linear/world.json +363 -0
  191. gatewaysdk/environment/schema/connectors/microsoft-teams/fidelity.json +45 -0
  192. gatewaysdk/environment/schema/connectors/microsoft-teams/handlers.py +1 -0
  193. gatewaysdk/environment/schema/connectors/microsoft-teams/provenance.json +851 -0
  194. gatewaysdk/environment/schema/connectors/microsoft-teams/scope.md +231 -0
  195. gatewaysdk/environment/schema/connectors/microsoft-teams/teams-types.json +1710 -0
  196. gatewaysdk/environment/schema/connectors/microsoft-teams/world.json +858 -0
  197. gatewaysdk/environment/schema/connectors/netsuite/handlers.py +32 -0
  198. gatewaysdk/environment/schema/connectors/netsuite/parity.json +50 -0
  199. gatewaysdk/environment/schema/connectors/netsuite/provenance.json +137 -0
  200. gatewaysdk/environment/schema/connectors/netsuite/scope.toml +78 -0
  201. gatewaysdk/environment/schema/connectors/netsuite/world.json +16243 -0
  202. gatewaysdk/environment/schema/connectors/salesforce/handlers.py +806 -0
  203. gatewaysdk/environment/schema/connectors/salesforce/parity.json +46 -0
  204. gatewaysdk/environment/schema/connectors/salesforce/provenance.json +143 -0
  205. gatewaysdk/environment/schema/connectors/salesforce/scope.toml +174 -0
  206. gatewaysdk/environment/schema/connectors/salesforce/world.json +4695 -0
  207. gatewaysdk/environment/schema/connectors/slack/MODEL.md +236 -0
  208. gatewaysdk/environment/schema/connectors/slack/capabilities.json +1039 -0
  209. gatewaysdk/environment/schema/connectors/slack/handlers.py +762 -0
  210. gatewaysdk/environment/schema/connectors/slack/parity.json +65 -0
  211. gatewaysdk/environment/schema/connectors/slack/provenance.json +294 -0
  212. gatewaysdk/environment/schema/connectors/slack/scope.toml +44 -0
  213. gatewaysdk/environment/schema/connectors/slack/world.json +4993 -0
  214. gatewaysdk/environment/schema/connectors/workday/handlers.py +75 -0
  215. gatewaysdk/environment/schema/connectors/workday/parity.json +36 -0
  216. gatewaysdk/environment/schema/connectors/workday/provenance.json +179 -0
  217. gatewaysdk/environment/schema/connectors/workday/scope.toml +180 -0
  218. gatewaysdk/environment/schema/connectors/workday/world.json +806 -0
  219. gatewaysdk/environment/schema/data.py +593 -0
  220. gatewaysdk/environment/schema/extract.py +263 -0
  221. gatewaysdk/environment/schema/host.py +2437 -0
  222. gatewaysdk/environment/schema/host_surfaces.py +275 -0
  223. gatewaysdk/environment/schema/platform.py +1272 -0
  224. gatewaysdk/environment/schema/scaffold.py +250 -0
  225. gatewaysdk/environment/schema/skill.py +118 -0
  226. gatewaysdk/environment/schema/skills/agents/connector-template-author.md +202 -0
  227. gatewaysdk/environment/schema/skills/connector-schema-authoring/CONNECTORS.md +331 -0
  228. gatewaysdk/environment/schema/skills/connector-schema-authoring/CONVENTIONS.md +100 -0
  229. gatewaysdk/environment/schema/skills/connector-schema-authoring/SKILL.md +313 -0
  230. gatewaysdk/environment/schema/skills/world-data-ingestion/ROWS.md +223 -0
  231. gatewaysdk/environment/schema/skills/world-data-ingestion/SKILL.md +293 -0
  232. gatewaysdk/environment/schema/skills/worlds-getting-started/SKILL.md +390 -0
  233. gatewaysdk/environment/schema/slack.py +757 -0
  234. gatewaysdk/environment/schema/snapshot.py +479 -0
  235. gatewaysdk/environment/schema/store.py +904 -0
  236. gatewaysdk/environment/schema/tasks.py +525 -0
  237. gatewaysdk/environment/schema/tools_world.py +322 -0
  238. gatewaysdk/environment/schema/validation.py +577 -0
  239. gatewaysdk/environment/schema/worker.py +78 -0
  240. gatewaysdk/environment/schema/workers.py +500 -0
  241. gatewaysdk/environment/schema/world_tests.py +336 -0
  242. gatewaysdk/environments/__init__.py +24 -0
  243. gatewaysdk/environments/client.py +486 -0
  244. gatewaysdk/environments/types.py +318 -0
  245. gatewaysdk/execution/__init__.py +15 -0
  246. gatewaysdk/execution/base.py +64 -0
  247. gatewaysdk/execution/client_server.py +443 -0
  248. gatewaysdk/execution/events.py +69 -0
  249. gatewaysdk/execution/inter_process.py +16 -0
  250. gatewaysdk/execution/shared_memory.py +282 -0
  251. gatewaysdk/experiments/__init__.py +90 -0
  252. gatewaysdk/experiments/assignment.py +177 -0
  253. gatewaysdk/experiments/client.py +877 -0
  254. gatewaysdk/experiments/exposure.py +222 -0
  255. gatewaysdk/experiments/types.py +81 -0
  256. gatewaysdk/importers/__init__.py +49 -0
  257. gatewaysdk/importers/_normalize.py +91 -0
  258. gatewaysdk/importers/builder.py +173 -0
  259. gatewaysdk/importers/client.py +95 -0
  260. gatewaysdk/importers/langsmith.py +196 -0
  261. gatewaysdk/importers/recipes.py +249 -0
  262. gatewaysdk/instrumentation/__init__.py +300 -0
  263. gatewaysdk/instrumentation/agentops.py +314 -0
  264. gatewaysdk/instrumentation/agentops_langchain.py +45 -0
  265. gatewaysdk/instrumentation/base.py +119 -0
  266. gatewaysdk/instrumentation/litellm.py +83 -0
  267. gatewaysdk/instrumentation/registry.py +273 -0
  268. gatewaysdk/instrumentation/vllm.py +81 -0
  269. gatewaysdk/instrumentation/weave.py +500 -0
  270. gatewaysdk/integrations/__init__.py +15 -0
  271. gatewaysdk/integrations/gateway/__init__.py +11 -0
  272. gatewaysdk/integrations/gateway/client.py +171 -0
  273. gatewaysdk/integrations/tool_access.py +549 -0
  274. gatewaysdk/litagent/__init__.py +11 -0
  275. gatewaysdk/litagent/decorator.py +536 -0
  276. gatewaysdk/litagent/litagent.py +252 -0
  277. gatewaysdk/llm_proxy.py +1742 -0
  278. gatewaysdk/logging.py +370 -0
  279. gatewaysdk/memory.py +278 -0
  280. gatewaysdk/personas.py +186 -0
  281. gatewaysdk/platform/__init__.py +17 -0
  282. gatewaysdk/platform/builder.py +221 -0
  283. gatewaysdk/platform/compatibility.py +67 -0
  284. gatewaysdk/platform/manifest.py +185 -0
  285. gatewaysdk/platform/orchestrator.py +901 -0
  286. gatewaysdk/platform/registry.py +122 -0
  287. gatewaysdk/platform/worker.py +864 -0
  288. gatewaysdk/replay/__init__.py +1032 -0
  289. gatewaysdk/replay/pytest.py +56 -0
  290. gatewaysdk/reward.py +7 -0
  291. gatewaysdk/run.py +1781 -0
  292. gatewaysdk/runner/__init__.py +11 -0
  293. gatewaysdk/runner/agent.py +878 -0
  294. gatewaysdk/runner/base.py +182 -0
  295. gatewaysdk/runner/legacy.py +309 -0
  296. gatewaysdk/security.py +700 -0
  297. gatewaysdk/semconv.py +170 -0
  298. gatewaysdk/server.py +399 -0
  299. gatewaysdk/sessions.py +282 -0
  300. gatewaysdk/store/__init__.py +45 -0
  301. gatewaysdk/store/base.py +908 -0
  302. gatewaysdk/store/client_server.py +2093 -0
  303. gatewaysdk/store/collection/__init__.py +30 -0
  304. gatewaysdk/store/collection/base.py +587 -0
  305. gatewaysdk/store/collection/memory.py +970 -0
  306. gatewaysdk/store/collection/mongo.py +1412 -0
  307. gatewaysdk/store/collection_based.py +1823 -0
  308. gatewaysdk/store/gateway.py +983 -0
  309. gatewaysdk/store/gateway_listener.py +465 -0
  310. gatewaysdk/store/listener.py +58 -0
  311. gatewaysdk/store/memory.py +396 -0
  312. gatewaysdk/store/mongo.py +165 -0
  313. gatewaysdk/store/redis_stream.py +517 -0
  314. gatewaysdk/store/sqlite.py +3 -0
  315. gatewaysdk/store/threading.py +370 -0
  316. gatewaysdk/store/utils.py +142 -0
  317. gatewaysdk/tracer/__init__.py +14 -0
  318. gatewaysdk/tracer/base.py +286 -0
  319. gatewaysdk/tracer/dummy.py +106 -0
  320. gatewaysdk/tracer/otel.py +559 -0
  321. gatewaysdk/tracing/__init__.py +110 -0
  322. gatewaysdk/tracing/api.py +808 -0
  323. gatewaysdk/tracing/attributes.py +9 -0
  324. gatewaysdk/tracing/context.py +272 -0
  325. gatewaysdk/tracing/exporters/__init__.py +10 -0
  326. gatewaysdk/tracing/exporters/gateway.py +228 -0
  327. gatewaysdk/tracing/identity.py +288 -0
  328. gatewaysdk/tracing/init.py +620 -0
  329. gatewaysdk/tracing/instrumentors/__init__.py +15 -0
  330. gatewaysdk/tracing/instrumentors/claude_agent_sdk.py +766 -0
  331. gatewaysdk/tracing/instrumentors/instrumentation_principles.md +294 -0
  332. gatewaysdk/tracing/instrumentors/registry.py +352 -0
  333. gatewaysdk/tracing/mapping.py +729 -0
  334. gatewaysdk/tracing/processors.py +393 -0
  335. gatewaysdk/tracing/push.py +617 -0
  336. gatewaysdk/tracing/push_models.py +247 -0
  337. gatewaysdk/tracing/semconv.py +294 -0
  338. gatewaysdk/tracing/span_builder.py +356 -0
  339. gatewaysdk/trainer/__init__.py +6 -0
  340. gatewaysdk/trainer/init_utils.py +263 -0
  341. gatewaysdk/trainer/legacy.py +359 -0
  342. gatewaysdk/trainer/registry.py +12 -0
  343. gatewaysdk/trainer/trainer.py +638 -0
  344. gatewaysdk/types/__init__.py +63 -0
  345. gatewaysdk/types/core.py +556 -0
  346. gatewaysdk/types/resources.py +204 -0
  347. gatewaysdk/types/tracer.py +515 -0
  348. gatewaysdk/types/tracing.py +162 -0
  349. gatewaysdk/users.py +251 -0
  350. gatewaysdk/utils/__init__.py +1 -0
  351. gatewaysdk/utils/id.py +18 -0
  352. gatewaysdk/utils/metrics.py +1025 -0
  353. gatewaysdk/utils/otel.py +550 -0
  354. gatewaysdk/utils/otlp.py +556 -0
  355. gatewaysdk/utils/redact.py +22 -0
  356. gatewaysdk/utils/server_launcher.py +1045 -0
  357. gatewaysdk/utils/system_snapshot.py +90 -0
  358. gatewaysdk/verl/__init__.py +8 -0
  359. gatewaysdk/verl/__main__.py +6 -0
  360. gatewaysdk/verl/async_server.py +46 -0
  361. gatewaysdk/verl/config.yaml +27 -0
  362. gatewaysdk/verl/daemon.py +1154 -0
  363. gatewaysdk/verl/dataset.py +44 -0
  364. gatewaysdk/verl/entrypoint.py +248 -0
  365. gatewaysdk/verl/trainer.py +549 -0
  366. gatewaysdk/world_browser.py +748 -0
  367. gatewaysdk/world_data.py +453 -0
  368. gatewaysdk/world_sessions.py +978 -0
  369. gatewaysdk/world_tasks.py +285 -0
  370. gatewaysdk/world_tools.py +115 -0
  371. gatewaysdk/worlds.py +639 -0
  372. gatewaysdk-0.3.2.dist-info/METADATA +236 -0
  373. gatewaysdk-0.3.2.dist-info/RECORD +376 -0
  374. gatewaysdk-0.3.2.dist-info/WHEEL +4 -0
  375. gatewaysdk-0.3.2.dist-info/entry_points.txt +5 -0
  376. gatewaysdk-0.3.2.dist-info/licenses/LICENSE +19 -0
@@ -0,0 +1,559 @@
1
+ # Copyright (c) Microsoft. All rights reserved.
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ import logging
7
+ import threading
8
+ import warnings
9
+ from contextlib import asynccontextmanager, contextmanager
10
+ from typing import Any, AsyncGenerator, Awaitable, Iterator, List, Optional
11
+
12
+ import opentelemetry.trace as trace_api
13
+ from opentelemetry.sdk.trace import ReadableSpan, SpanProcessor
14
+ from opentelemetry.sdk.trace import TracerProvider as TracerProviderImpl
15
+ from opentelemetry.sdk.trace.export import BatchSpanProcessor, SimpleSpanProcessor
16
+
17
+ from gatewaysdk.store.base import LightningStore
18
+ from gatewaysdk.types import Attributes, Span, SpanCoreFields, SpanRecordingContext, StatusCode, TraceStatus
19
+ from gatewaysdk.types.tracer import convert_timestamp
20
+ from gatewaysdk.utils.otlp import LightningStoreOTLPExporter
21
+
22
+ from .base import Tracer, with_active_tracer_context
23
+
24
+ logger = logging.getLogger(__name__)
25
+
26
+ STORE_WRITE_TIMEOUT_SECONDS = 10.0
27
+
28
+
29
+ def to_otel_status_code(status_code: StatusCode) -> trace_api.StatusCode:
30
+ if status_code == "UNSET":
31
+ return trace_api.StatusCode.UNSET
32
+ elif status_code == "ERROR":
33
+ return trace_api.StatusCode.ERROR
34
+ else:
35
+ return trace_api.StatusCode.OK
36
+
37
+
38
+ class OtelSpanRecordingContext(SpanRecordingContext):
39
+ def __init__(self, span: trace_api.Span) -> None:
40
+ self._span = span
41
+
42
+ def record_exception(self, exception: BaseException) -> None:
43
+ self._span.record_exception(exception)
44
+ self.record_status("ERROR", str(exception))
45
+
46
+ def record_attributes(self, attributes: Attributes) -> None:
47
+ self._span.set_attributes(attributes)
48
+
49
+ def record_status(self, status_code: StatusCode, description: Optional[str] = None) -> None:
50
+ otel_status_code = to_otel_status_code(status_code)
51
+ self._span.set_status(otel_status_code, description)
52
+
53
+ def get_otel_span(self) -> trace_api.Span:
54
+ return self._span
55
+
56
+ def get_recorded_span(self) -> SpanCoreFields:
57
+ if isinstance(self._span, ReadableSpan):
58
+ return SpanCoreFields(
59
+ name=self._span.name,
60
+ attributes=dict(self._span.attributes) if self._span.attributes else {},
61
+ start_time=convert_timestamp(self._span.start_time),
62
+ end_time=convert_timestamp(self._span.end_time),
63
+ status=TraceStatus.from_opentelemetry(self._span.status),
64
+ )
65
+ else:
66
+ raise ValueError(f"Span is not a ReadableSpan: {self._span}")
67
+
68
+
69
+ class OtelTracer(Tracer):
70
+ """Tracer that provides a basic OpenTelemetry tracer provider.
71
+
72
+ You should be able to collect gatewaysdk signals like rewards with this tracer,
73
+ but no other function instrumentations like `openai.chat.completion`.
74
+ """
75
+
76
+ def __init__(self):
77
+ super().__init__()
78
+ # This provider is only initialized when the worker is initialized.
79
+ self._tracer_provider: Optional[trace_api.TracerProvider] = None
80
+ self._lightning_span_processor: Optional[LightningSpanProcessor] = None
81
+ self._simple_span_processor: Optional[SimpleSpanProcessor] = None
82
+ self._otlp_span_exporter: Optional[LightningStoreOTLPExporter] = None
83
+ self._initialized: bool = False
84
+
85
+ def init_worker(self, worker_id: int, store: Optional[LightningStore] = None):
86
+ super().init_worker(worker_id, store)
87
+ self._initialize_tracer_provider(worker_id)
88
+
89
+ def _initialize_tracer_provider(self, worker_id: int):
90
+ logger.info(f"[Worker {worker_id}] Setting up OpenTelemetry tracer...")
91
+
92
+ if self._initialized:
93
+ logger.info(f"[Worker {worker_id}] Tracer provider is already initialized. Skipping initialization.")
94
+ return
95
+
96
+ # Delegate provider creation to tracing.init() — this ensures:
97
+ # 1. A single global TracerProvider exists
98
+ # 2. TracingContextSpanProcessor is registered (auto-injects run_id,
99
+ # session_id, call_type, tags, environment into ALL spans)
100
+ # We pass exporters=[] to suppress Gateway OTLP auto-detection since
101
+ # we manage our own LightningStoreOTLPExporter below.
102
+ from gatewaysdk.tracing.init import init as tracing_init
103
+
104
+ tracing_init(
105
+ service_name="gatewaysdk-trainer",
106
+ exporters=[],
107
+ instrument_default=False,
108
+ )
109
+
110
+ # Retrieve the global provider (created or reused by tracing.init)
111
+ provider = trace_api.get_tracer_provider()
112
+ if not isinstance(provider, TracerProviderImpl):
113
+ raise RuntimeError(
114
+ f"Expected TracerProviderImpl after tracing.init(), got {type(provider)}. "
115
+ "Ensure no other library replaces the global TracerProvider."
116
+ )
117
+ self._tracer_provider = provider
118
+
119
+ # Add LightningSpanProcessor for local span recording + store submission
120
+ self._lightning_span_processor = LightningSpanProcessor()
121
+ self._tracer_provider.add_span_processor(self._lightning_span_processor)
122
+
123
+ # Add LightningStoreOTLPExporter for OTLP export to Gateway
124
+ self._otlp_span_exporter = LightningStoreOTLPExporter()
125
+ self._simple_span_processor = SimpleSpanProcessor(self._otlp_span_exporter)
126
+ self._tracer_provider.add_span_processor(self._simple_span_processor)
127
+
128
+ self._initialized = True
129
+ logger.info(f"[Worker {worker_id}] OpenTelemetry tracer provider initialized (via tracing.init).")
130
+
131
+ def teardown_worker(self, worker_id: int):
132
+ super().teardown_worker(worker_id)
133
+ logger.info(f"[Worker {worker_id}] Tearing down OpenTelemetry tracer does NOT remove the tracer provider.")
134
+
135
+ @with_active_tracer_context
136
+ @asynccontextmanager
137
+ async def trace_context(
138
+ self,
139
+ name: Optional[str] = None,
140
+ *,
141
+ store: Optional[LightningStore] = None,
142
+ rollout_id: Optional[str] = None,
143
+ attempt_id: Optional[str] = None,
144
+ ) -> AsyncGenerator[trace_api.Tracer, None]:
145
+ """
146
+ Starts a new tracing context. This should be used as a context manager.
147
+
148
+ Args:
149
+ name: Optional name for the tracing context.
150
+ store: Optional store to add the spans to.
151
+ rollout_id: Optional rollout ID to add the spans to.
152
+ attempt_id: Optional attempt ID to add the spans to.
153
+
154
+ Yields:
155
+ The OpenTelemetry tracer instance to collect spans.
156
+ """
157
+ if not self._lightning_span_processor:
158
+ raise RuntimeError("LightningSpanProcessor is not initialized. Call init_worker() first.")
159
+
160
+ if store is not None:
161
+ warnings.warn(
162
+ "store is deprecated in favor of init_worker(). It will be removed in the future.",
163
+ DeprecationWarning,
164
+ stacklevel=3,
165
+ )
166
+ else:
167
+ store = self._store
168
+
169
+ if rollout_id is not None and attempt_id is not None:
170
+ if store is None:
171
+ raise ValueError("store is required to be initialized when rollout_id and attempt_id are provided")
172
+ if store.capabilities.get("otlp_traces", False) is True:
173
+ logger.debug(f"Tracing to LightningStore rollout_id={rollout_id}, attempt_id={attempt_id}")
174
+ await self._enable_native_otlp_exporter(store, rollout_id, attempt_id)
175
+ else:
176
+ self._disable_native_otlp_exporter()
177
+ ctx = self._lightning_span_processor.with_context(store=store, rollout_id=rollout_id, attempt_id=attempt_id)
178
+ with ctx:
179
+ yield trace_api.get_tracer(__name__, tracer_provider=self._tracer_provider)
180
+ elif rollout_id is None and attempt_id is None:
181
+ self._disable_native_otlp_exporter()
182
+ with self._lightning_span_processor:
183
+ yield trace_api.get_tracer(__name__, tracer_provider=self._tracer_provider)
184
+ else:
185
+ raise ValueError("rollout_id and attempt_id must be either all provided or all None")
186
+
187
+ def create_span(
188
+ self,
189
+ name: str,
190
+ attributes: Optional[Attributes] = None,
191
+ timestamp: Optional[float] = None,
192
+ status: Optional[TraceStatus] = None,
193
+ ) -> SpanCoreFields:
194
+ # Fire the span to the current active tracer provider.
195
+ tracer_provider = self._get_tracer_provider()
196
+ tracer = tracer_provider.get_tracer(__name__)
197
+ span = tracer.start_span(
198
+ name, attributes=attributes, start_time=int(timestamp * 1_000_000_000) if timestamp else None
199
+ )
200
+ if status is not None:
201
+ span.set_status(to_otel_status_code(status.status_code), status.description)
202
+ span.end(int(timestamp * 1_000_000_000) if timestamp else None)
203
+
204
+ # The span should have been auto-created by now.
205
+ # Return the core fields of the span.
206
+ if isinstance(span, ReadableSpan):
207
+ return SpanCoreFields(
208
+ name=name,
209
+ attributes=dict(span.attributes) if span.attributes else {},
210
+ start_time=convert_timestamp(span.start_time),
211
+ end_time=convert_timestamp(span.end_time),
212
+ status=TraceStatus.from_opentelemetry(span.status),
213
+ )
214
+ else:
215
+ raise ValueError(f"Span is not a ReadableSpan: {span}")
216
+
217
+ @contextmanager
218
+ def operation_context(
219
+ self,
220
+ name: str,
221
+ attributes: Optional[Attributes] = None,
222
+ start_time: Optional[float] = None,
223
+ end_time: Optional[float] = None,
224
+ ) -> Iterator[SpanRecordingContext]:
225
+ if end_time is not None:
226
+ logger.warning("OpenTelemetry doesn't support customizing the end time of a span. End time is ignored.")
227
+ # Record the span to the current active tracer provider.
228
+ tracer_provider = self._get_tracer_provider()
229
+ tracer = tracer_provider.get_tracer(__name__)
230
+
231
+ # Activate the span as the current span within otel.
232
+ with tracer.start_as_current_span(
233
+ name, attributes=attributes, start_time=int(start_time * 1_000_000_000) if start_time else None
234
+ ) as span:
235
+ recording_context = OtelSpanRecordingContext(span)
236
+ try:
237
+ yield recording_context
238
+ except Exception as exc:
239
+ recording_context.record_exception(exc)
240
+ raise
241
+
242
+ # No need to retrieve the span here. It's already been sent to otel processor.
243
+
244
+ def get_last_trace(self) -> List[Span]:
245
+ """
246
+ Retrieves the raw list of captured spans from the most recent trace.
247
+
248
+ Returns:
249
+ A list of [`Span`][gatewaysdk.Span] objects captured during the most recent trace.
250
+ """
251
+ if not self._lightning_span_processor:
252
+ raise RuntimeError("LightningSpanProcessor is not initialized. Call init_worker() first.")
253
+ return self._lightning_span_processor.spans()
254
+
255
+ def _get_tracer_provider(self) -> TracerProviderImpl:
256
+ if self._tracer_provider is None:
257
+ raise RuntimeError("TracerProvider is not initialized. Call init_worker() first.")
258
+ if not isinstance(self._tracer_provider, TracerProviderImpl):
259
+ raise TypeError(f"TracerProvider is not a opentelemetry.sdk.trace.TracerProvider: {self._tracer_provider}")
260
+ return self._tracer_provider
261
+
262
+ async def _enable_native_otlp_exporter(self, store: LightningStore, rollout_id: str, attempt_id: str):
263
+ tracer_provider = self._get_tracer_provider()
264
+ active_span_processor = tracer_provider._active_span_processor # pyright: ignore[reportPrivateUsage]
265
+
266
+ # Note: We do NOT mutate tracer_provider._resource here. The
267
+ # LightningStoreOTLPExporter uses SpanWithResource wrappers to enrich
268
+ # spans with rollout_id/attempt_id at export time, without mutating
269
+ # shared ReadableSpan or TracerProvider resources.
270
+
271
+ # Fetch rollout metadata for tracing configuration (environment, tags)
272
+ environment = None
273
+ tags = None
274
+ try:
275
+ rollout = await store.get_rollout_by_id(rollout_id)
276
+ if rollout and rollout.metadata:
277
+ environment = rollout.metadata.get("environment")
278
+ tags = rollout.metadata.get("tags")
279
+ logger.debug(
280
+ f"Fetched tracing metadata for rollout {rollout_id}: environment={environment}, tags={tags}"
281
+ )
282
+ except Exception as e:
283
+ logger.warning(f"Failed to fetch rollout metadata for {rollout_id}: {e}")
284
+
285
+ instrumented = False
286
+ candidates: List[str] = []
287
+ for processor in active_span_processor._span_processors: # pyright: ignore[reportPrivateUsage]
288
+ if isinstance(processor, LightningSpanProcessor):
289
+ # We don't need the LightningSpanProcessor any more.
290
+ logger.debug("LightningSpanProcessor already present in TracerProvider, disabling it.")
291
+ processor.disable_store_submission = True
292
+ elif isinstance(processor, (SimpleSpanProcessor, BatchSpanProcessor)):
293
+ # Instead, we rely on the OTLPSpanExporter to send spans to the store.
294
+ if isinstance(processor.span_exporter, LightningStoreOTLPExporter):
295
+ # Get optional auth headers from store
296
+ otlp_headers = None
297
+ if hasattr(store, "get_otlp_headers"):
298
+ otlp_headers = store.get_otlp_headers()
299
+ processor.span_exporter.enable_store_otlp(
300
+ store.otlp_traces_endpoint(),
301
+ rollout_id,
302
+ attempt_id,
303
+ headers=otlp_headers,
304
+ environment=environment,
305
+ tags=tags,
306
+ )
307
+ logger.debug(f"Set LightningStoreOTLPExporter endpoint to {store.otlp_traces_endpoint()}")
308
+ instrumented = True
309
+ else:
310
+ candidates.append(
311
+ f"{processor.__class__.__name__} with {processor.span_exporter.__class__.__name__}"
312
+ )
313
+ else:
314
+ candidates.append(f"{processor.__class__.__name__}")
315
+
316
+ if not instrumented:
317
+ raise RuntimeError(
318
+ "Failed to enable native OTLP exporter: no BatchSpanProcessor or SimpleSpanProcessor with "
319
+ "LightningStoreOTLPExporter found in TracerProvider. Please try using a non-OTLP store."
320
+ "Candidates are: " + ", ".join(candidates)
321
+ )
322
+
323
+ def _disable_native_otlp_exporter(self):
324
+ tracer_provider = self._get_tracer_provider()
325
+ active_span_processor = tracer_provider._active_span_processor # pyright: ignore[reportPrivateUsage]
326
+ for processor in active_span_processor._span_processors: # pyright: ignore[reportPrivateUsage]
327
+ if isinstance(processor, LightningSpanProcessor):
328
+ logger.debug("Enabling LightningSpanProcessor in TracerProvider.")
329
+ processor.disable_store_submission = False
330
+ elif isinstance(processor, (SimpleSpanProcessor, BatchSpanProcessor)):
331
+ if isinstance(processor.span_exporter, LightningStoreOTLPExporter):
332
+ processor.span_exporter.disable_store_otlp()
333
+
334
+
335
+ class LightningSpanProcessor(SpanProcessor):
336
+ """Span processor that subclasses OpenTelemetry's `SpanProcessor` and adds support to dump traces
337
+ to a [`LightningStore`][gatewaysdk.LightningStore].
338
+
339
+ It serves two purposes:
340
+
341
+ 1. Records all the spans in a local buffer.
342
+ 2. Submits the spans to the event loop to be added to the store.
343
+ """
344
+
345
+ def __init__(self, disable_store_submission: bool = False):
346
+ self._disable_store_submission: bool = disable_store_submission
347
+ self._spans: List[Span] = []
348
+
349
+ # Store related context and states
350
+ self._store: Optional[LightningStore] = None
351
+ self._rollout_id: Optional[str] = None
352
+ self._attempt_id: Optional[str] = None
353
+ self._local_sequence_id: int = 0
354
+ self._lock = threading.Lock()
355
+
356
+ # private asyncio loop running in a daemon thread
357
+ self._loop_ready = threading.Event()
358
+ self._loop: Optional[asyncio.AbstractEventLoop] = None
359
+ self._loop_thread: Optional[threading.Thread] = None
360
+ self._loop_init_lock = threading.Lock()
361
+
362
+ def __repr__(self) -> str:
363
+ return (
364
+ f"{self.__class__.__name__}("
365
+ + f"disable_store_submission={self.disable_store_submission}, "
366
+ + f"store={self.store!r}, "
367
+ + f"rollout_id={self.rollout_id!r}, "
368
+ + f"attempt_id={self.attempt_id!r})"
369
+ )
370
+
371
+ @property
372
+ def store(self) -> Optional[LightningStore]:
373
+ """The store to submit the spans to."""
374
+ return self._store
375
+
376
+ @property
377
+ def rollout_id(self) -> Optional[str]:
378
+ """The rollout ID to submit the spans to."""
379
+ return self._rollout_id
380
+
381
+ @property
382
+ def attempt_id(self) -> Optional[str]:
383
+ """The attempt ID to submit the spans to."""
384
+ return self._attempt_id
385
+
386
+ @property
387
+ def disable_store_submission(self) -> bool:
388
+ """Whether to disable submitting spans to the store."""
389
+ return self._disable_store_submission
390
+
391
+ @disable_store_submission.setter
392
+ def disable_store_submission(self, value: bool) -> None:
393
+ self._disable_store_submission = value
394
+
395
+ def _ensure_loop(self) -> None:
396
+ # Fast path: loop already initialized
397
+ if self._loop_thread is not None and self._loop is not None:
398
+ return
399
+
400
+ with self._loop_init_lock:
401
+ # Double-check after acquiring lock
402
+ if self._loop_thread is not None and self._loop is not None:
403
+ return
404
+ self._loop_ready.clear()
405
+ self._loop_thread = threading.Thread(target=self._loop_runner, name="otel-loop", daemon=True)
406
+ self._loop_thread.start()
407
+ if not self._loop_ready.wait(timeout=30.0):
408
+ raise RuntimeError("Timed out waiting for otel-loop thread to start")
409
+
410
+ def _loop_runner(self):
411
+ loop = asyncio.new_event_loop()
412
+ self._loop = loop
413
+ asyncio.set_event_loop(loop)
414
+ self._loop_ready.set()
415
+ loop.run_forever()
416
+ loop.close()
417
+
418
+ def __enter__(self):
419
+ self._last_trace = None
420
+ self._spans = []
421
+ return self
422
+
423
+ def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any):
424
+ self._store = None
425
+ self._rollout_id = None
426
+ self._attempt_id = None
427
+
428
+ def _await_in_loop(self, coro: Awaitable[Any], timeout: Optional[float] = None) -> Any:
429
+ # submit to the dedicated loop and wait synchronously
430
+ self._ensure_loop()
431
+ if self._loop is None:
432
+ raise RuntimeError("Loop is not initialized. This should not happen.")
433
+
434
+ # If already on the exporter loop thread, schedule and return immediately.
435
+ # ---------------------------------------------------------------------------
436
+ # WHY THIS CONDITIONAL EXISTS:
437
+ # In rare cases, span.end() is triggered from a LangchainCallbackHandler.__del__
438
+ # (or another finalizer) while the Python garbage collector is running on the
439
+ # *same thread* that owns our exporter event loop ("otel-loop").
440
+ #
441
+ # When that happens, on_end() executes on the exporter loop thread itself.
442
+ # If we were to call `asyncio.run_coroutine_threadsafe(...).result()` here,
443
+ # it would deadlock immediately — because the loop cannot both wait on and run
444
+ # the same coroutine. The Future stays pending forever and the loop stops
445
+ # processing scheduled callbacks.
446
+ #
447
+ # To avoid that self-deadlock, we detect when on_end() runs on the exporter
448
+ # loop thread. If so, we *schedule* the coroutine on the loop (fire-and-forget)
449
+ # instead of blocking with .result().
450
+ #
451
+ # This situation can occur because Python calls __del__ in whatever thread
452
+ # releases the last reference, which can easily be our loop thread if the
453
+ # object is dereferenced during loop._run_once().
454
+ # ---------------------------------------------------------------------------
455
+ if threading.current_thread() is self._loop_thread:
456
+ self._loop.call_soon_threadsafe(asyncio.create_task, coro) # type: ignore
457
+ return None
458
+
459
+ fut = asyncio.run_coroutine_threadsafe(coro, self._loop) # type: ignore
460
+ return fut.result(timeout=timeout) # raises on error # type: ignore
461
+
462
+ def shutdown(self) -> None:
463
+ if self._loop:
464
+ self._loop.call_soon_threadsafe(self._loop.stop)
465
+ self._loop = None
466
+ if self._loop_thread:
467
+ self._loop_thread.join(timeout=5)
468
+
469
+ def force_flush(self, timeout_millis: int = 30000) -> bool:
470
+ return True
471
+
472
+ def spans(self) -> List[Span]:
473
+ """
474
+ Get the list of spans collected by this processor.
475
+ This is useful for debugging and testing purposes.
476
+
477
+ Returns:
478
+ List of [`Span`][gatewaysdk.Span] objects collected during tracing.
479
+ """
480
+ return self._spans
481
+
482
+ def with_context(self, store: LightningStore, rollout_id: str, attempt_id: str):
483
+ # simple context manager without nesting into asyncio
484
+ class _Ctx:
485
+ def __enter__(_): # type: ignore
486
+ # Use _ instead of self to avoid shadowing the instance method.
487
+ with self._lock:
488
+ self._store, self._rollout_id, self._attempt_id = store, rollout_id, attempt_id
489
+ self._last_trace = None
490
+ self._spans = []
491
+ return self
492
+
493
+ def __exit__(_, exc_type, exc, tb): # type: ignore
494
+ with self._lock:
495
+ self._store = self._rollout_id = self._attempt_id = None
496
+
497
+ return _Ctx()
498
+
499
+ def on_end(self, span: ReadableSpan) -> None:
500
+ """
501
+ Process a span when it ends.
502
+
503
+ Args:
504
+ span: The span that has ended.
505
+ """
506
+ # Skip if span is not sampled
507
+ if not span.context or not span.context.trace_flags.sampled:
508
+ return
509
+
510
+ if not self._disable_store_submission and self._store and self._rollout_id and self._attempt_id:
511
+ try:
512
+ # Submit add_otel_span to the event loop and wait for it to complete
513
+ from opentelemetry.instrumentation.utils import suppress_instrumentation
514
+
515
+ with suppress_instrumentation():
516
+ self._ensure_loop()
517
+ uploaded_span = self._await_in_loop(
518
+ self._store.add_otel_span(self._rollout_id, self._attempt_id, span),
519
+ timeout=STORE_WRITE_TIMEOUT_SECONDS,
520
+ )
521
+ if uploaded_span is not None:
522
+ self._spans.append(uploaded_span)
523
+ except TimeoutError:
524
+ logger.warning(
525
+ "Timed out adding span %s to store after %.1f seconds. The span will be stored locally "
526
+ "but it's not guaranteed to be persisted.",
527
+ span.name,
528
+ STORE_WRITE_TIMEOUT_SECONDS,
529
+ )
530
+ self._spans.append(
531
+ Span.from_opentelemetry(
532
+ span,
533
+ rollout_id=self._rollout_id,
534
+ attempt_id=self._attempt_id,
535
+ sequence_id=self._local_sequence_id,
536
+ )
537
+ )
538
+ except Exception:
539
+ # log; on_end MUST NOT raise
540
+ logger.exception(f"Error adding span to store: {span.name}. The span will be store locally only.")
541
+ self._spans.append(
542
+ Span.from_opentelemetry(
543
+ span,
544
+ rollout_id=self._rollout_id,
545
+ attempt_id=self._attempt_id,
546
+ sequence_id=self._local_sequence_id,
547
+ )
548
+ )
549
+
550
+ else:
551
+ # Fallback path
552
+ created_span = Span.from_opentelemetry(
553
+ span,
554
+ rollout_id=self._rollout_id or "rollout-dummy",
555
+ attempt_id=self._attempt_id or "attempt-dummy",
556
+ sequence_id=self._local_sequence_id,
557
+ )
558
+ self._local_sequence_id += 1
559
+ self._spans.append(created_span)
@@ -0,0 +1,110 @@
1
+ # Copyright (c) Gateway. All rights reserved.
2
+
3
+ """Standalone tracing module for GatewayDK.
4
+
5
+ This module provides a "one-liner" entry point for instrumenting applications
6
+ with OpenTelemetry, compatible with OpenInference ecosystem,
7
+ while natively supporting export to Gateway.
8
+
9
+ Example usage::
10
+
11
+ import gatewaysdk.tracing as tracing
12
+
13
+ # Auto-detect Gateway from environment variables
14
+ tracing.init()
15
+
16
+ # Use as a decorator (auto-captures input/output)
17
+ @tracing.trace
18
+ def my_function(query: str) -> dict:
19
+ return {"result": query.upper()}
20
+
21
+ # Use as a context manager (explicit input/output)
22
+ with tracing.trace("my-workflow", input=query) as span:
23
+ result = do_work()
24
+ span.set_attribute(tracing.semconv.TRACE_OUTPUT, result)
25
+
26
+ # Use semantic conventions for rich metadata
27
+ span.set_attribute(tracing.semconv.USER_ID, "user-123")
28
+ span.set_attribute(tracing.semconv.SESSION_ID, "session-456")
29
+
30
+ # Ensure spans are flushed on shutdown
31
+ tracing.shutdown()
32
+
33
+ Environment variables for Gateway auto-detect:
34
+ - GATEWAY_HOST: The Gateway server URL (e.g., "https://app.gateway.dev")
35
+ - GATEWAY_PUBLIC_KEY: The public key for authentication (pk-lf-...)
36
+ - GATEWAY_SECRET_KEY: The secret key for authentication (sk-lf-...)
37
+ - GATEWAY_OTLP_ENDPOINT: Optional override for the OTLP endpoint
38
+ """
39
+
40
+ from . import semconv
41
+ from .api import aspan, atrace, get_current_span, span, tool, trace
42
+
43
+ # Re-export context managers and decorators for tagging traces
44
+ from .context import (
45
+ session,
46
+ call_type_decorator,
47
+ get_current_call_type,
48
+ get_current_environment,
49
+ get_current_tags,
50
+ tracing_context,
51
+ )
52
+
53
+ # Re-export exporter factory for explicit configuration
54
+ from .exporters.gateway import gateway as gateway_exporter
55
+ from .init import flush, init, instrument, shutdown
56
+
57
+ # JSON push-to-trace
58
+ from .push import inspect, push, validate
59
+ from .push_models import (
60
+ ChildMapping,
61
+ FieldInfo,
62
+ InspectResult,
63
+ PushError,
64
+ PushResult,
65
+ SpanPreview,
66
+ TraceMapping,
67
+ TracePreview,
68
+ ValidationIssue,
69
+ )
70
+
71
+ __all__ = [
72
+ # Initialization
73
+ "init",
74
+ "instrument",
75
+ "shutdown",
76
+ "flush",
77
+ # Context managers / decorators (sync)
78
+ "trace",
79
+ "span",
80
+ "tool",
81
+ # Context managers (async)
82
+ "atrace",
83
+ "aspan",
84
+ # Span utilities
85
+ "get_current_span",
86
+ # Semantic conventions
87
+ "semconv",
88
+ # Exporter factory
89
+ "gateway_exporter",
90
+ # Tracing context and decorators
91
+ "session",
92
+ "tracing_context",
93
+ "call_type_decorator",
94
+ "get_current_call_type",
95
+ "get_current_tags",
96
+ "get_current_environment",
97
+ # JSON push-to-trace
98
+ "push",
99
+ "validate",
100
+ "inspect",
101
+ "TraceMapping",
102
+ "ChildMapping",
103
+ "PushResult",
104
+ "PushError",
105
+ "TracePreview",
106
+ "SpanPreview",
107
+ "ValidationIssue",
108
+ "InspectResult",
109
+ "FieldInfo",
110
+ ]