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.
- synth_ai/__init__.py +28 -2
- synth_ai/core/system.py +4 -0
- synth_ai/environments/__init__.py +35 -0
- synth_ai/environments/environment/__init__.py +1 -0
- synth_ai/environments/environment/artifacts/__init__.py +1 -0
- synth_ai/environments/environment/artifacts/base.py +50 -0
- synth_ai/environments/environment/core.py +22 -0
- synth_ai/environments/environment/db/__init__.py +1 -0
- synth_ai/environments/environment/db/sqlite.py +45 -0
- synth_ai/environments/environment/registry.py +24 -0
- synth_ai/environments/environment/resources/sqlite.py +46 -0
- synth_ai/environments/environment/results.py +1 -0
- synth_ai/environments/environment/rewards/__init__.py +1 -0
- synth_ai/environments/environment/rewards/core.py +28 -0
- synth_ai/environments/environment/shared_engine.py +26 -0
- synth_ai/environments/environment/tools/__init__.py +34 -0
- synth_ai/environments/examples/__init__.py +1 -0
- synth_ai/environments/examples/crafter_classic/__init__.py +8 -0
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_comprehensive_evaluation.py +58 -0
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_evaluation_browser.py +152 -0
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_evaluation_framework.py +1194 -0
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_quick_evaluation.py +51 -0
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_react_agent.py +872 -0
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_trace_evaluation.py +1412 -0
- synth_ai/environments/examples/crafter_classic/agent_demos/test_crafter_react_agent.py +1110 -0
- synth_ai/environments/examples/crafter_classic/config_logging.py +111 -0
- synth_ai/environments/examples/crafter_classic/engine.py +502 -0
- synth_ai/environments/examples/crafter_classic/engine_deterministic_patch.py +63 -0
- synth_ai/environments/examples/crafter_classic/engine_helpers/action_map.py +5 -0
- synth_ai/environments/examples/crafter_classic/engine_helpers/serialization.py +74 -0
- synth_ai/environments/examples/crafter_classic/environment.py +255 -0
- synth_ai/environments/examples/crafter_classic/taskset.py +228 -0
- synth_ai/environments/examples/enron/agent_demos/test_synth_react.py +535 -0
- synth_ai/environments/examples/enron/art_helpers/email_search_tools.py +156 -0
- synth_ai/environments/examples/enron/art_helpers/local_email_db.py +280 -0
- synth_ai/environments/examples/enron/art_helpers/types_enron.py +24 -0
- synth_ai/environments/examples/enron/engine.py +291 -0
- synth_ai/environments/examples/enron/environment.py +165 -0
- synth_ai/environments/examples/enron/taskset.py +112 -0
- synth_ai/environments/examples/enron/units/keyword_stats.py +111 -0
- synth_ai/environments/examples/enron/units/test_email_index.py +8 -0
- synth_ai/environments/examples/minigrid/__init__.py +48 -0
- synth_ai/environments/examples/minigrid/agent_demos/minigrid_evaluation_framework.py +1188 -0
- synth_ai/environments/examples/minigrid/agent_demos/minigrid_quick_evaluation.py +47 -0
- synth_ai/environments/examples/minigrid/agent_demos/minigrid_react_agent.py +562 -0
- synth_ai/environments/examples/minigrid/agent_demos/minigrid_trace_evaluation.py +220 -0
- synth_ai/environments/examples/minigrid/agent_demos/test_minigrid_react_agent.py +393 -0
- synth_ai/environments/examples/minigrid/engine.py +589 -0
- synth_ai/environments/examples/minigrid/environment.py +274 -0
- synth_ai/environments/examples/minigrid/environment_mapping.py +242 -0
- synth_ai/environments/examples/minigrid/puzzle_loader.py +416 -0
- synth_ai/environments/examples/minigrid/taskset.py +583 -0
- synth_ai/environments/examples/minigrid/units/test_action_behavior.py +226 -0
- synth_ai/environments/examples/minigrid/units/test_debug_messages.py +83 -0
- synth_ai/environments/examples/minigrid/units/test_exploration.py +120 -0
- synth_ai/environments/examples/minigrid/units/test_minigrid_engine.py +214 -0
- synth_ai/environments/examples/minigrid/units/test_minigrid_environment.py +238 -0
- synth_ai/environments/examples/minigrid/units/test_minigrid_environment_mapping.py +301 -0
- synth_ai/environments/examples/minigrid/units/test_minigrid_taskset.py +210 -0
- synth_ai/environments/examples/nethack/__init__.py +7 -0
- synth_ai/environments/examples/nethack/achievements.py +337 -0
- synth_ai/environments/examples/nethack/agent_demos/nethack_evaluation_framework.py +981 -0
- synth_ai/environments/examples/nethack/agent_demos/nethack_quick_evaluation.py +74 -0
- synth_ai/environments/examples/nethack/agent_demos/nethack_react_agent.py +832 -0
- synth_ai/environments/examples/nethack/agent_demos/test_nethack_react_agent.py +1112 -0
- synth_ai/environments/examples/nethack/engine.py +738 -0
- synth_ai/environments/examples/nethack/environment.py +255 -0
- synth_ai/environments/examples/nethack/helpers/__init__.py +42 -0
- synth_ai/environments/examples/nethack/helpers/action_mapping.py +301 -0
- synth_ai/environments/examples/nethack/helpers/nle_wrapper.py +401 -0
- synth_ai/environments/examples/nethack/helpers/observation_utils.py +433 -0
- synth_ai/environments/examples/nethack/helpers/recording_wrapper.py +201 -0
- synth_ai/environments/examples/nethack/helpers/trajectory_recorder.py +268 -0
- synth_ai/environments/examples/nethack/helpers/visualization/replay_viewer.py +308 -0
- synth_ai/environments/examples/nethack/helpers/visualization/visualizer.py +430 -0
- synth_ai/environments/examples/nethack/taskset.py +323 -0
- synth_ai/environments/examples/nethack/units/test_nethack_engine.py +277 -0
- synth_ai/environments/examples/nethack/units/test_nethack_environment.py +281 -0
- synth_ai/environments/examples/nethack/units/test_nethack_taskset.py +213 -0
- synth_ai/environments/examples/nethack/units/test_recording.py +307 -0
- synth_ai/environments/examples/red/__init__.py +7 -0
- synth_ai/environments/examples/red/agent_demos/__init__.py +1 -0
- synth_ai/environments/examples/red/agent_demos/test_synth_react.py +1471 -0
- synth_ai/environments/examples/red/config_logging.py +110 -0
- synth_ai/environments/examples/red/engine.py +693 -0
- synth_ai/environments/examples/red/engine_helpers/__init__.py +1 -0
- synth_ai/environments/examples/red/engine_helpers/memory_map.py +28 -0
- synth_ai/environments/examples/red/engine_helpers/reward_components.py +275 -0
- synth_ai/environments/examples/red/engine_helpers/reward_library/__init__.py +142 -0
- synth_ai/environments/examples/red/engine_helpers/reward_library/adaptive_rewards.py +56 -0
- synth_ai/environments/examples/red/engine_helpers/reward_library/battle_rewards.py +283 -0
- synth_ai/environments/examples/red/engine_helpers/reward_library/composite_rewards.py +149 -0
- synth_ai/environments/examples/red/engine_helpers/reward_library/economy_rewards.py +137 -0
- synth_ai/environments/examples/red/engine_helpers/reward_library/efficiency_rewards.py +56 -0
- synth_ai/environments/examples/red/engine_helpers/reward_library/exploration_rewards.py +330 -0
- synth_ai/environments/examples/red/engine_helpers/reward_library/novelty_rewards.py +120 -0
- synth_ai/environments/examples/red/engine_helpers/reward_library/pallet_town_rewards.py +558 -0
- synth_ai/environments/examples/red/engine_helpers/reward_library/pokemon_rewards.py +312 -0
- synth_ai/environments/examples/red/engine_helpers/reward_library/social_rewards.py +147 -0
- synth_ai/environments/examples/red/engine_helpers/reward_library/story_rewards.py +246 -0
- synth_ai/environments/examples/red/engine_helpers/screen_analysis.py +367 -0
- synth_ai/environments/examples/red/engine_helpers/state_extraction.py +139 -0
- synth_ai/environments/examples/red/environment.py +235 -0
- synth_ai/environments/examples/red/taskset.py +77 -0
- synth_ai/environments/examples/red/test_fixes.py +125 -0
- synth_ai/environments/examples/red/test_fixes_mock.py +148 -0
- synth_ai/environments/examples/red/units/__init__.py +1 -0
- synth_ai/environments/examples/red/units/test_basic_functionality.py +97 -0
- synth_ai/environments/examples/red/units/test_button_press_requirements.py +217 -0
- synth_ai/environments/examples/red/units/test_engine.py +192 -0
- synth_ai/environments/examples/red/units/test_environment.py +455 -0
- synth_ai/environments/examples/red/units/test_exploration_strategy.py +227 -0
- synth_ai/environments/examples/red/units/test_integration.py +217 -0
- synth_ai/environments/examples/red/units/test_memory_extraction.py +111 -0
- synth_ai/environments/examples/red/units/test_menu_bug_reproduction.py +1100 -0
- synth_ai/environments/examples/red/units/test_movement_debug.py +255 -0
- synth_ai/environments/examples/red/units/test_pokemon_mcts_debug.py +163 -0
- synth_ai/environments/examples/red/units/test_pokemon_mcts_verbose.py +117 -0
- synth_ai/environments/examples/red/units/test_red_basic.py +145 -0
- synth_ai/environments/examples/red/units/test_red_comprehensive.py +323 -0
- synth_ai/environments/examples/red/units/test_retry_movement.py +195 -0
- synth_ai/environments/examples/red/units/test_reward_components.py +186 -0
- synth_ai/environments/examples/red/units/test_rom_integration.py +260 -0
- synth_ai/environments/examples/red/units/test_taskset.py +116 -0
- synth_ai/environments/examples/red/units/test_tree.py +448 -0
- synth_ai/environments/examples/sokoban/__init__.py +1 -0
- synth_ai/environments/examples/sokoban/agent_demos/sokoban_full_eval.py +900 -0
- synth_ai/environments/examples/sokoban/agent_demos/test_dspy_react.py +1 -0
- synth_ai/environments/examples/sokoban/agent_demos/test_sokoban_react_agent.py +498 -0
- synth_ai/environments/examples/sokoban/agent_demos/test_synth_lats.py +1 -0
- synth_ai/environments/examples/sokoban/agent_demos/test_synth_react_locally.py +748 -0
- synth_ai/environments/examples/sokoban/agent_demos/test_synth_react_service.py +296 -0
- synth_ai/environments/examples/sokoban/engine.py +675 -0
- synth_ai/environments/examples/sokoban/engine_helpers/__init__.py +1 -0
- synth_ai/environments/examples/sokoban/engine_helpers/room_utils.py +656 -0
- synth_ai/environments/examples/sokoban/engine_helpers/vendored/__init__.py +17 -0
- synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/__init__.py +3 -0
- synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/boxoban_env.py +129 -0
- synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/render_utils.py +370 -0
- synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/room_utils.py +331 -0
- synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env.py +305 -0
- synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env_fixed_targets.py +66 -0
- synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env_pull.py +114 -0
- synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env_two_player.py +122 -0
- synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env_variations.py +394 -0
- synth_ai/environments/examples/sokoban/environment.py +228 -0
- synth_ai/environments/examples/sokoban/generate_verified_puzzles.py +438 -0
- synth_ai/environments/examples/sokoban/puzzle_loader.py +311 -0
- synth_ai/environments/examples/sokoban/taskset.py +425 -0
- synth_ai/environments/examples/sokoban/units/astar_common.py +94 -0
- synth_ai/environments/examples/sokoban/units/test_building_task_set.py +49 -0
- synth_ai/environments/examples/sokoban/units/test_false_positive.py +120 -0
- synth_ai/environments/examples/sokoban/units/test_simple_run_through_environment.py +119 -0
- synth_ai/environments/examples/sokoban/units/test_sokoban_environment.py +98 -0
- synth_ai/environments/examples/sokoban/units/test_tree.py +364 -0
- synth_ai/environments/examples/tictactoe/__init__.py +1 -0
- synth_ai/environments/examples/tictactoe/agent_demos/test_synth_react.py +266 -0
- synth_ai/environments/examples/tictactoe/agent_demos/test_tictactoe_react_agent.py +470 -0
- synth_ai/environments/examples/tictactoe/engine.py +368 -0
- synth_ai/environments/examples/tictactoe/environment.py +239 -0
- synth_ai/environments/examples/tictactoe/taskset.py +214 -0
- synth_ai/environments/examples/tictactoe/units/test_tictactoe_engine.py +393 -0
- synth_ai/environments/examples/tictactoe/units/test_tictactoe_environment.py +493 -0
- synth_ai/environments/examples/tictactoe/units/test_tictactoe_taskset.py +191 -0
- synth_ai/environments/examples/verilog/__init__.py +10 -0
- synth_ai/environments/examples/verilog/agent_demos/test_synth_react.py +520 -0
- synth_ai/environments/examples/verilog/engine.py +328 -0
- synth_ai/environments/examples/verilog/environment.py +349 -0
- synth_ai/environments/examples/verilog/taskset.py +418 -0
- synth_ai/environments/examples/verilog/units/test_verilog_engine.py +466 -0
- synth_ai/environments/examples/verilog/units/test_verilog_environment.py +585 -0
- synth_ai/environments/examples/verilog/units/test_verilog_integration.py +383 -0
- synth_ai/environments/examples/verilog/units/test_verilog_taskset.py +457 -0
- synth_ai/environments/reproducibility/core.py +42 -0
- synth_ai/environments/reproducibility/tree.py +364 -0
- synth_ai/environments/service/app.py +78 -0
- synth_ai/environments/service/core_routes.py +775 -0
- synth_ai/environments/service/external_registry.py +57 -0
- synth_ai/environments/service/registry.py +9 -0
- synth_ai/environments/stateful/__init__.py +1 -0
- synth_ai/environments/stateful/core.py +28 -0
- synth_ai/environments/stateful/engine.py +21 -0
- synth_ai/environments/stateful/state.py +7 -0
- synth_ai/environments/tasks/api.py +19 -0
- synth_ai/environments/tasks/core.py +78 -0
- synth_ai/environments/tasks/filters.py +39 -0
- synth_ai/environments/tasks/utils.py +89 -0
- synth_ai/environments/v0_observability/history.py +3 -0
- synth_ai/environments/v0_observability/log.py +2 -0
- synth_ai/lm/caching/constants.py +1 -0
- synth_ai/{zyk/lms → lm}/caching/ephemeral.py +4 -8
- synth_ai/{zyk/lms → lm}/caching/handler.py +15 -15
- synth_ai/{zyk/lms → lm}/caching/initialize.py +2 -4
- synth_ai/{zyk/lms → lm}/caching/persistent.py +4 -10
- synth_ai/{zyk/lms → lm}/config.py +2 -1
- synth_ai/{zyk/lms → lm}/constants.py +2 -2
- synth_ai/{zyk/lms → lm}/core/all.py +10 -10
- synth_ai/{zyk/lms → lm}/core/main.py +57 -33
- synth_ai/{zyk/lms → lm}/core/vendor_clients.py +12 -10
- synth_ai/lm/cost/monitor.py +1 -0
- synth_ai/lm/cost/statefulness.py +1 -0
- synth_ai/lm/provider_support/__init__.py +8 -0
- synth_ai/lm/provider_support/anthropic.py +945 -0
- synth_ai/lm/provider_support/openai.py +1115 -0
- synth_ai/lm/provider_support/suppress_logging.py +31 -0
- synth_ai/{zyk/lms → lm}/structured_outputs/handler.py +58 -80
- synth_ai/{zyk/lms → lm}/structured_outputs/inject.py +6 -20
- synth_ai/{zyk/lms → lm}/structured_outputs/rehabilitate.py +6 -12
- synth_ai/{zyk/lms → lm}/vendors/core/anthropic_api.py +21 -30
- synth_ai/{zyk/lms → lm}/vendors/core/gemini_api.py +35 -32
- synth_ai/{zyk/lms → lm}/vendors/core/mistral_api.py +19 -28
- synth_ai/{zyk/lms → lm}/vendors/core/openai_api.py +26 -36
- synth_ai/{zyk/lms → lm}/vendors/openai_standard.py +29 -33
- synth_ai/{zyk/lms → lm}/vendors/retries.py +1 -1
- synth_ai/lm/vendors/supported/__init__.py +0 -0
- synth_ai/{zyk/lms → lm}/vendors/supported/custom_endpoint.py +131 -118
- synth_ai/{zyk/lms → lm}/vendors/supported/deepseek.py +4 -8
- synth_ai/{zyk/lms → lm}/vendors/supported/grok.py +6 -8
- synth_ai/{zyk/lms → lm}/vendors/supported/groq.py +1 -1
- synth_ai/{zyk/lms → lm}/vendors/supported/ollama.py +2 -2
- synth_ai/{zyk/lms → lm}/vendors/supported/openrouter.py +18 -16
- synth_ai/{zyk/lms → lm}/vendors/supported/together.py +1 -1
- synth_ai/tracing/__init__.py +0 -0
- synth_ai/tracing/abstractions.py +224 -0
- synth_ai/tracing/base_client.py +91 -0
- synth_ai/tracing/client_manager.py +131 -0
- synth_ai/tracing/config.py +140 -0
- synth_ai/tracing/context.py +146 -0
- synth_ai/tracing/decorators.py +679 -0
- synth_ai/tracing/events/__init__.py +0 -0
- synth_ai/tracing/events/manage.py +147 -0
- synth_ai/tracing/events/scope.py +86 -0
- synth_ai/tracing/events/store.py +227 -0
- synth_ai/tracing/immediate_client.py +152 -0
- synth_ai/tracing/local.py +18 -0
- synth_ai/tracing/log_client_base.py +74 -0
- synth_ai/tracing/retry_queue.py +187 -0
- synth_ai/tracing/trackers.py +515 -0
- synth_ai/tracing/upload.py +504 -0
- synth_ai/tracing/utils.py +9 -0
- synth_ai/zyk/__init__.py +28 -2
- synth_ai-0.2.1.dev0.dist-info/METADATA +349 -0
- synth_ai-0.2.1.dev0.dist-info/RECORD +261 -0
- {synth_ai-0.2.0.dist-info → synth_ai-0.2.1.dev0.dist-info}/WHEEL +1 -1
- synth_ai/zyk/lms/caching/constants.py +0 -1
- synth_ai/zyk/lms/cost/monitor.py +0 -1
- synth_ai/zyk/lms/cost/statefulness.py +0 -1
- synth_ai-0.2.0.dist-info/METADATA +0 -36
- synth_ai-0.2.0.dist-info/RECORD +0 -50
- /synth_ai/{zyk/lms/__init__.py → environments/reproducibility/helpers.py} +0 -0
- /synth_ai/{zyk/lms/caching → lm}/__init__.py +0 -0
- /synth_ai/{zyk/lms/core → lm/caching}/__init__.py +0 -0
- /synth_ai/{zyk/lms → lm}/caching/dbs.py +0 -0
- /synth_ai/{zyk/lms/cost → lm/core}/__init__.py +0 -0
- /synth_ai/{zyk/lms → lm}/core/exceptions.py +0 -0
- /synth_ai/{zyk/lms/structured_outputs → lm/cost}/__init__.py +0 -0
- /synth_ai/{zyk/lms/vendors → lm/structured_outputs}/__init__.py +0 -0
- /synth_ai/{zyk/lms → lm}/tools/__init__.py +0 -0
- /synth_ai/{zyk/lms → lm}/tools/base.py +0 -0
- /synth_ai/{zyk/lms/vendors/core → lm/vendors}/__init__.py +0 -0
- /synth_ai/{zyk/lms → lm}/vendors/base.py +0 -0
- /synth_ai/{zyk/lms/vendors/local → lm/vendors/core}/__init__.py +0 -0
- /synth_ai/{zyk/lms/vendors/supported → lm/vendors/local}/__init__.py +0 -0
- /synth_ai/{zyk/lms → lm}/vendors/local/ollama.py +0 -0
- {synth_ai-0.2.0.dist-info → synth_ai-0.2.1.dev0.dist-info/licenses}/LICENSE +0 -0
- {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
|