synth-ai 0.2.0__py3-none-any.whl → 0.2.1.dev0__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 (266) hide show
  1. synth_ai/__init__.py +28 -2
  2. synth_ai/core/system.py +4 -0
  3. synth_ai/environments/__init__.py +35 -0
  4. synth_ai/environments/environment/__init__.py +1 -0
  5. synth_ai/environments/environment/artifacts/__init__.py +1 -0
  6. synth_ai/environments/environment/artifacts/base.py +50 -0
  7. synth_ai/environments/environment/core.py +22 -0
  8. synth_ai/environments/environment/db/__init__.py +1 -0
  9. synth_ai/environments/environment/db/sqlite.py +45 -0
  10. synth_ai/environments/environment/registry.py +24 -0
  11. synth_ai/environments/environment/resources/sqlite.py +46 -0
  12. synth_ai/environments/environment/results.py +1 -0
  13. synth_ai/environments/environment/rewards/__init__.py +1 -0
  14. synth_ai/environments/environment/rewards/core.py +28 -0
  15. synth_ai/environments/environment/shared_engine.py +26 -0
  16. synth_ai/environments/environment/tools/__init__.py +34 -0
  17. synth_ai/environments/examples/__init__.py +1 -0
  18. synth_ai/environments/examples/crafter_classic/__init__.py +8 -0
  19. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_comprehensive_evaluation.py +58 -0
  20. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_evaluation_browser.py +152 -0
  21. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_evaluation_framework.py +1194 -0
  22. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_quick_evaluation.py +51 -0
  23. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_react_agent.py +872 -0
  24. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_trace_evaluation.py +1412 -0
  25. synth_ai/environments/examples/crafter_classic/agent_demos/test_crafter_react_agent.py +1110 -0
  26. synth_ai/environments/examples/crafter_classic/config_logging.py +111 -0
  27. synth_ai/environments/examples/crafter_classic/engine.py +502 -0
  28. synth_ai/environments/examples/crafter_classic/engine_deterministic_patch.py +63 -0
  29. synth_ai/environments/examples/crafter_classic/engine_helpers/action_map.py +5 -0
  30. synth_ai/environments/examples/crafter_classic/engine_helpers/serialization.py +74 -0
  31. synth_ai/environments/examples/crafter_classic/environment.py +255 -0
  32. synth_ai/environments/examples/crafter_classic/taskset.py +228 -0
  33. synth_ai/environments/examples/enron/agent_demos/test_synth_react.py +535 -0
  34. synth_ai/environments/examples/enron/art_helpers/email_search_tools.py +156 -0
  35. synth_ai/environments/examples/enron/art_helpers/local_email_db.py +280 -0
  36. synth_ai/environments/examples/enron/art_helpers/types_enron.py +24 -0
  37. synth_ai/environments/examples/enron/engine.py +291 -0
  38. synth_ai/environments/examples/enron/environment.py +165 -0
  39. synth_ai/environments/examples/enron/taskset.py +112 -0
  40. synth_ai/environments/examples/enron/units/keyword_stats.py +111 -0
  41. synth_ai/environments/examples/enron/units/test_email_index.py +8 -0
  42. synth_ai/environments/examples/minigrid/__init__.py +48 -0
  43. synth_ai/environments/examples/minigrid/agent_demos/minigrid_evaluation_framework.py +1188 -0
  44. synth_ai/environments/examples/minigrid/agent_demos/minigrid_quick_evaluation.py +47 -0
  45. synth_ai/environments/examples/minigrid/agent_demos/minigrid_react_agent.py +562 -0
  46. synth_ai/environments/examples/minigrid/agent_demos/minigrid_trace_evaluation.py +220 -0
  47. synth_ai/environments/examples/minigrid/agent_demos/test_minigrid_react_agent.py +393 -0
  48. synth_ai/environments/examples/minigrid/engine.py +589 -0
  49. synth_ai/environments/examples/minigrid/environment.py +274 -0
  50. synth_ai/environments/examples/minigrid/environment_mapping.py +242 -0
  51. synth_ai/environments/examples/minigrid/puzzle_loader.py +416 -0
  52. synth_ai/environments/examples/minigrid/taskset.py +583 -0
  53. synth_ai/environments/examples/minigrid/units/test_action_behavior.py +226 -0
  54. synth_ai/environments/examples/minigrid/units/test_debug_messages.py +83 -0
  55. synth_ai/environments/examples/minigrid/units/test_exploration.py +120 -0
  56. synth_ai/environments/examples/minigrid/units/test_minigrid_engine.py +214 -0
  57. synth_ai/environments/examples/minigrid/units/test_minigrid_environment.py +238 -0
  58. synth_ai/environments/examples/minigrid/units/test_minigrid_environment_mapping.py +301 -0
  59. synth_ai/environments/examples/minigrid/units/test_minigrid_taskset.py +210 -0
  60. synth_ai/environments/examples/nethack/__init__.py +7 -0
  61. synth_ai/environments/examples/nethack/achievements.py +337 -0
  62. synth_ai/environments/examples/nethack/agent_demos/nethack_evaluation_framework.py +981 -0
  63. synth_ai/environments/examples/nethack/agent_demos/nethack_quick_evaluation.py +74 -0
  64. synth_ai/environments/examples/nethack/agent_demos/nethack_react_agent.py +832 -0
  65. synth_ai/environments/examples/nethack/agent_demos/test_nethack_react_agent.py +1112 -0
  66. synth_ai/environments/examples/nethack/engine.py +738 -0
  67. synth_ai/environments/examples/nethack/environment.py +255 -0
  68. synth_ai/environments/examples/nethack/helpers/__init__.py +42 -0
  69. synth_ai/environments/examples/nethack/helpers/action_mapping.py +301 -0
  70. synth_ai/environments/examples/nethack/helpers/nle_wrapper.py +401 -0
  71. synth_ai/environments/examples/nethack/helpers/observation_utils.py +433 -0
  72. synth_ai/environments/examples/nethack/helpers/recording_wrapper.py +201 -0
  73. synth_ai/environments/examples/nethack/helpers/trajectory_recorder.py +268 -0
  74. synth_ai/environments/examples/nethack/helpers/visualization/replay_viewer.py +308 -0
  75. synth_ai/environments/examples/nethack/helpers/visualization/visualizer.py +430 -0
  76. synth_ai/environments/examples/nethack/taskset.py +323 -0
  77. synth_ai/environments/examples/nethack/units/test_nethack_engine.py +277 -0
  78. synth_ai/environments/examples/nethack/units/test_nethack_environment.py +281 -0
  79. synth_ai/environments/examples/nethack/units/test_nethack_taskset.py +213 -0
  80. synth_ai/environments/examples/nethack/units/test_recording.py +307 -0
  81. synth_ai/environments/examples/red/__init__.py +7 -0
  82. synth_ai/environments/examples/red/agent_demos/__init__.py +1 -0
  83. synth_ai/environments/examples/red/agent_demos/test_synth_react.py +1471 -0
  84. synth_ai/environments/examples/red/config_logging.py +110 -0
  85. synth_ai/environments/examples/red/engine.py +693 -0
  86. synth_ai/environments/examples/red/engine_helpers/__init__.py +1 -0
  87. synth_ai/environments/examples/red/engine_helpers/memory_map.py +28 -0
  88. synth_ai/environments/examples/red/engine_helpers/reward_components.py +275 -0
  89. synth_ai/environments/examples/red/engine_helpers/reward_library/__init__.py +142 -0
  90. synth_ai/environments/examples/red/engine_helpers/reward_library/adaptive_rewards.py +56 -0
  91. synth_ai/environments/examples/red/engine_helpers/reward_library/battle_rewards.py +283 -0
  92. synth_ai/environments/examples/red/engine_helpers/reward_library/composite_rewards.py +149 -0
  93. synth_ai/environments/examples/red/engine_helpers/reward_library/economy_rewards.py +137 -0
  94. synth_ai/environments/examples/red/engine_helpers/reward_library/efficiency_rewards.py +56 -0
  95. synth_ai/environments/examples/red/engine_helpers/reward_library/exploration_rewards.py +330 -0
  96. synth_ai/environments/examples/red/engine_helpers/reward_library/novelty_rewards.py +120 -0
  97. synth_ai/environments/examples/red/engine_helpers/reward_library/pallet_town_rewards.py +558 -0
  98. synth_ai/environments/examples/red/engine_helpers/reward_library/pokemon_rewards.py +312 -0
  99. synth_ai/environments/examples/red/engine_helpers/reward_library/social_rewards.py +147 -0
  100. synth_ai/environments/examples/red/engine_helpers/reward_library/story_rewards.py +246 -0
  101. synth_ai/environments/examples/red/engine_helpers/screen_analysis.py +367 -0
  102. synth_ai/environments/examples/red/engine_helpers/state_extraction.py +139 -0
  103. synth_ai/environments/examples/red/environment.py +235 -0
  104. synth_ai/environments/examples/red/taskset.py +77 -0
  105. synth_ai/environments/examples/red/test_fixes.py +125 -0
  106. synth_ai/environments/examples/red/test_fixes_mock.py +148 -0
  107. synth_ai/environments/examples/red/units/__init__.py +1 -0
  108. synth_ai/environments/examples/red/units/test_basic_functionality.py +97 -0
  109. synth_ai/environments/examples/red/units/test_button_press_requirements.py +217 -0
  110. synth_ai/environments/examples/red/units/test_engine.py +192 -0
  111. synth_ai/environments/examples/red/units/test_environment.py +455 -0
  112. synth_ai/environments/examples/red/units/test_exploration_strategy.py +227 -0
  113. synth_ai/environments/examples/red/units/test_integration.py +217 -0
  114. synth_ai/environments/examples/red/units/test_memory_extraction.py +111 -0
  115. synth_ai/environments/examples/red/units/test_menu_bug_reproduction.py +1100 -0
  116. synth_ai/environments/examples/red/units/test_movement_debug.py +255 -0
  117. synth_ai/environments/examples/red/units/test_pokemon_mcts_debug.py +163 -0
  118. synth_ai/environments/examples/red/units/test_pokemon_mcts_verbose.py +117 -0
  119. synth_ai/environments/examples/red/units/test_red_basic.py +145 -0
  120. synth_ai/environments/examples/red/units/test_red_comprehensive.py +323 -0
  121. synth_ai/environments/examples/red/units/test_retry_movement.py +195 -0
  122. synth_ai/environments/examples/red/units/test_reward_components.py +186 -0
  123. synth_ai/environments/examples/red/units/test_rom_integration.py +260 -0
  124. synth_ai/environments/examples/red/units/test_taskset.py +116 -0
  125. synth_ai/environments/examples/red/units/test_tree.py +448 -0
  126. synth_ai/environments/examples/sokoban/__init__.py +1 -0
  127. synth_ai/environments/examples/sokoban/agent_demos/sokoban_full_eval.py +900 -0
  128. synth_ai/environments/examples/sokoban/agent_demos/test_dspy_react.py +1 -0
  129. synth_ai/environments/examples/sokoban/agent_demos/test_sokoban_react_agent.py +498 -0
  130. synth_ai/environments/examples/sokoban/agent_demos/test_synth_lats.py +1 -0
  131. synth_ai/environments/examples/sokoban/agent_demos/test_synth_react_locally.py +748 -0
  132. synth_ai/environments/examples/sokoban/agent_demos/test_synth_react_service.py +296 -0
  133. synth_ai/environments/examples/sokoban/engine.py +675 -0
  134. synth_ai/environments/examples/sokoban/engine_helpers/__init__.py +1 -0
  135. synth_ai/environments/examples/sokoban/engine_helpers/room_utils.py +656 -0
  136. synth_ai/environments/examples/sokoban/engine_helpers/vendored/__init__.py +17 -0
  137. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/__init__.py +3 -0
  138. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/boxoban_env.py +129 -0
  139. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/render_utils.py +370 -0
  140. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/room_utils.py +331 -0
  141. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env.py +305 -0
  142. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env_fixed_targets.py +66 -0
  143. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env_pull.py +114 -0
  144. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env_two_player.py +122 -0
  145. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env_variations.py +394 -0
  146. synth_ai/environments/examples/sokoban/environment.py +228 -0
  147. synth_ai/environments/examples/sokoban/generate_verified_puzzles.py +438 -0
  148. synth_ai/environments/examples/sokoban/puzzle_loader.py +311 -0
  149. synth_ai/environments/examples/sokoban/taskset.py +425 -0
  150. synth_ai/environments/examples/sokoban/units/astar_common.py +94 -0
  151. synth_ai/environments/examples/sokoban/units/test_building_task_set.py +49 -0
  152. synth_ai/environments/examples/sokoban/units/test_false_positive.py +120 -0
  153. synth_ai/environments/examples/sokoban/units/test_simple_run_through_environment.py +119 -0
  154. synth_ai/environments/examples/sokoban/units/test_sokoban_environment.py +98 -0
  155. synth_ai/environments/examples/sokoban/units/test_tree.py +364 -0
  156. synth_ai/environments/examples/tictactoe/__init__.py +1 -0
  157. synth_ai/environments/examples/tictactoe/agent_demos/test_synth_react.py +266 -0
  158. synth_ai/environments/examples/tictactoe/agent_demos/test_tictactoe_react_agent.py +470 -0
  159. synth_ai/environments/examples/tictactoe/engine.py +368 -0
  160. synth_ai/environments/examples/tictactoe/environment.py +239 -0
  161. synth_ai/environments/examples/tictactoe/taskset.py +214 -0
  162. synth_ai/environments/examples/tictactoe/units/test_tictactoe_engine.py +393 -0
  163. synth_ai/environments/examples/tictactoe/units/test_tictactoe_environment.py +493 -0
  164. synth_ai/environments/examples/tictactoe/units/test_tictactoe_taskset.py +191 -0
  165. synth_ai/environments/examples/verilog/__init__.py +10 -0
  166. synth_ai/environments/examples/verilog/agent_demos/test_synth_react.py +520 -0
  167. synth_ai/environments/examples/verilog/engine.py +328 -0
  168. synth_ai/environments/examples/verilog/environment.py +349 -0
  169. synth_ai/environments/examples/verilog/taskset.py +418 -0
  170. synth_ai/environments/examples/verilog/units/test_verilog_engine.py +466 -0
  171. synth_ai/environments/examples/verilog/units/test_verilog_environment.py +585 -0
  172. synth_ai/environments/examples/verilog/units/test_verilog_integration.py +383 -0
  173. synth_ai/environments/examples/verilog/units/test_verilog_taskset.py +457 -0
  174. synth_ai/environments/reproducibility/core.py +42 -0
  175. synth_ai/environments/reproducibility/tree.py +364 -0
  176. synth_ai/environments/service/app.py +78 -0
  177. synth_ai/environments/service/core_routes.py +775 -0
  178. synth_ai/environments/service/external_registry.py +57 -0
  179. synth_ai/environments/service/registry.py +9 -0
  180. synth_ai/environments/stateful/__init__.py +1 -0
  181. synth_ai/environments/stateful/core.py +28 -0
  182. synth_ai/environments/stateful/engine.py +21 -0
  183. synth_ai/environments/stateful/state.py +7 -0
  184. synth_ai/environments/tasks/api.py +19 -0
  185. synth_ai/environments/tasks/core.py +78 -0
  186. synth_ai/environments/tasks/filters.py +39 -0
  187. synth_ai/environments/tasks/utils.py +89 -0
  188. synth_ai/environments/v0_observability/history.py +3 -0
  189. synth_ai/environments/v0_observability/log.py +2 -0
  190. synth_ai/lm/caching/constants.py +1 -0
  191. synth_ai/{zyk/lms → lm}/caching/ephemeral.py +4 -8
  192. synth_ai/{zyk/lms → lm}/caching/handler.py +15 -15
  193. synth_ai/{zyk/lms → lm}/caching/initialize.py +2 -4
  194. synth_ai/{zyk/lms → lm}/caching/persistent.py +4 -10
  195. synth_ai/{zyk/lms → lm}/config.py +2 -1
  196. synth_ai/{zyk/lms → lm}/constants.py +2 -2
  197. synth_ai/{zyk/lms → lm}/core/all.py +10 -10
  198. synth_ai/{zyk/lms → lm}/core/main.py +57 -33
  199. synth_ai/{zyk/lms → lm}/core/vendor_clients.py +12 -10
  200. synth_ai/lm/cost/monitor.py +1 -0
  201. synth_ai/lm/cost/statefulness.py +1 -0
  202. synth_ai/lm/provider_support/__init__.py +8 -0
  203. synth_ai/lm/provider_support/anthropic.py +945 -0
  204. synth_ai/lm/provider_support/openai.py +1115 -0
  205. synth_ai/lm/provider_support/suppress_logging.py +31 -0
  206. synth_ai/{zyk/lms → lm}/structured_outputs/handler.py +58 -80
  207. synth_ai/{zyk/lms → lm}/structured_outputs/inject.py +6 -20
  208. synth_ai/{zyk/lms → lm}/structured_outputs/rehabilitate.py +6 -12
  209. synth_ai/{zyk/lms → lm}/vendors/core/anthropic_api.py +21 -30
  210. synth_ai/{zyk/lms → lm}/vendors/core/gemini_api.py +35 -32
  211. synth_ai/{zyk/lms → lm}/vendors/core/mistral_api.py +19 -28
  212. synth_ai/{zyk/lms → lm}/vendors/core/openai_api.py +26 -36
  213. synth_ai/{zyk/lms → lm}/vendors/openai_standard.py +29 -33
  214. synth_ai/{zyk/lms → lm}/vendors/retries.py +1 -1
  215. synth_ai/lm/vendors/supported/__init__.py +0 -0
  216. synth_ai/{zyk/lms → lm}/vendors/supported/custom_endpoint.py +131 -118
  217. synth_ai/{zyk/lms → lm}/vendors/supported/deepseek.py +4 -8
  218. synth_ai/{zyk/lms → lm}/vendors/supported/grok.py +6 -8
  219. synth_ai/{zyk/lms → lm}/vendors/supported/groq.py +1 -1
  220. synth_ai/{zyk/lms → lm}/vendors/supported/ollama.py +2 -2
  221. synth_ai/{zyk/lms → lm}/vendors/supported/openrouter.py +18 -16
  222. synth_ai/{zyk/lms → lm}/vendors/supported/together.py +1 -1
  223. synth_ai/tracing/__init__.py +0 -0
  224. synth_ai/tracing/abstractions.py +224 -0
  225. synth_ai/tracing/base_client.py +91 -0
  226. synth_ai/tracing/client_manager.py +131 -0
  227. synth_ai/tracing/config.py +140 -0
  228. synth_ai/tracing/context.py +146 -0
  229. synth_ai/tracing/decorators.py +679 -0
  230. synth_ai/tracing/events/__init__.py +0 -0
  231. synth_ai/tracing/events/manage.py +147 -0
  232. synth_ai/tracing/events/scope.py +86 -0
  233. synth_ai/tracing/events/store.py +227 -0
  234. synth_ai/tracing/immediate_client.py +152 -0
  235. synth_ai/tracing/local.py +18 -0
  236. synth_ai/tracing/log_client_base.py +74 -0
  237. synth_ai/tracing/retry_queue.py +187 -0
  238. synth_ai/tracing/trackers.py +515 -0
  239. synth_ai/tracing/upload.py +504 -0
  240. synth_ai/tracing/utils.py +9 -0
  241. synth_ai/zyk/__init__.py +28 -2
  242. synth_ai-0.2.1.dev0.dist-info/METADATA +349 -0
  243. synth_ai-0.2.1.dev0.dist-info/RECORD +261 -0
  244. {synth_ai-0.2.0.dist-info → synth_ai-0.2.1.dev0.dist-info}/WHEEL +1 -1
  245. synth_ai/zyk/lms/caching/constants.py +0 -1
  246. synth_ai/zyk/lms/cost/monitor.py +0 -1
  247. synth_ai/zyk/lms/cost/statefulness.py +0 -1
  248. synth_ai-0.2.0.dist-info/METADATA +0 -36
  249. synth_ai-0.2.0.dist-info/RECORD +0 -50
  250. /synth_ai/{zyk/lms/__init__.py → environments/reproducibility/helpers.py} +0 -0
  251. /synth_ai/{zyk/lms/caching → lm}/__init__.py +0 -0
  252. /synth_ai/{zyk/lms/core → lm/caching}/__init__.py +0 -0
  253. /synth_ai/{zyk/lms → lm}/caching/dbs.py +0 -0
  254. /synth_ai/{zyk/lms/cost → lm/core}/__init__.py +0 -0
  255. /synth_ai/{zyk/lms → lm}/core/exceptions.py +0 -0
  256. /synth_ai/{zyk/lms/structured_outputs → lm/cost}/__init__.py +0 -0
  257. /synth_ai/{zyk/lms/vendors → lm/structured_outputs}/__init__.py +0 -0
  258. /synth_ai/{zyk/lms → lm}/tools/__init__.py +0 -0
  259. /synth_ai/{zyk/lms → lm}/tools/base.py +0 -0
  260. /synth_ai/{zyk/lms/vendors/core → lm/vendors}/__init__.py +0 -0
  261. /synth_ai/{zyk/lms → lm}/vendors/base.py +0 -0
  262. /synth_ai/{zyk/lms/vendors/local → lm/vendors/core}/__init__.py +0 -0
  263. /synth_ai/{zyk/lms/vendors/supported → lm/vendors/local}/__init__.py +0 -0
  264. /synth_ai/{zyk/lms → lm}/vendors/local/ollama.py +0 -0
  265. {synth_ai-0.2.0.dist-info → synth_ai-0.2.1.dev0.dist-info/licenses}/LICENSE +0 -0
  266. {synth_ai-0.2.0.dist-info → synth_ai-0.2.1.dev0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,945 @@
1
+ """
2
+ Drop-in replacement for anthropic.Client to log requests with Langfuse and track messages using Synth SDK.
3
+ Analogous to the modified OpenAI version.
4
+ """
5
+
6
+ import logging
7
+ import types
8
+ from dataclasses import dataclass
9
+ from typing import Optional
10
+
11
+ try:
12
+ import anthropic
13
+ except ImportError:
14
+ raise ModuleNotFoundError(
15
+ "Please install anthropic to use this feature: 'pip install anthropic'"
16
+ )
17
+
18
+ try:
19
+ from anthropic import AsyncClient, Client
20
+ except ImportError:
21
+ Client = None
22
+ AsyncClient = None
23
+
24
+ from langfuse import Langfuse
25
+ from langfuse.client import StatefulGenerationClient
26
+ from langfuse.decorators import langfuse_context
27
+ from langfuse.utils import _get_timestamp
28
+ from langfuse.utils.langfuse_singleton import LangfuseSingleton
29
+ from wrapt import wrap_function_wrapper
30
+
31
+ from synth_ai.lm.provider_support.suppress_logging import *
32
+ from synth_ai.tracing.trackers import (
33
+ synth_tracker_async,
34
+ synth_tracker_sync,
35
+ )
36
+
37
+ logger = logging.getLogger(__name__)
38
+ logger.setLevel(logging.DEBUG) # Adjust as needed
39
+
40
+ # CREDIT TO LANGFUSE FOR OPEN-SOURCING THE CODE THAT THIS IS BASED ON
41
+ # USING WITH MIT LICENSE PERMISSION
42
+ # https://langfuse.com
43
+
44
+
45
+ @dataclass
46
+ class AnthropicDefinition:
47
+ module: str
48
+ object: str
49
+ method: str
50
+ sync: bool
51
+
52
+
53
+ ANTHROPIC_METHODS = [
54
+ AnthropicDefinition(
55
+ module="anthropic.Client",
56
+ object="completions",
57
+ method="create",
58
+ sync=True,
59
+ ),
60
+ AnthropicDefinition(
61
+ module="anthropic.Client",
62
+ object="completions",
63
+ method="stream",
64
+ sync=True,
65
+ ),
66
+ AnthropicDefinition(
67
+ module="anthropic.AsyncClient",
68
+ object="completions",
69
+ method="create",
70
+ sync=False,
71
+ ),
72
+ AnthropicDefinition(
73
+ module="anthropic.AsyncClient",
74
+ object="completions",
75
+ method="stream",
76
+ sync=False,
77
+ ),
78
+ AnthropicDefinition(
79
+ module="anthropic.Client",
80
+ object="messages",
81
+ method="create",
82
+ sync=True,
83
+ ),
84
+ AnthropicDefinition(
85
+ module="anthropic.AsyncClient",
86
+ object="messages",
87
+ method="create",
88
+ sync=False,
89
+ ),
90
+ ]
91
+
92
+
93
+ class AnthropicArgsExtractor:
94
+ def __init__(
95
+ self,
96
+ name=None,
97
+ metadata=None,
98
+ trace_id=None,
99
+ session_id=None,
100
+ user_id=None,
101
+ tags=None,
102
+ parent_observation_id=None,
103
+ langfuse_prompt=None,
104
+ **kwargs,
105
+ ):
106
+ self.args = {
107
+ "name": name,
108
+ "metadata": metadata,
109
+ "trace_id": trace_id,
110
+ "session_id": session_id,
111
+ "user_id": user_id,
112
+ "tags": tags,
113
+ "parent_observation_id": parent_observation_id,
114
+ "langfuse_prompt": langfuse_prompt,
115
+ }
116
+ self.kwargs = kwargs
117
+
118
+ def get_langfuse_args(self):
119
+ return {**self.args, **self.kwargs}
120
+
121
+ def get_anthropic_args(self):
122
+ return self.kwargs
123
+
124
+
125
+ def _langfuse_wrapper(func):
126
+ def _with_langfuse(anthropic_resource, initialize):
127
+ def wrapper(wrapped, instance, args, kwargs):
128
+ return func(anthropic_resource, initialize, wrapped, args, kwargs)
129
+
130
+ return wrapper
131
+
132
+ return _with_langfuse
133
+
134
+
135
+ def _extract_anthropic_prompt(kwargs: dict) -> str:
136
+ """Return the user prompt if present, else empty."""
137
+ logger.debug(f"Extracting prompt from kwargs: {kwargs}")
138
+
139
+ # Handle Messages API format
140
+ if "messages" in kwargs:
141
+ messages = kwargs["messages"]
142
+ logger.debug(f"Found messages format: {messages}")
143
+ # Extract the last user message
144
+ user_messages = [m["content"] for m in messages if m["role"] == "user"]
145
+ return user_messages[-1] if user_messages else ""
146
+
147
+ # Handle Completions API format
148
+ return kwargs.get("prompt", "")
149
+
150
+
151
+ def _extract_anthropic_completion(response):
152
+ """Extract final completion, model, usage from the anthropic response."""
153
+ if not response:
154
+ return None, "<NoneType response returned from Anthropic>", None
155
+
156
+ model = getattr(response, "model", None)
157
+ raw_usage = getattr(response, "usage", None)
158
+
159
+ # Handle content which might be a TextBlock or list of TextBlocks
160
+ content = getattr(response, "content", None) or getattr(response, "completion", None)
161
+ if isinstance(content, list):
162
+ # Handle list of TextBlocks
163
+ completion = " ".join(
164
+ block.text if hasattr(block, "text") else str(block) for block in content
165
+ )
166
+ elif hasattr(content, "text"):
167
+ # Handle single TextBlock
168
+ completion = content.text
169
+ else:
170
+ completion = str(content) if content is not None else ""
171
+
172
+ # Convert Anthropic usage format to Langfuse format
173
+ if raw_usage:
174
+ usage = {
175
+ "promptTokens": getattr(raw_usage, "input_tokens", 0),
176
+ "completionTokens": getattr(raw_usage, "output_tokens", 0),
177
+ "totalTokens": getattr(raw_usage, "total_tokens", 0),
178
+ }
179
+ else:
180
+ usage = {"promptTokens": 0, "completionTokens": 0, "totalTokens": 0}
181
+
182
+ return model, completion, usage
183
+
184
+
185
+ def _extract_streamed_anthropic_response(items):
186
+ """Extract final completion, model, usage from streamed anthropic response."""
187
+ if not items:
188
+ return None, "<Empty response from Anthropic>", None
189
+
190
+ last_item = items[-1]
191
+ model = getattr(last_item, "model", None)
192
+ raw_usage = getattr(last_item, "usage", None)
193
+
194
+ # Combine all content pieces, handling TextBlocks
195
+ completion_parts = []
196
+ for item in items:
197
+ content = getattr(item, "content", None) or getattr(item, "completion", None)
198
+ if isinstance(content, list):
199
+ # Handle list of TextBlocks
200
+ completion_parts.extend(
201
+ block.text if hasattr(block, "text") else str(block) for block in content
202
+ )
203
+ elif hasattr(content, "text"):
204
+ # Handle single TextBlock
205
+ completion_parts.append(content.text)
206
+ elif content:
207
+ completion_parts.append(str(content))
208
+
209
+ completion = " ".join(completion_parts)
210
+
211
+ # Convert usage format
212
+ if raw_usage:
213
+ usage = {
214
+ "promptTokens": getattr(raw_usage, "input_tokens", 0),
215
+ "completionTokens": getattr(raw_usage, "output_tokens", 0),
216
+ "totalTokens": getattr(raw_usage, "total_tokens", 0),
217
+ }
218
+ else:
219
+ usage = {"promptTokens": 0, "completionTokens": 0, "totalTokens": 0}
220
+
221
+ return model, completion, usage
222
+
223
+
224
+ def _get_langfuse_data_from_kwargs(anthropic_resource, langfuse: Langfuse, start_time, kwargs):
225
+ name = kwargs.get("name", "Anthropic-generation")
226
+ if name is not None and not isinstance(name, str):
227
+ raise TypeError("name must be a string")
228
+
229
+ decorator_context_observation_id = langfuse_context.get_current_observation_id()
230
+ decorator_context_trace_id = langfuse_context.get_current_trace_id()
231
+
232
+ trace_id = kwargs.get("trace_id", None) or decorator_context_trace_id
233
+ if trace_id is not None and not isinstance(trace_id, str):
234
+ raise TypeError("trace_id must be a string")
235
+
236
+ session_id = kwargs.get("session_id", None)
237
+ if session_id is not None and not isinstance(session_id, str):
238
+ raise TypeError("session_id must be a string")
239
+
240
+ user_id = kwargs.get("user_id", None)
241
+ if user_id is not None and not isinstance(user_id, str):
242
+ raise TypeError("user_id must be a string")
243
+
244
+ tags = kwargs.get("tags", None)
245
+ if tags is not None and (
246
+ not isinstance(tags, list) or not all(isinstance(tag, str) for tag in tags)
247
+ ):
248
+ raise TypeError("tags must be a list of strings")
249
+
250
+ if decorator_context_trace_id:
251
+ langfuse_context.update_current_trace(session_id=session_id, user_id=user_id, tags=tags)
252
+
253
+ parent_observation_id = kwargs.get("parent_observation_id", None) or (
254
+ decorator_context_observation_id
255
+ if decorator_context_observation_id != decorator_context_trace_id
256
+ else None
257
+ )
258
+ if parent_observation_id is not None and not isinstance(parent_observation_id, str):
259
+ raise TypeError("parent_observation_id must be a string")
260
+ if parent_observation_id is not None and trace_id is None:
261
+ raise ValueError("parent_observation_id requires trace_id to be set")
262
+
263
+ metadata = kwargs.get("metadata", {})
264
+ if metadata is not None and not isinstance(metadata, dict):
265
+ raise TypeError("metadata must be a dictionary")
266
+
267
+ # Collect user prompt and model from arguments
268
+ prompt = _extract_anthropic_prompt(kwargs)
269
+ model = kwargs.get("model", None)
270
+ # If user supplied inputs for a model in some nested structure, consider hooking in here.
271
+
272
+ # Basic hyperparams
273
+ model_params = {
274
+ "temperature": kwargs.get("temperature", 1.0),
275
+ "max_tokens": kwargs.get("max_tokens_to_sample", None),
276
+ "top_p": kwargs.get("top_p", None),
277
+ }
278
+
279
+ is_nested_trace = False
280
+ if trace_id:
281
+ is_nested_trace = True
282
+ langfuse.trace(id=trace_id, session_id=session_id, user_id=user_id, tags=tags)
283
+ else:
284
+ trace_instance = langfuse.trace(
285
+ session_id=session_id,
286
+ user_id=user_id,
287
+ tags=tags,
288
+ name=name,
289
+ input=prompt,
290
+ metadata=metadata,
291
+ )
292
+ trace_id = trace_instance.id
293
+
294
+ langfuse_prompt = kwargs.get("langfuse_prompt", None)
295
+
296
+ return (
297
+ {
298
+ "name": name,
299
+ "metadata": metadata,
300
+ "trace_id": trace_id,
301
+ "parent_observation_id": parent_observation_id,
302
+ "user_id": user_id,
303
+ "start_time": start_time,
304
+ "input": prompt,
305
+ "model_params": model_params,
306
+ "prompt": langfuse_prompt,
307
+ "model": model,
308
+ },
309
+ is_nested_trace,
310
+ )
311
+
312
+
313
+ def _create_langfuse_update(
314
+ completion,
315
+ generation: StatefulGenerationClient,
316
+ completion_start_time,
317
+ model=None,
318
+ usage=None,
319
+ model_params=None,
320
+ ):
321
+ update = {
322
+ "end_time": _get_timestamp(),
323
+ "output": completion,
324
+ "completion_start_time": completion_start_time,
325
+ }
326
+ if model:
327
+ if not model_params:
328
+ model_params = {}
329
+ model_params["model_name"] = model
330
+ if model_params is not None:
331
+ update["model_params"] = model_params
332
+ if usage is not None:
333
+ update["usage"] = usage
334
+ generation.update(**update)
335
+
336
+
337
+ @_langfuse_wrapper
338
+ def _wrap(anthropic_resource: AnthropicDefinition, initialize, wrapped, args, kwargs):
339
+ # print("\n=== WRAP START ===")
340
+ # print(f"WRAP: Args: {args}")
341
+ # print(f"WRAP: Kwargs: {kwargs}")
342
+
343
+ new_langfuse = initialize()
344
+ start_time = _get_timestamp()
345
+ arg_extractor = AnthropicArgsExtractor(*args, **kwargs)
346
+ generation_data, is_nested_trace = _get_langfuse_data_from_kwargs(
347
+ anthropic_resource, new_langfuse, start_time, arg_extractor.get_langfuse_args()
348
+ )
349
+ generation = new_langfuse.generation(**generation_data)
350
+
351
+ try:
352
+ anthropic_response = wrapped(*args, **arg_extractor.get_anthropic_args())
353
+
354
+ # If it's a streaming call, returns a generator
355
+ if isinstance(anthropic_response, types.GeneratorType):
356
+ return LangfuseAnthropicResponseGeneratorSync(
357
+ response=anthropic_response,
358
+ generation=generation,
359
+ langfuse=new_langfuse,
360
+ is_nested_trace=is_nested_trace,
361
+ kwargs=arg_extractor.get_anthropic_args(),
362
+ )
363
+ else:
364
+ model, completion, usage = _extract_anthropic_completion(anthropic_response)
365
+ # Synth tracking
366
+ if "messages" in arg_extractor.get_anthropic_args():
367
+ # print("\nWRAP: Messages API path")
368
+ system_content = arg_extractor.get_anthropic_args().get("system")
369
+ original_messages = arg_extractor.get_anthropic_args()["messages"]
370
+ # print(f"WRAP: Original messages: {original_messages}")
371
+ # print(f"WRAP: System content: {system_content}")
372
+
373
+ if system_content:
374
+ messages = [{"role": "system", "content": system_content}] + original_messages
375
+ else:
376
+ messages = original_messages
377
+
378
+ # print(f"WRAP: Final messages to track: {messages}")
379
+ # print("WRAP: About to call track_lm")
380
+ synth_tracker_sync.track_lm(
381
+ messages=messages,
382
+ model_name=model,
383
+ model_params=generation_data.get("model_params", {}),
384
+ finetune=False,
385
+ )
386
+ # print("WRAP: Finished track_lm call")
387
+
388
+ # Track assistant output
389
+ assistant_msg = [{"role": "assistant", "content": completion}]
390
+ # rint("About to track LM output")
391
+ # print("Assistant message: %s", assistant_msg)
392
+
393
+ synth_tracker_sync.track_lm_output(
394
+ messages=assistant_msg,
395
+ model_name=model,
396
+ finetune=False,
397
+ )
398
+ # print("Finished tracking LM output")
399
+
400
+ elif "prompt" in arg_extractor.get_anthropic_args():
401
+ # print("\nWRAP: Completions API path")
402
+ user_prompt = arg_extractor.get_anthropic_args().get("prompt", "")
403
+ # print(f"WRAP: User prompt: {user_prompt}")
404
+ messages = [{"role": "user", "content": user_prompt}]
405
+ # print(f"WRAP: Messages created: {messages}")
406
+ assistant_msg = [{"role": "assistant", "content": completion}]
407
+
408
+ # print("About to track LM call with model: %s", model)
409
+ # print("User prompt: %s", user_prompt)
410
+ # print("Messages to track: %s", messages)
411
+ # print("Model params: %s", generation_data.get("model_params", {}))
412
+
413
+ synth_tracker_sync.track_lm(
414
+ messages=messages,
415
+ model_name=model,
416
+ model_params=generation_data.get("model_params", {}),
417
+ finetune=False,
418
+ )
419
+
420
+ # print("About to track LM output")
421
+ # print("Assistant message: %s", assistant_msg)
422
+
423
+ synth_tracker_sync.track_lm_output(
424
+ messages=assistant_msg,
425
+ model_name=model,
426
+ finetune=False,
427
+ )
428
+ # print("Finished tracking LM output")
429
+
430
+ # Complete the generation update
431
+ _create_langfuse_update(
432
+ completion,
433
+ generation,
434
+ start_time,
435
+ model=model,
436
+ usage=usage,
437
+ model_params=generation_data.get("model_params", {}),
438
+ )
439
+ if not is_nested_trace:
440
+ new_langfuse.trace(id=generation.trace_id, output=completion)
441
+
442
+ return anthropic_response
443
+ except Exception as ex:
444
+ model_params = generation_data.get("model_params", {})
445
+ generation.update(
446
+ end_time=_get_timestamp(),
447
+ status_message=str(ex),
448
+ level="ERROR",
449
+ model_params=model_params,
450
+ usage={"promptTokens": 0, "completionTokens": 0, "totalTokens": 0},
451
+ )
452
+ raise ex
453
+
454
+
455
+ @_langfuse_wrapper
456
+ async def _wrap_async(anthropic_resource: AnthropicDefinition, initialize, wrapped, args, kwargs):
457
+ # print("\n=== WRAP_ASYNC START ===")
458
+ # print(f"WRAP_ASYNC: Args: {args}")
459
+ # print(f"WRAP_ASYNC: Kwargs: {kwargs}")
460
+
461
+ new_langfuse = initialize()
462
+ start_time = _get_timestamp()
463
+ arg_extractor = AnthropicArgsExtractor(*args, **kwargs)
464
+
465
+ # Initialize tracker if needed
466
+ if not hasattr(synth_tracker_async, "_local") or not getattr(
467
+ synth_tracker_async._local, "initialized", False
468
+ ):
469
+ synth_tracker_async.initialize()
470
+ # print("WRAP_ASYNC: Initialized async tracker")
471
+
472
+ generation_data, is_nested_trace = _get_langfuse_data_from_kwargs(
473
+ anthropic_resource, new_langfuse, start_time, arg_extractor.get_langfuse_args()
474
+ )
475
+ generation = new_langfuse.generation(**generation_data)
476
+
477
+ try:
478
+ logger.debug("About to call wrapped function")
479
+ response = await wrapped(*args, **kwargs)
480
+ logger.debug(f"Got response: {response}")
481
+
482
+ model, completion, usage = _extract_anthropic_completion(response)
483
+ logger.debug(f"Extracted completion - Model: {model}, Usage: {usage}")
484
+
485
+ # Synth tracking
486
+ if "messages" in arg_extractor.get_anthropic_args():
487
+ # logger.debug("WRAP_ASYNC: Messages API path detected")
488
+ system_content = arg_extractor.get_anthropic_args().get("system")
489
+ original_messages = arg_extractor.get_anthropic_args()["messages"]
490
+ # logger.debug("WRAP_ASYNC: Original messages: %s", original_messages)
491
+ # logger.debug("WRAP_ASYNC: System content: %s", system_content)
492
+
493
+ if system_content:
494
+ messages = [{"role": "system", "content": system_content}] + original_messages
495
+ else:
496
+ messages = original_messages
497
+
498
+ # logger.debug("WRAP_ASYNC: About to track messages: %s", messages)
499
+ synth_tracker_async.track_lm(
500
+ messages=messages,
501
+ model_name=model,
502
+ model_params=generation_data.get("model_params", {}),
503
+ finetune=False,
504
+ )
505
+
506
+ # Track assistant output
507
+ assistant_msg = [{"role": "assistant", "content": completion}]
508
+ logger.debug("Tracking assistant message: %s", assistant_msg)
509
+ synth_tracker_async.track_lm_output(
510
+ messages=assistant_msg,
511
+ model_name=model,
512
+ finetune=False,
513
+ )
514
+ elif "prompt" in arg_extractor.get_anthropic_args():
515
+ # Handle Completions API format
516
+ user_prompt = arg_extractor.get_anthropic_args().get("prompt", "")
517
+ messages = [{"role": "user", "content": user_prompt}]
518
+ assistant_msg = [{"role": "assistant", "content": completion}]
519
+
520
+ logger.debug("About to track async LM call with model: %s", model)
521
+ logger.debug("User prompt: %s", user_prompt)
522
+ logger.debug("Messages to track: %s", messages)
523
+ logger.debug("Model params: %s", generation_data.get("model_params", {}))
524
+
525
+ # Track input
526
+ # SynthTracker.track_lm(
527
+ # messages=messages,
528
+ # model_name=model,
529
+ # model_params=generation_data.get("model_params", {}),
530
+ # finetune=False,
531
+ # )
532
+
533
+ logger.debug("About to track async LM output")
534
+ logger.debug("Assistant message: %s", assistant_msg)
535
+
536
+ # Track output
537
+ # SynthTracker.track_lm_output(
538
+ # messages=assistant_msg,
539
+ # model_name=model,
540
+ # finetune=False,
541
+ # )
542
+
543
+ # Complete the generation update
544
+ _create_langfuse_update(
545
+ completion,
546
+ generation,
547
+ start_time,
548
+ model=model,
549
+ usage=usage,
550
+ model_params=generation_data.get("model_params", {}),
551
+ )
552
+ if not is_nested_trace:
553
+ new_langfuse.trace(id=generation.trace_id, output=completion)
554
+
555
+ return response
556
+
557
+ except Exception as ex:
558
+ model_params = generation_data.get("model_params", {})
559
+ generation.update(
560
+ end_time=_get_timestamp(),
561
+ status_message=str(ex),
562
+ level="ERROR",
563
+ model_params=model_params,
564
+ usage={"promptTokens": 0, "completionTokens": 0, "totalTokens": 0},
565
+ )
566
+ raise ex
567
+
568
+
569
+ class LangfuseAnthropicResponseGeneratorSync:
570
+ def __init__(self, *, response, generation, langfuse, is_nested_trace, kwargs):
571
+ self.response = response
572
+ self.generation = generation
573
+ self.langfuse = langfuse
574
+ self.is_nested_trace = is_nested_trace
575
+ self.kwargs = kwargs
576
+ self.items = []
577
+ self.completion_start_time = None
578
+
579
+ def __iter__(self):
580
+ try:
581
+ for chunk in self.response:
582
+ self.items.append(chunk)
583
+ if self.completion_start_time is None:
584
+ self.completion_start_time = _get_timestamp()
585
+ yield chunk
586
+ finally:
587
+ self._finalize()
588
+
589
+ def __next__(self):
590
+ try:
591
+ chunk = next(self.response)
592
+ self.items.append(chunk)
593
+ if self.completion_start_time is None:
594
+ self.completion_start_time = _get_timestamp()
595
+ return chunk
596
+ except StopIteration:
597
+ self._finalize()
598
+ raise
599
+
600
+ def _finalize(self):
601
+ print("\n=== FINALIZE START ===")
602
+ print(f"FINALIZE: Self kwargs: {self.kwargs}")
603
+ model, completion, usage = _extract_streamed_anthropic_response(self.items)
604
+
605
+ if "messages" in self.kwargs:
606
+ print("\nFINALIZE: Messages API path")
607
+ system_content = self.kwargs.get("system")
608
+ original_messages = self.kwargs["messages"]
609
+ print(f"FINALIZE: Original messages: {original_messages}")
610
+ print(f"FINALIZE: System content: {system_content}")
611
+
612
+ if system_content:
613
+ messages = [{"role": "system", "content": system_content}] + original_messages
614
+ else:
615
+ messages = original_messages
616
+
617
+ print(f"FINALIZE: Final messages to track: {messages}")
618
+ print("FINALIZE: About to call track_lm")
619
+ # synth_tracker_sync.track_lm(
620
+ # messages=messages,
621
+ # model_name=model,
622
+ # model_params=self.generation.model_params or {},
623
+ # finetune=False,
624
+ # )
625
+ print("FINALIZE: Finished track_lm call")
626
+
627
+ # Track assistant output
628
+ assistant_msg = [{"role": "assistant", "content": completion}]
629
+ print("Tracking assistant message: %s", assistant_msg)
630
+ # synth_tracker_sync.track_lm_output(
631
+ # messages=assistant_msg,
632
+ # model_name=model,
633
+ # finetune=False,
634
+ # )
635
+ elif "prompt" in self.kwargs:
636
+ print("\nFINALIZE: Completions API path")
637
+ user_prompt = self.kwargs.get("prompt", "")
638
+ print(f"FINALIZE: User prompt: {user_prompt}")
639
+ messages = [{"role": "user", "content": user_prompt}]
640
+ print(f"FINALIZE: Messages created: {messages}")
641
+ assistant_msg = [{"role": "assistant", "content": completion}]
642
+
643
+ # synth_tracker_sync.track_lm(
644
+ # messages=messages,
645
+ # model_name=model,
646
+ # model_params=self.generation.model_params or {},
647
+ # finetune=False,
648
+ # )
649
+
650
+ # synth_tracker_sync.track_lm_output(
651
+ # messages=assistant_msg,
652
+ # model_name=model,
653
+ # finetune=False,
654
+ # )
655
+
656
+ if not self.is_nested_trace:
657
+ self.langfuse.trace(id=self.generation.trace_id, output=completion)
658
+ _create_langfuse_update(
659
+ completion,
660
+ self.generation,
661
+ self.completion_start_time,
662
+ model=model,
663
+ usage=usage,
664
+ model_params=self.generation.model_params,
665
+ )
666
+
667
+
668
+ class LangfuseAnthropicResponseGeneratorAsync:
669
+ def __init__(self, *, response, generation, langfuse, is_nested_trace, kwargs):
670
+ self.response = response
671
+ self.generation = generation
672
+ self.langfuse = langfuse
673
+ self.is_nested_trace = is_nested_trace
674
+ self.kwargs = kwargs
675
+ self.items = []
676
+ self.completion_start_time = None
677
+
678
+ async def __aiter__(self):
679
+ try:
680
+ async for chunk in self.response:
681
+ self.items.append(chunk)
682
+ if self.completion_start_time is None:
683
+ self.completion_start_time = _get_timestamp()
684
+ yield chunk
685
+ finally:
686
+ await self._finalize()
687
+
688
+ async def __anext__(self):
689
+ try:
690
+ chunk = await self.response.__anext__()
691
+ self.items.append(chunk)
692
+ if self.completion_start_time is None:
693
+ self.completion_start_time = _get_timestamp()
694
+ return chunk
695
+ except StopAsyncIteration:
696
+ await self._finalize()
697
+ raise
698
+
699
+ async def _finalize(self):
700
+ print("\n=== FINALIZE START ===")
701
+ if not synth_tracker_async:
702
+ print("ERROR: synth_tracker_async is not initialized!")
703
+ raise RuntimeError("synth_tracker_async must be initialized before use")
704
+
705
+ print(f"FINALIZE: Self kwargs: {self.kwargs}")
706
+ model, completion, usage = _extract_streamed_anthropic_response(self.items)
707
+
708
+ if "messages" in self.kwargs:
709
+ print("\nFINALIZE: Messages API path")
710
+ system_content = self.kwargs.get("system")
711
+ original_messages = self.kwargs["messages"]
712
+ print(f"FINALIZE: Original messages: {original_messages}")
713
+ print(f"FINALIZE: System content: {system_content}")
714
+
715
+ if system_content:
716
+ messages = [{"role": "system", "content": system_content}] + original_messages
717
+ else:
718
+ messages = original_messages
719
+
720
+ print(f"FINALIZE: Final messages to track: {messages}")
721
+ print("FINALIZE: About to call track_lm")
722
+ synth_tracker_async.track_lm(
723
+ messages=messages,
724
+ model_name=model,
725
+ model_params=self.generation.model_params or {},
726
+ finetune=False,
727
+ )
728
+ print("FINALIZE: Finished track_lm call")
729
+
730
+ # Track assistant output
731
+ assistant_msg = [{"role": "assistant", "content": completion}]
732
+ print("Tracking assistant message: %s", assistant_msg)
733
+ synth_tracker_async.track_lm_output(
734
+ messages=assistant_msg,
735
+ model_name=model,
736
+ finetune=False,
737
+ )
738
+ elif "prompt" in self.kwargs:
739
+ print("\nFINALIZE: Completions API path")
740
+ user_prompt = self.kwargs.get("prompt", "")
741
+ print(f"FINALIZE: User prompt: {user_prompt}")
742
+ messages = [{"role": "user", "content": user_prompt}]
743
+ print(f"FINALIZE: Messages created: {messages}")
744
+ assistant_msg = [{"role": "assistant", "content": completion}]
745
+
746
+ synth_tracker_async.track_lm(
747
+ messages=messages,
748
+ model_name=model,
749
+ model_params=self.generation.model_params or {},
750
+ finetune=False,
751
+ )
752
+
753
+ synth_tracker_async.track_lm_output(
754
+ messages=assistant_msg,
755
+ model_name=model,
756
+ finetune=False,
757
+ )
758
+
759
+ if not self.is_nested_trace:
760
+ self.langfuse.trace(id=self.generation.trace_id, output=completion)
761
+ _create_langfuse_update(
762
+ completion,
763
+ self.generation,
764
+ self.completion_start_time,
765
+ model=model,
766
+ usage=usage,
767
+ model_params=self.generation.model_params,
768
+ )
769
+
770
+ async def close(self):
771
+ await self.response.aclose()
772
+
773
+
774
+ class AnthropicLangfuse:
775
+ _langfuse: Optional[Langfuse] = None
776
+
777
+ def initialize(self):
778
+ self._langfuse = LangfuseSingleton().get(
779
+ public_key=getattr(anthropic, "langfuse_public_key", None),
780
+ secret_key=getattr(anthropic, "langfuse_secret_key", None),
781
+ host=getattr(anthropic, "langfuse_host", None),
782
+ debug=getattr(anthropic, "langfuse_debug", None),
783
+ enabled=getattr(anthropic, "langfuse_enabled", True),
784
+ sdk_integration="anthropic",
785
+ sample_rate=getattr(anthropic, "langfuse_sample_rate", None),
786
+ )
787
+ return self._langfuse
788
+
789
+ def flush(self):
790
+ if self._langfuse is not None:
791
+ self._langfuse.flush()
792
+
793
+ def langfuse_auth_check(self):
794
+ if self._langfuse is None:
795
+ self.initialize()
796
+ return self._langfuse.auth_check()
797
+
798
+ def register_tracing(self):
799
+ # Patch anthropic.Client to wrap both completions and messages methods
800
+ original_client_init = anthropic.Client.__init__
801
+
802
+ def new_client_init(instance, *args, **kwargs):
803
+ logger.debug("Initializing new Anthropic Client with tracing")
804
+ original_client_init(instance, *args, **kwargs)
805
+
806
+ # Wrap completions methods
807
+ comp_obj = getattr(instance, "completions", None)
808
+ if comp_obj is not None:
809
+ logger.debug("Found completions object, wrapping methods")
810
+ # Wrap 'create' method if available.
811
+ if hasattr(comp_obj, "create"):
812
+ wrap_function_wrapper(
813
+ comp_obj,
814
+ "create",
815
+ _wrap(
816
+ next(
817
+ r
818
+ for r in ANTHROPIC_METHODS
819
+ if r.method == "create" and r.module == "anthropic.Client"
820
+ ),
821
+ self.initialize,
822
+ ),
823
+ )
824
+ # Wrap 'stream' method only if it exists.
825
+ if hasattr(comp_obj, "stream"):
826
+ wrap_function_wrapper(
827
+ comp_obj,
828
+ "stream",
829
+ _wrap(
830
+ next(
831
+ r
832
+ for r in ANTHROPIC_METHODS
833
+ if r.method == "stream" and r.module == "anthropic.Client"
834
+ ),
835
+ self.initialize,
836
+ ),
837
+ )
838
+
839
+ # Wrap messages methods
840
+ msg_obj = getattr(instance, "messages", None)
841
+ if msg_obj is not None:
842
+ logger.debug("Found messages object, wrapping methods")
843
+ if hasattr(msg_obj, "create"):
844
+ wrap_function_wrapper(
845
+ msg_obj,
846
+ "create",
847
+ _wrap(
848
+ next(
849
+ r
850
+ for r in ANTHROPIC_METHODS
851
+ if r.method == "create"
852
+ and r.module == "anthropic.Client"
853
+ and r.object == "messages"
854
+ ),
855
+ self.initialize,
856
+ ),
857
+ )
858
+
859
+ anthropic.Client.__init__ = new_client_init
860
+
861
+ # Patch anthropic.AsyncClient similarly.
862
+ original_async_init = anthropic.AsyncClient.__init__
863
+
864
+ def new_async_init(instance, *args, **kwargs):
865
+ logger.debug("Initializing new Async Anthropic Client with tracing")
866
+ original_async_init(instance, *args, **kwargs)
867
+
868
+ # Wrap completions methods
869
+ comp_obj = getattr(instance, "completions", None)
870
+ if comp_obj is not None:
871
+ logger.debug("Found async completions object, wrapping methods")
872
+ if hasattr(comp_obj, "create"):
873
+ wrap_function_wrapper(
874
+ comp_obj,
875
+ "create",
876
+ _wrap_async(
877
+ next(
878
+ r
879
+ for r in ANTHROPIC_METHODS
880
+ if r.method == "create" and r.module == "anthropic.AsyncClient"
881
+ ),
882
+ self.initialize,
883
+ ),
884
+ )
885
+ if hasattr(comp_obj, "stream"):
886
+ wrap_function_wrapper(
887
+ comp_obj,
888
+ "stream",
889
+ _wrap_async(
890
+ next(
891
+ r
892
+ for r in ANTHROPIC_METHODS
893
+ if r.method == "stream" and r.module == "anthropic.AsyncClient"
894
+ ),
895
+ self.initialize,
896
+ ),
897
+ )
898
+
899
+ # Wrap messages methods
900
+ msg_obj = getattr(instance, "messages", None)
901
+ if msg_obj is not None:
902
+ logger.debug("Found async messages object, wrapping methods")
903
+ if hasattr(msg_obj, "create"):
904
+ logger.debug("Wrapping async messages.create method")
905
+ wrap_function_wrapper(
906
+ msg_obj,
907
+ "create",
908
+ _wrap_async(
909
+ next(
910
+ r
911
+ for r in ANTHROPIC_METHODS
912
+ if r.method == "create"
913
+ and r.module == "anthropic.AsyncClient"
914
+ and r.object == "messages"
915
+ ),
916
+ self.initialize,
917
+ ),
918
+ )
919
+
920
+ anthropic.AsyncClient.__init__ = new_async_init
921
+
922
+ setattr(anthropic, "langfuse_public_key", None)
923
+ setattr(anthropic, "langfuse_secret_key", None)
924
+ setattr(anthropic, "langfuse_host", None)
925
+ setattr(anthropic, "langfuse_debug", None)
926
+ setattr(anthropic, "langfuse_enabled", True)
927
+ setattr(anthropic, "langfuse_sample_rate", None)
928
+ setattr(anthropic, "langfuse_auth_check", self.langfuse_auth_check)
929
+ setattr(anthropic, "flush_langfuse", self.flush)
930
+
931
+
932
+ modifier = AnthropicLangfuse()
933
+ modifier.register_tracing()
934
+
935
+
936
+ # DEPRECATED: Use `anthropic.langfuse_auth_check()` instead
937
+ def auth_check():
938
+ if modifier._langfuse is None:
939
+ modifier.initialize()
940
+ return modifier._langfuse.auth_check()
941
+
942
+
943
+ # Rename Client to Anthropic and AsyncClient to AsyncAnthropic for better clarity
944
+ Anthropic = Client
945
+ AsyncAnthropic = AsyncClient