activeagent 1.0.0.rc1 → 1.0.1
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +102 -1
- data/lib/active_agent/providers/_base_provider.rb +94 -82
- data/lib/active_agent/providers/anthropic/_types.rb +2 -2
- data/lib/active_agent/providers/anthropic/options.rb +4 -6
- data/lib/active_agent/providers/anthropic/request.rb +157 -78
- data/lib/active_agent/providers/anthropic/transforms.rb +482 -0
- data/lib/active_agent/providers/anthropic_provider.rb +159 -59
- data/lib/active_agent/providers/common/messages/_types.rb +46 -3
- data/lib/active_agent/providers/common/messages/assistant.rb +20 -4
- data/lib/active_agent/providers/common/responses/base.rb +118 -70
- data/lib/active_agent/providers/common/usage.rb +385 -0
- data/lib/active_agent/providers/concerns/instrumentation.rb +263 -0
- data/lib/active_agent/providers/concerns/previewable.rb +39 -5
- data/lib/active_agent/providers/concerns/tool_choice_clearing.rb +62 -0
- data/lib/active_agent/providers/log_subscriber.rb +64 -246
- data/lib/active_agent/providers/mock_provider.rb +23 -23
- data/lib/active_agent/providers/ollama/chat/request.rb +214 -35
- data/lib/active_agent/providers/ollama/chat/transforms.rb +135 -0
- data/lib/active_agent/providers/ollama/embedding/request.rb +160 -47
- data/lib/active_agent/providers/ollama/embedding/transforms.rb +160 -0
- data/lib/active_agent/providers/ollama_provider.rb +0 -1
- data/lib/active_agent/providers/open_ai/_base.rb +3 -2
- data/lib/active_agent/providers/open_ai/chat/_types.rb +13 -1
- data/lib/active_agent/providers/open_ai/chat/request.rb +132 -186
- data/lib/active_agent/providers/open_ai/chat/transforms.rb +444 -0
- data/lib/active_agent/providers/open_ai/chat_provider.rb +95 -36
- data/lib/active_agent/providers/open_ai/embedding/_types.rb +13 -2
- data/lib/active_agent/providers/open_ai/embedding/request.rb +38 -70
- data/lib/active_agent/providers/open_ai/embedding/transforms.rb +88 -0
- data/lib/active_agent/providers/open_ai/responses/_types.rb +1 -7
- data/lib/active_agent/providers/open_ai/responses/request.rb +116 -135
- data/lib/active_agent/providers/open_ai/responses/transforms.rb +363 -0
- data/lib/active_agent/providers/open_ai/responses_provider.rb +115 -30
- data/lib/active_agent/providers/open_ai_provider.rb +0 -3
- data/lib/active_agent/providers/open_router/_types.rb +27 -1
- data/lib/active_agent/providers/open_router/options.rb +49 -1
- data/lib/active_agent/providers/open_router/request.rb +252 -66
- data/lib/active_agent/providers/open_router/requests/_types.rb +0 -1
- data/lib/active_agent/providers/open_router/requests/messages/_types.rb +37 -40
- data/lib/active_agent/providers/open_router/requests/messages/content/file.rb +19 -3
- data/lib/active_agent/providers/open_router/requests/messages/content/files/details.rb +15 -4
- data/lib/active_agent/providers/open_router/requests/plugin.rb +19 -3
- data/lib/active_agent/providers/open_router/requests/plugins/pdf_config.rb +30 -8
- data/lib/active_agent/providers/open_router/requests/prediction.rb +17 -0
- data/lib/active_agent/providers/open_router/requests/provider_preferences/max_price.rb +41 -7
- data/lib/active_agent/providers/open_router/requests/provider_preferences.rb +60 -19
- data/lib/active_agent/providers/open_router/requests/response_format.rb +30 -2
- data/lib/active_agent/providers/open_router/transforms.rb +164 -0
- data/lib/active_agent/providers/open_router_provider.rb +23 -0
- data/lib/active_agent/version.rb +1 -1
- metadata +17 -160
- data/lib/active_agent/generation_provider/open_router/types.rb +0 -505
- data/lib/active_agent/generation_provider/xai_provider.rb +0 -144
- data/lib/active_agent/providers/anthropic/requests/_types.rb +0 -190
- data/lib/active_agent/providers/anthropic/requests/container_params.rb +0 -19
- data/lib/active_agent/providers/anthropic/requests/content/base.rb +0 -21
- data/lib/active_agent/providers/anthropic/requests/content/sources/base.rb +0 -22
- data/lib/active_agent/providers/anthropic/requests/context_management_config.rb +0 -18
- data/lib/active_agent/providers/anthropic/requests/messages/_types.rb +0 -189
- data/lib/active_agent/providers/anthropic/requests/messages/assistant.rb +0 -23
- data/lib/active_agent/providers/anthropic/requests/messages/base.rb +0 -63
- data/lib/active_agent/providers/anthropic/requests/messages/content/_types.rb +0 -143
- data/lib/active_agent/providers/anthropic/requests/messages/content/base.rb +0 -21
- data/lib/active_agent/providers/anthropic/requests/messages/content/document.rb +0 -26
- data/lib/active_agent/providers/anthropic/requests/messages/content/image.rb +0 -23
- data/lib/active_agent/providers/anthropic/requests/messages/content/redacted_thinking.rb +0 -21
- data/lib/active_agent/providers/anthropic/requests/messages/content/search_result.rb +0 -27
- data/lib/active_agent/providers/anthropic/requests/messages/content/sources/_types.rb +0 -171
- data/lib/active_agent/providers/anthropic/requests/messages/content/sources/base.rb +0 -22
- data/lib/active_agent/providers/anthropic/requests/messages/content/sources/document_base64.rb +0 -25
- data/lib/active_agent/providers/anthropic/requests/messages/content/sources/document_file.rb +0 -23
- data/lib/active_agent/providers/anthropic/requests/messages/content/sources/document_text.rb +0 -25
- data/lib/active_agent/providers/anthropic/requests/messages/content/sources/document_url.rb +0 -23
- data/lib/active_agent/providers/anthropic/requests/messages/content/sources/image_base64.rb +0 -27
- data/lib/active_agent/providers/anthropic/requests/messages/content/sources/image_file.rb +0 -23
- data/lib/active_agent/providers/anthropic/requests/messages/content/sources/image_url.rb +0 -23
- data/lib/active_agent/providers/anthropic/requests/messages/content/text.rb +0 -22
- data/lib/active_agent/providers/anthropic/requests/messages/content/thinking.rb +0 -23
- data/lib/active_agent/providers/anthropic/requests/messages/content/tool_result.rb +0 -24
- data/lib/active_agent/providers/anthropic/requests/messages/content/tool_use.rb +0 -28
- data/lib/active_agent/providers/anthropic/requests/messages/user.rb +0 -21
- data/lib/active_agent/providers/anthropic/requests/metadata.rb +0 -18
- data/lib/active_agent/providers/anthropic/requests/response_format.rb +0 -22
- data/lib/active_agent/providers/anthropic/requests/thinking_config/_types.rb +0 -60
- data/lib/active_agent/providers/anthropic/requests/thinking_config/base.rb +0 -20
- data/lib/active_agent/providers/anthropic/requests/thinking_config/disabled.rb +0 -16
- data/lib/active_agent/providers/anthropic/requests/thinking_config/enabled.rb +0 -20
- data/lib/active_agent/providers/anthropic/requests/tool_choice/_types.rb +0 -78
- data/lib/active_agent/providers/anthropic/requests/tool_choice/any.rb +0 -17
- data/lib/active_agent/providers/anthropic/requests/tool_choice/auto.rb +0 -17
- data/lib/active_agent/providers/anthropic/requests/tool_choice/base.rb +0 -20
- data/lib/active_agent/providers/anthropic/requests/tool_choice/none.rb +0 -16
- data/lib/active_agent/providers/anthropic/requests/tool_choice/tool.rb +0 -20
- data/lib/active_agent/providers/ollama/chat/requests/_types.rb +0 -3
- data/lib/active_agent/providers/ollama/chat/requests/messages/_types.rb +0 -116
- data/lib/active_agent/providers/ollama/chat/requests/messages/assistant.rb +0 -19
- data/lib/active_agent/providers/ollama/chat/requests/messages/user.rb +0 -19
- data/lib/active_agent/providers/ollama/embedding/requests/_types.rb +0 -83
- data/lib/active_agent/providers/ollama/embedding/requests/options.rb +0 -104
- data/lib/active_agent/providers/open_ai/chat/requests/_types.rb +0 -229
- data/lib/active_agent/providers/open_ai/chat/requests/audio.rb +0 -24
- data/lib/active_agent/providers/open_ai/chat/requests/messages/_types.rb +0 -123
- data/lib/active_agent/providers/open_ai/chat/requests/messages/assistant.rb +0 -42
- data/lib/active_agent/providers/open_ai/chat/requests/messages/base.rb +0 -78
- data/lib/active_agent/providers/open_ai/chat/requests/messages/content/_types.rb +0 -133
- data/lib/active_agent/providers/open_ai/chat/requests/messages/content/audio.rb +0 -35
- data/lib/active_agent/providers/open_ai/chat/requests/messages/content/base.rb +0 -24
- data/lib/active_agent/providers/open_ai/chat/requests/messages/content/file.rb +0 -26
- data/lib/active_agent/providers/open_ai/chat/requests/messages/content/files/_types.rb +0 -60
- data/lib/active_agent/providers/open_ai/chat/requests/messages/content/files/details.rb +0 -41
- data/lib/active_agent/providers/open_ai/chat/requests/messages/content/image.rb +0 -37
- data/lib/active_agent/providers/open_ai/chat/requests/messages/content/refusal.rb +0 -25
- data/lib/active_agent/providers/open_ai/chat/requests/messages/content/text.rb +0 -25
- data/lib/active_agent/providers/open_ai/chat/requests/messages/developer.rb +0 -25
- data/lib/active_agent/providers/open_ai/chat/requests/messages/function.rb +0 -25
- data/lib/active_agent/providers/open_ai/chat/requests/messages/system.rb +0 -25
- data/lib/active_agent/providers/open_ai/chat/requests/messages/tool.rb +0 -26
- data/lib/active_agent/providers/open_ai/chat/requests/messages/user.rb +0 -32
- data/lib/active_agent/providers/open_ai/chat/requests/prediction.rb +0 -46
- data/lib/active_agent/providers/open_ai/chat/requests/response_format.rb +0 -53
- data/lib/active_agent/providers/open_ai/chat/requests/stream_options.rb +0 -24
- data/lib/active_agent/providers/open_ai/chat/requests/tool_choice.rb +0 -26
- data/lib/active_agent/providers/open_ai/chat/requests/tools/_types.rb +0 -5
- data/lib/active_agent/providers/open_ai/chat/requests/tools/base.rb +0 -22
- data/lib/active_agent/providers/open_ai/chat/requests/tools/custom_tool.rb +0 -41
- data/lib/active_agent/providers/open_ai/chat/requests/tools/function_tool.rb +0 -51
- data/lib/active_agent/providers/open_ai/chat/requests/web_search_options.rb +0 -45
- data/lib/active_agent/providers/open_ai/embedding/requests/_types.rb +0 -49
- data/lib/active_agent/providers/open_ai/responses/requests/_types.rb +0 -231
- data/lib/active_agent/providers/open_ai/responses/requests/conversation.rb +0 -23
- data/lib/active_agent/providers/open_ai/responses/requests/inputs/_types.rb +0 -264
- data/lib/active_agent/providers/open_ai/responses/requests/inputs/assistant_message.rb +0 -22
- data/lib/active_agent/providers/open_ai/responses/requests/inputs/base.rb +0 -89
- data/lib/active_agent/providers/open_ai/responses/requests/inputs/code_interpreter_tool_call.rb +0 -30
- data/lib/active_agent/providers/open_ai/responses/requests/inputs/computer_tool_call.rb +0 -28
- data/lib/active_agent/providers/open_ai/responses/requests/inputs/computer_tool_call_output.rb +0 -33
- data/lib/active_agent/providers/open_ai/responses/requests/inputs/content/_types.rb +0 -207
- data/lib/active_agent/providers/open_ai/responses/requests/inputs/content/base.rb +0 -22
- data/lib/active_agent/providers/open_ai/responses/requests/inputs/content/input_audio.rb +0 -26
- data/lib/active_agent/providers/open_ai/responses/requests/inputs/content/input_file.rb +0 -28
- data/lib/active_agent/providers/open_ai/responses/requests/inputs/content/input_image.rb +0 -28
- data/lib/active_agent/providers/open_ai/responses/requests/inputs/content/input_text.rb +0 -25
- data/lib/active_agent/providers/open_ai/responses/requests/inputs/custom_tool_call.rb +0 -28
- data/lib/active_agent/providers/open_ai/responses/requests/inputs/custom_tool_call_output.rb +0 -27
- data/lib/active_agent/providers/open_ai/responses/requests/inputs/developer_message.rb +0 -20
- data/lib/active_agent/providers/open_ai/responses/requests/inputs/file_search_tool_call.rb +0 -25
- data/lib/active_agent/providers/open_ai/responses/requests/inputs/function_call_output.rb +0 -32
- data/lib/active_agent/providers/open_ai/responses/requests/inputs/function_tool_call.rb +0 -28
- data/lib/active_agent/providers/open_ai/responses/requests/inputs/image_gen_tool_call.rb +0 -27
- data/lib/active_agent/providers/open_ai/responses/requests/inputs/input_message.rb +0 -31
- data/lib/active_agent/providers/open_ai/responses/requests/inputs/item_reference.rb +0 -23
- data/lib/active_agent/providers/open_ai/responses/requests/inputs/local_shell_tool_call.rb +0 -26
- data/lib/active_agent/providers/open_ai/responses/requests/inputs/local_shell_tool_call_output.rb +0 -33
- data/lib/active_agent/providers/open_ai/responses/requests/inputs/mcp_approval_request.rb +0 -30
- data/lib/active_agent/providers/open_ai/responses/requests/inputs/mcp_approval_response.rb +0 -28
- data/lib/active_agent/providers/open_ai/responses/requests/inputs/mcp_list_tools.rb +0 -29
- data/lib/active_agent/providers/open_ai/responses/requests/inputs/mcp_tool_call.rb +0 -35
- data/lib/active_agent/providers/open_ai/responses/requests/inputs/output_message.rb +0 -35
- data/lib/active_agent/providers/open_ai/responses/requests/inputs/reasoning.rb +0 -33
- data/lib/active_agent/providers/open_ai/responses/requests/inputs/system_message.rb +0 -20
- data/lib/active_agent/providers/open_ai/responses/requests/inputs/tool_call_base.rb +0 -27
- data/lib/active_agent/providers/open_ai/responses/requests/inputs/tool_message.rb +0 -23
- data/lib/active_agent/providers/open_ai/responses/requests/inputs/user_message.rb +0 -20
- data/lib/active_agent/providers/open_ai/responses/requests/inputs/web_search_tool_call.rb +0 -24
- data/lib/active_agent/providers/open_ai/responses/requests/prompt_reference.rb +0 -23
- data/lib/active_agent/providers/open_ai/responses/requests/reasoning.rb +0 -23
- data/lib/active_agent/providers/open_ai/responses/requests/stream_options.rb +0 -20
- data/lib/active_agent/providers/open_ai/responses/requests/text/_types.rb +0 -89
- data/lib/active_agent/providers/open_ai/responses/requests/text/base.rb +0 -22
- data/lib/active_agent/providers/open_ai/responses/requests/text/json_object.rb +0 -20
- data/lib/active_agent/providers/open_ai/responses/requests/text/json_schema.rb +0 -48
- data/lib/active_agent/providers/open_ai/responses/requests/text/plain.rb +0 -20
- data/lib/active_agent/providers/open_ai/responses/requests/text.rb +0 -41
- data/lib/active_agent/providers/open_ai/responses/requests/tool_choice.rb +0 -26
- data/lib/active_agent/providers/open_ai/responses/requests/tools/_types.rb +0 -112
- data/lib/active_agent/providers/open_ai/responses/requests/tools/base.rb +0 -25
- data/lib/active_agent/providers/open_ai/responses/requests/tools/code_interpreter_tool.rb +0 -23
- data/lib/active_agent/providers/open_ai/responses/requests/tools/computer_tool.rb +0 -27
- data/lib/active_agent/providers/open_ai/responses/requests/tools/custom_tool.rb +0 -28
- data/lib/active_agent/providers/open_ai/responses/requests/tools/file_search_tool.rb +0 -27
- data/lib/active_agent/providers/open_ai/responses/requests/tools/function_tool.rb +0 -29
- data/lib/active_agent/providers/open_ai/responses/requests/tools/image_generation_tool.rb +0 -37
- data/lib/active_agent/providers/open_ai/responses/requests/tools/local_shell_tool.rb +0 -21
- data/lib/active_agent/providers/open_ai/responses/requests/tools/mcp_tool.rb +0 -41
- data/lib/active_agent/providers/open_ai/responses/requests/tools/web_search_preview_tool.rb +0 -24
- data/lib/active_agent/providers/open_ai/responses/requests/tools/web_search_tool.rb +0 -25
- data/lib/active_agent/providers/open_ai/schema.yml +0 -65937
- data/lib/active_agent/providers/open_router/requests/message.rb +0 -1
- data/lib/active_agent/providers/open_router/requests/messages/assistant.rb +0 -20
- data/lib/active_agent/providers/open_router/requests/messages/user.rb +0 -30
|
@@ -1,27 +1,24 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "active_agent/providers/common/model"
|
|
4
|
+
require "active_agent/providers/common/usage"
|
|
4
5
|
|
|
5
6
|
module ActiveAgent
|
|
6
7
|
module Providers
|
|
7
8
|
module Common
|
|
8
9
|
module Responses
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
# This class represents the standard response structure from AI providers
|
|
12
|
-
# across different services (OpenAI, Anthropic, etc.). It provides a unified
|
|
13
|
-
# interface for accessing response data, usage statistics, and request context.
|
|
10
|
+
# Provides unified interface for AI provider responses across OpenAI, Anthropic, etc.
|
|
14
11
|
#
|
|
15
12
|
# @abstract Subclass and override {#usage} if provider uses non-standard format
|
|
16
13
|
#
|
|
17
|
-
# @note
|
|
18
|
-
# - {Prompt} for conversational/completion responses
|
|
19
|
-
# - {Embed} for embedding responses
|
|
14
|
+
# @note Use specialized subclasses for specific response types:
|
|
15
|
+
# - {Prompt} for conversational/completion responses
|
|
16
|
+
# - {Embed} for embedding responses
|
|
20
17
|
#
|
|
21
18
|
# @example Accessing response data
|
|
22
19
|
# response = agent.prompt.generate_now
|
|
23
20
|
# response.success? #=> true
|
|
24
|
-
# response.usage #=>
|
|
21
|
+
# response.usage #=> Usage object with normalized fields
|
|
25
22
|
# response.total_tokens #=> 30
|
|
26
23
|
#
|
|
27
24
|
# @example Inspecting raw provider data
|
|
@@ -33,117 +30,168 @@ module ActiveAgent
|
|
|
33
30
|
# @see BaseModel
|
|
34
31
|
class Base < BaseModel
|
|
35
32
|
# @!attribute [r] context
|
|
36
|
-
#
|
|
33
|
+
# Original request context sent to the provider.
|
|
37
34
|
#
|
|
38
|
-
#
|
|
39
|
-
# messages, tools, and other configuration passed to the LLM.
|
|
35
|
+
# Includes instructions, messages, tools, and configuration.
|
|
40
36
|
#
|
|
41
|
-
# @return [Hash]
|
|
37
|
+
# @return [Hash]
|
|
42
38
|
attribute :context, writable: false
|
|
43
39
|
|
|
44
40
|
# @!attribute [r] raw_request
|
|
45
|
-
#
|
|
41
|
+
# Most recent request in provider-specific format.
|
|
46
42
|
#
|
|
47
|
-
#
|
|
48
|
-
# useful for debugging and logging.
|
|
43
|
+
# Useful for debugging and logging.
|
|
49
44
|
#
|
|
50
|
-
# @return [Hash]
|
|
45
|
+
# @return [Hash]
|
|
51
46
|
attribute :raw_request, writable: false
|
|
52
47
|
|
|
53
48
|
# @!attribute [r] raw_response
|
|
54
|
-
#
|
|
49
|
+
# Most recent response in provider-specific format.
|
|
55
50
|
#
|
|
56
|
-
#
|
|
57
|
-
#
|
|
51
|
+
# Includes metadata, usage stats, and provider-specific fields.
|
|
52
|
+
# Hash keys are deep symbolized for consistent access.
|
|
58
53
|
#
|
|
59
|
-
# @return [Hash]
|
|
54
|
+
# @return [Hash]
|
|
60
55
|
attribute :raw_response, writable: false
|
|
61
56
|
|
|
62
|
-
#
|
|
57
|
+
# @!attribute [r] usages
|
|
58
|
+
# Usage objects from each API call in multi-turn conversations.
|
|
59
|
+
#
|
|
60
|
+
# Each call (e.g., for tool calling) tracks usage separately. These are
|
|
61
|
+
# summed to provide cumulative statistics via {#usage}.
|
|
63
62
|
#
|
|
64
|
-
#
|
|
65
|
-
|
|
66
|
-
|
|
63
|
+
# @return [Array<Usage>]
|
|
64
|
+
attribute :usages, default: -> { [] }, writable: false
|
|
65
|
+
|
|
66
|
+
# Initializes response with deep-duplicated attributes.
|
|
67
67
|
#
|
|
68
|
-
#
|
|
69
|
-
#
|
|
70
|
-
# @option kwargs [Hash] :raw_request the provider-formatted request
|
|
71
|
-
# @option kwargs [Hash] :raw_response the provider-formatted response
|
|
68
|
+
# Deep duplication prevents external modifications from affecting internal state.
|
|
69
|
+
# The raw_response is deep symbolized for consistent key access across providers.
|
|
72
70
|
#
|
|
73
|
-
# @
|
|
71
|
+
# @param kwargs [Hash]
|
|
72
|
+
# @option kwargs [Hash] :context
|
|
73
|
+
# @option kwargs [Hash] :raw_request
|
|
74
|
+
# @option kwargs [Hash] :raw_response
|
|
74
75
|
def initialize(kwargs = {})
|
|
75
|
-
|
|
76
|
+
kwargs = kwargs.deep_dup # Ensure that userland can't fuck with our memory space
|
|
77
|
+
|
|
78
|
+
# Deep symbolize raw_response for consistent access across all extraction methods
|
|
79
|
+
if kwargs[:raw_response].is_a?(Hash)
|
|
80
|
+
kwargs[:raw_response] = kwargs[:raw_response].deep_symbolize_keys
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
super(kwargs)
|
|
76
84
|
end
|
|
77
85
|
|
|
78
|
-
#
|
|
79
|
-
#
|
|
80
|
-
# @return [String, Array<Hash>, nil] the instructions that were sent to the provider
|
|
86
|
+
# @return [String, Array<Hash>, nil]
|
|
81
87
|
def instructions
|
|
82
88
|
context[:instructions]
|
|
83
89
|
end
|
|
84
90
|
|
|
85
|
-
# Indicates whether the generation request was successful.
|
|
86
|
-
#
|
|
87
91
|
# @todo Better handling of failure flows
|
|
88
|
-
#
|
|
89
|
-
# @return [Boolean] true if successful, false otherwise
|
|
92
|
+
# @return [Boolean]
|
|
90
93
|
def success?
|
|
91
94
|
true
|
|
92
95
|
end
|
|
93
96
|
|
|
94
|
-
#
|
|
97
|
+
# Normalized usage statistics across all providers.
|
|
98
|
+
#
|
|
99
|
+
# For multi-turn conversations with tool calling, returns cumulative
|
|
100
|
+
# usage across all API calls (sum of {#usages}).
|
|
95
101
|
#
|
|
96
|
-
#
|
|
97
|
-
# standardized format within the response. This method extracts that
|
|
98
|
-
# information for token counting and billing purposes.
|
|
102
|
+
# @return [Usage, nil]
|
|
99
103
|
#
|
|
100
|
-
# @
|
|
101
|
-
#
|
|
104
|
+
# @example Single-turn usage
|
|
105
|
+
# response.usage.input_tokens #=> 100
|
|
106
|
+
# response.usage.output_tokens #=> 25
|
|
107
|
+
# response.usage.total_tokens #=> 125
|
|
102
108
|
#
|
|
103
|
-
# @example
|
|
104
|
-
#
|
|
105
|
-
#
|
|
106
|
-
#
|
|
107
|
-
#
|
|
108
|
-
#
|
|
109
|
+
# @example Multi-turn usage (cumulative)
|
|
110
|
+
# # After 3 API calls due to tool usage:
|
|
111
|
+
# response.usage.input_tokens #=> 350 (sum of all calls)
|
|
112
|
+
# response.usage.output_tokens #=> 120 (sum of all calls)
|
|
113
|
+
#
|
|
114
|
+
# @see Usage
|
|
109
115
|
def usage
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
116
|
+
@usage ||= begin
|
|
117
|
+
if usages.any?
|
|
118
|
+
usages.reduce(:+)
|
|
119
|
+
elsif raw_response
|
|
120
|
+
Usage.from_provider_usage(
|
|
121
|
+
raw_response.is_a?(Hash) ? raw_response[:usage] : raw_response.usage
|
|
122
|
+
)
|
|
123
|
+
end
|
|
115
124
|
end
|
|
116
125
|
end
|
|
117
126
|
|
|
118
|
-
#
|
|
127
|
+
# Response ID from provider, useful for tracking and debugging.
|
|
119
128
|
#
|
|
120
|
-
# @return [
|
|
129
|
+
# @return [String, nil]
|
|
121
130
|
#
|
|
122
131
|
# @example
|
|
123
|
-
# response.
|
|
124
|
-
|
|
125
|
-
|
|
132
|
+
# response.id #=> "chatcmpl-CbDx1nXoNSBrNIMhiuy5fk7jXQjmT" (OpenAI)
|
|
133
|
+
# response.id #=> "msg_01RotDmSnYpKQjrTpaHUaEBz" (Anthropic)
|
|
134
|
+
# response.id #=> "gen-1761505659-yxgaVsqVABMQqw6oA7QF" (OpenRouter)
|
|
135
|
+
def id
|
|
136
|
+
@id ||= begin
|
|
137
|
+
return nil unless raw_response
|
|
138
|
+
|
|
139
|
+
if raw_response.is_a?(Hash)
|
|
140
|
+
raw_response[:id]
|
|
141
|
+
elsif raw_response.respond_to?(:id)
|
|
142
|
+
raw_response.id
|
|
143
|
+
end
|
|
144
|
+
end
|
|
126
145
|
end
|
|
127
146
|
|
|
128
|
-
#
|
|
147
|
+
# Model name from provider response.
|
|
148
|
+
#
|
|
149
|
+
# Useful for confirming which model was actually used, as providers may
|
|
150
|
+
# use different versions than requested.
|
|
129
151
|
#
|
|
130
|
-
# @return [
|
|
152
|
+
# @return [String, nil]
|
|
131
153
|
#
|
|
132
154
|
# @example
|
|
133
|
-
# response.
|
|
134
|
-
|
|
135
|
-
|
|
155
|
+
# response.model #=> "gpt-4o-mini-2024-07-18"
|
|
156
|
+
# response.model #=> "claude-3-5-haiku-20241022"
|
|
157
|
+
def model
|
|
158
|
+
@model ||= begin
|
|
159
|
+
return nil unless raw_response
|
|
160
|
+
|
|
161
|
+
if raw_response.is_a?(Hash)
|
|
162
|
+
raw_response[:model]
|
|
163
|
+
elsif raw_response.respond_to?(:model)
|
|
164
|
+
raw_response.model
|
|
165
|
+
end
|
|
166
|
+
end
|
|
136
167
|
end
|
|
137
168
|
|
|
138
|
-
#
|
|
169
|
+
# Finish reason from provider response.
|
|
170
|
+
#
|
|
171
|
+
# Indicates why generation stopped (e.g., "stop", "length", "tool_calls").
|
|
172
|
+
# Normalizes access across providers that use different field names.
|
|
139
173
|
#
|
|
140
|
-
# @return [
|
|
174
|
+
# @return [String, nil]
|
|
141
175
|
#
|
|
142
176
|
# @example
|
|
143
|
-
# response.
|
|
144
|
-
|
|
145
|
-
|
|
177
|
+
# response.finish_reason #=> "stop"
|
|
178
|
+
# response.finish_reason #=> "length"
|
|
179
|
+
# response.finish_reason #=> "tool_calls"
|
|
180
|
+
# response.stop_reason #=> "stop" (alias)
|
|
181
|
+
def finish_reason
|
|
182
|
+
@finish_reason ||= begin
|
|
183
|
+
return nil unless raw_response
|
|
184
|
+
|
|
185
|
+
if raw_response.is_a?(Hash)
|
|
186
|
+
# OpenAI format: choices[0].finish_reason or choices[0].message.finish_reason
|
|
187
|
+
raw_response.dig(:choices, 0, :finish_reason) ||
|
|
188
|
+
raw_response.dig(:choices, 0, :message, :finish_reason) ||
|
|
189
|
+
# Anthropic format: stop_reason
|
|
190
|
+
raw_response[:stop_reason]
|
|
191
|
+
end
|
|
192
|
+
end
|
|
146
193
|
end
|
|
194
|
+
alias_method :stop_reason, :finish_reason
|
|
147
195
|
end
|
|
148
196
|
end
|
|
149
197
|
end
|
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_agent/providers/common/model"
|
|
4
|
+
|
|
5
|
+
module ActiveAgent
|
|
6
|
+
module Providers
|
|
7
|
+
module Common
|
|
8
|
+
# Normalizes token usage statistics across AI providers.
|
|
9
|
+
#
|
|
10
|
+
# Providers return usage data in different formats with different field names.
|
|
11
|
+
# This model normalizes them into a consistent structure, automatically calculating
|
|
12
|
+
# +total_tokens+ if not provided.
|
|
13
|
+
#
|
|
14
|
+
# @example Accessing normalized usage data
|
|
15
|
+
# usage = response.normalized_usage
|
|
16
|
+
# usage.input_tokens #=> 100
|
|
17
|
+
# usage.output_tokens #=> 25
|
|
18
|
+
# usage.total_tokens #=> 125
|
|
19
|
+
# usage.cached_tokens #=> 20 (if available)
|
|
20
|
+
#
|
|
21
|
+
# @example Provider-specific details
|
|
22
|
+
# usage.provider_details #=> { "completion_tokens_details" => {...}, ... }
|
|
23
|
+
# usage.duration_ms #=> 5000 (for Ollama)
|
|
24
|
+
# usage.service_tier #=> "standard" (for Anthropic)
|
|
25
|
+
#
|
|
26
|
+
# @see https://platform.openai.com/docs/api-reference/chat/object OpenAI Chat Completion
|
|
27
|
+
# @see https://docs.anthropic.com/en/api/messages Anthropic Messages API
|
|
28
|
+
# @see https://github.com/ollama/ollama/blob/main/docs/api.md Ollama API
|
|
29
|
+
class Usage < BaseModel
|
|
30
|
+
# @!attribute [rw] input_tokens
|
|
31
|
+
# Normalized from:
|
|
32
|
+
# - OpenAI Chat/Embeddings: prompt_tokens
|
|
33
|
+
# - OpenAI Responses API: input_tokens
|
|
34
|
+
# - Anthropic: input_tokens
|
|
35
|
+
# - Ollama: prompt_eval_count
|
|
36
|
+
# - OpenRouter: prompt_tokens
|
|
37
|
+
#
|
|
38
|
+
# @return [Integer]
|
|
39
|
+
attribute :input_tokens, :integer, default: 0
|
|
40
|
+
|
|
41
|
+
# @!attribute [rw] output_tokens
|
|
42
|
+
# Normalized from:
|
|
43
|
+
# - OpenAI Chat: completion_tokens
|
|
44
|
+
# - OpenAI Responses API: output_tokens
|
|
45
|
+
# - Anthropic: output_tokens
|
|
46
|
+
# - Ollama: eval_count
|
|
47
|
+
# - OpenRouter: completion_tokens
|
|
48
|
+
# - OpenAI Embeddings: 0 (no output tokens)
|
|
49
|
+
#
|
|
50
|
+
# @return [Integer]
|
|
51
|
+
attribute :output_tokens, :integer, default: 0
|
|
52
|
+
|
|
53
|
+
# @!attribute [rw] total_tokens
|
|
54
|
+
# Automatically calculated as input_tokens + output_tokens if not provided by provider.
|
|
55
|
+
#
|
|
56
|
+
# @return [Integer]
|
|
57
|
+
attribute :total_tokens, :integer
|
|
58
|
+
|
|
59
|
+
# @!attribute [rw] cached_tokens
|
|
60
|
+
# Available from:
|
|
61
|
+
# - OpenAI: prompt_tokens_details.cached_tokens or input_tokens_details.cached_tokens
|
|
62
|
+
# - Anthropic: cache_read_input_tokens
|
|
63
|
+
#
|
|
64
|
+
# @return [Integer, nil]
|
|
65
|
+
attribute :cached_tokens, :integer
|
|
66
|
+
|
|
67
|
+
# @!attribute [rw] reasoning_tokens
|
|
68
|
+
# Available from:
|
|
69
|
+
# - OpenAI Chat: completion_tokens_details.reasoning_tokens
|
|
70
|
+
# - OpenAI Responses: output_tokens_details.reasoning_tokens
|
|
71
|
+
#
|
|
72
|
+
# @return [Integer, nil]
|
|
73
|
+
attribute :reasoning_tokens, :integer
|
|
74
|
+
|
|
75
|
+
# @!attribute [rw] audio_tokens
|
|
76
|
+
# Available from:
|
|
77
|
+
# - OpenAI: sum of prompt_tokens_details.audio_tokens and completion_tokens_details.audio_tokens
|
|
78
|
+
#
|
|
79
|
+
# @return [Integer, nil]
|
|
80
|
+
attribute :audio_tokens, :integer
|
|
81
|
+
|
|
82
|
+
# @!attribute [rw] cache_creation_tokens
|
|
83
|
+
# Available from:
|
|
84
|
+
# - Anthropic: cache_creation_input_tokens
|
|
85
|
+
#
|
|
86
|
+
# @return [Integer, nil]
|
|
87
|
+
attribute :cache_creation_tokens, :integer
|
|
88
|
+
|
|
89
|
+
# @!attribute [rw] service_tier
|
|
90
|
+
# Available from:
|
|
91
|
+
# - Anthropic: service_tier ("standard", "priority", "batch")
|
|
92
|
+
#
|
|
93
|
+
# @return [String, nil]
|
|
94
|
+
attribute :service_tier, :string
|
|
95
|
+
|
|
96
|
+
# @!attribute [rw] duration_ms
|
|
97
|
+
# Available from:
|
|
98
|
+
# - Ollama: total_duration (converted from nanoseconds)
|
|
99
|
+
#
|
|
100
|
+
# @return [Integer, nil]
|
|
101
|
+
attribute :duration_ms, :integer
|
|
102
|
+
|
|
103
|
+
# @!attribute [rw] provider_details
|
|
104
|
+
# Preserves provider-specific information that doesn't fit the normalized structure.
|
|
105
|
+
# Useful for debugging or provider-specific features.
|
|
106
|
+
#
|
|
107
|
+
# @return [Hash]
|
|
108
|
+
attribute :provider_details, default: -> { {} }
|
|
109
|
+
|
|
110
|
+
# Automatically calculates total_tokens if not provided.
|
|
111
|
+
#
|
|
112
|
+
# @param attributes [Hash]
|
|
113
|
+
# @option attributes [Integer] :input_tokens
|
|
114
|
+
# @option attributes [Integer] :output_tokens
|
|
115
|
+
# @option attributes [Integer] :total_tokens (calculated if not provided)
|
|
116
|
+
# @option attributes [Integer] :cached_tokens
|
|
117
|
+
# @option attributes [Integer] :reasoning_tokens
|
|
118
|
+
# @option attributes [Integer] :audio_tokens
|
|
119
|
+
# @option attributes [Integer] :cache_creation_tokens
|
|
120
|
+
# @option attributes [String] :service_tier
|
|
121
|
+
# @option attributes [Integer] :duration_ms
|
|
122
|
+
# @option attributes [Hash] :provider_details
|
|
123
|
+
def initialize(attributes = {})
|
|
124
|
+
super
|
|
125
|
+
# Calculate total_tokens if not provided
|
|
126
|
+
self.total_tokens ||= (input_tokens || 0) + (output_tokens || 0)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Sums all token counts from two Usage objects.
|
|
130
|
+
#
|
|
131
|
+
# @param other [Usage]
|
|
132
|
+
# @return [Usage]
|
|
133
|
+
#
|
|
134
|
+
# @example
|
|
135
|
+
# usage1 = Usage.new(input_tokens: 100, output_tokens: 50)
|
|
136
|
+
# usage2 = Usage.new(input_tokens: 75, output_tokens: 25)
|
|
137
|
+
# combined = usage1 + usage2
|
|
138
|
+
# combined.input_tokens #=> 175
|
|
139
|
+
# combined.output_tokens #=> 75
|
|
140
|
+
# combined.total_tokens #=> 250
|
|
141
|
+
def +(other)
|
|
142
|
+
return self unless other
|
|
143
|
+
|
|
144
|
+
self.class.new(
|
|
145
|
+
input_tokens: self.input_tokens + other.input_tokens,
|
|
146
|
+
output_tokens: self.output_tokens + other.output_tokens,
|
|
147
|
+
total_tokens: self.total_tokens + other.total_tokens,
|
|
148
|
+
cached_tokens: sum_optional(self.cached_tokens, other.cached_tokens),
|
|
149
|
+
cache_creation_tokens: sum_optional(self.cache_creation_tokens, other.cache_creation_tokens),
|
|
150
|
+
reasoning_tokens: sum_optional(self.reasoning_tokens, other.reasoning_tokens),
|
|
151
|
+
audio_tokens: sum_optional(self.audio_tokens, other.audio_tokens)
|
|
152
|
+
)
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# Creates a Usage object from OpenAI Chat Completion usage data.
|
|
156
|
+
#
|
|
157
|
+
# @param usage_hash [Hash]
|
|
158
|
+
# @return [Usage]
|
|
159
|
+
#
|
|
160
|
+
# @example
|
|
161
|
+
# Usage.from_openai_chat({
|
|
162
|
+
# "prompt_tokens" => 100,
|
|
163
|
+
# "completion_tokens" => 25,
|
|
164
|
+
# "total_tokens" => 125,
|
|
165
|
+
# "prompt_tokens_details" => { "cached_tokens" => 20 },
|
|
166
|
+
# "completion_tokens_details" => { "reasoning_tokens" => 3 }
|
|
167
|
+
# })
|
|
168
|
+
def self.from_openai_chat(usage_hash)
|
|
169
|
+
return nil unless usage_hash
|
|
170
|
+
|
|
171
|
+
usage = usage_hash.deep_symbolize_keys
|
|
172
|
+
prompt_details = usage[:prompt_tokens_details] || {}
|
|
173
|
+
completion_details = usage[:completion_tokens_details] || {}
|
|
174
|
+
|
|
175
|
+
audio_sum = [
|
|
176
|
+
prompt_details[:audio_tokens],
|
|
177
|
+
completion_details[:audio_tokens]
|
|
178
|
+
].compact.sum
|
|
179
|
+
|
|
180
|
+
new(
|
|
181
|
+
**usage.slice(:total_tokens),
|
|
182
|
+
input_tokens: usage[:prompt_tokens] || 0,
|
|
183
|
+
output_tokens: usage[:completion_tokens] || 0,
|
|
184
|
+
cached_tokens: prompt_details[:cached_tokens],
|
|
185
|
+
reasoning_tokens: completion_details[:reasoning_tokens],
|
|
186
|
+
audio_tokens: audio_sum > 0 ? audio_sum : nil,
|
|
187
|
+
provider_details: usage.slice(:prompt_tokens_details, :completion_tokens_details).compact
|
|
188
|
+
)
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# Creates a Usage object from OpenAI Embedding API usage data.
|
|
192
|
+
#
|
|
193
|
+
# @param usage_hash [Hash]
|
|
194
|
+
# @return [Usage]
|
|
195
|
+
#
|
|
196
|
+
# @example
|
|
197
|
+
# Usage.from_openai_embedding({
|
|
198
|
+
# "prompt_tokens" => 8,
|
|
199
|
+
# "total_tokens" => 8
|
|
200
|
+
# })
|
|
201
|
+
def self.from_openai_embedding(usage_hash)
|
|
202
|
+
return nil unless usage_hash
|
|
203
|
+
|
|
204
|
+
usage = usage_hash.deep_symbolize_keys
|
|
205
|
+
|
|
206
|
+
new(
|
|
207
|
+
**usage.slice(:total_tokens),
|
|
208
|
+
input_tokens: usage[:prompt_tokens] || 0,
|
|
209
|
+
output_tokens: 0, # Embeddings don't generate output tokens
|
|
210
|
+
provider_details: usage.except(:prompt_tokens, :total_tokens)
|
|
211
|
+
)
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
# Creates a Usage object from OpenAI Responses API usage data.
|
|
215
|
+
#
|
|
216
|
+
# @param usage_hash [Hash]
|
|
217
|
+
# @return [Usage]
|
|
218
|
+
#
|
|
219
|
+
# @example
|
|
220
|
+
# Usage.from_openai_responses({
|
|
221
|
+
# "input_tokens" => 150,
|
|
222
|
+
# "output_tokens" => 75,
|
|
223
|
+
# "total_tokens" => 225,
|
|
224
|
+
# "input_tokens_details" => { "cached_tokens" => 50 },
|
|
225
|
+
# "output_tokens_details" => { "reasoning_tokens" => 10 }
|
|
226
|
+
# })
|
|
227
|
+
def self.from_openai_responses(usage_hash)
|
|
228
|
+
return nil unless usage_hash
|
|
229
|
+
|
|
230
|
+
usage = usage_hash.deep_symbolize_keys
|
|
231
|
+
input_details = usage[:input_tokens_details] || {}
|
|
232
|
+
output_details = usage[:output_tokens_details] || {}
|
|
233
|
+
|
|
234
|
+
new(
|
|
235
|
+
**usage.slice(:input_tokens, :output_tokens, :total_tokens),
|
|
236
|
+
input_tokens: usage[:input_tokens] || 0,
|
|
237
|
+
output_tokens: usage[:output_tokens] || 0,
|
|
238
|
+
cached_tokens: input_details[:cached_tokens],
|
|
239
|
+
reasoning_tokens: output_details[:reasoning_tokens],
|
|
240
|
+
provider_details: usage.slice(:input_tokens_details, :output_tokens_details).compact
|
|
241
|
+
)
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
# Creates a Usage object from Anthropic usage data.
|
|
245
|
+
#
|
|
246
|
+
# @param usage_hash [Hash]
|
|
247
|
+
# @return [Usage]
|
|
248
|
+
#
|
|
249
|
+
# @example
|
|
250
|
+
# Usage.from_anthropic({
|
|
251
|
+
# "input_tokens" => 2095,
|
|
252
|
+
# "output_tokens" => 503,
|
|
253
|
+
# "cache_read_input_tokens" => 1500,
|
|
254
|
+
# "cache_creation_input_tokens" => 2051,
|
|
255
|
+
# "service_tier" => "standard"
|
|
256
|
+
# })
|
|
257
|
+
def self.from_anthropic(usage_hash)
|
|
258
|
+
return nil unless usage_hash
|
|
259
|
+
|
|
260
|
+
usage = usage_hash.deep_symbolize_keys
|
|
261
|
+
|
|
262
|
+
new(
|
|
263
|
+
**usage.slice(:input_tokens, :output_tokens, :service_tier),
|
|
264
|
+
input_tokens: usage[:input_tokens] || 0,
|
|
265
|
+
output_tokens: usage[:output_tokens] || 0,
|
|
266
|
+
cached_tokens: usage[:cache_read_input_tokens],
|
|
267
|
+
cache_creation_tokens: usage[:cache_creation_input_tokens],
|
|
268
|
+
provider_details: usage.slice(:cache_creation, :server_tool_use).compact
|
|
269
|
+
)
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
# Creates a Usage object from Ollama usage data.
|
|
273
|
+
#
|
|
274
|
+
# @param usage_hash [Hash]
|
|
275
|
+
# @return [Usage]
|
|
276
|
+
#
|
|
277
|
+
# @example
|
|
278
|
+
# Usage.from_ollama({
|
|
279
|
+
# "prompt_eval_count" => 50,
|
|
280
|
+
# "eval_count" => 25,
|
|
281
|
+
# "total_duration" => 5000000000,
|
|
282
|
+
# "load_duration" => 1000000000
|
|
283
|
+
# })
|
|
284
|
+
def self.from_ollama(usage_hash)
|
|
285
|
+
return nil unless usage_hash
|
|
286
|
+
|
|
287
|
+
usage = usage_hash.deep_symbolize_keys
|
|
288
|
+
|
|
289
|
+
new(
|
|
290
|
+
input_tokens: usage[:prompt_eval_count] || 0,
|
|
291
|
+
output_tokens: usage[:eval_count] || 0,
|
|
292
|
+
duration_ms: convert_nanoseconds_to_ms(usage[:total_duration]),
|
|
293
|
+
provider_details: {
|
|
294
|
+
load_duration_ms: convert_nanoseconds_to_ms(usage[:load_duration]),
|
|
295
|
+
prompt_eval_duration_ms: convert_nanoseconds_to_ms(usage[:prompt_eval_duration]),
|
|
296
|
+
eval_duration_ms: convert_nanoseconds_to_ms(usage[:eval_duration]),
|
|
297
|
+
tokens_per_second: calculate_tokens_per_second(usage[:eval_count], usage[:eval_duration])
|
|
298
|
+
}.compact
|
|
299
|
+
)
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
# Creates a Usage object from OpenRouter usage data.
|
|
303
|
+
#
|
|
304
|
+
# OpenRouter uses the same format as OpenAI Chat Completion.
|
|
305
|
+
#
|
|
306
|
+
# @param usage_hash [Hash]
|
|
307
|
+
# @return [Usage]
|
|
308
|
+
#
|
|
309
|
+
# @example
|
|
310
|
+
# Usage.from_openrouter({
|
|
311
|
+
# "prompt_tokens" => 14,
|
|
312
|
+
# "completion_tokens" => 4,
|
|
313
|
+
# "total_tokens" => 18
|
|
314
|
+
# })
|
|
315
|
+
def self.from_openrouter(usage_hash)
|
|
316
|
+
from_openai_chat(usage_hash)
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
# Auto-detects the provider format and creates a normalized Usage object.
|
|
320
|
+
#
|
|
321
|
+
# @note Detection is based on hash structure rather than native gem types
|
|
322
|
+
# because we cannot force-load all provider gems. This allows the framework
|
|
323
|
+
# to work with only the gems the user has installed.
|
|
324
|
+
#
|
|
325
|
+
# @param usage_hash [Hash]
|
|
326
|
+
# @return [Usage, nil]
|
|
327
|
+
#
|
|
328
|
+
# @example
|
|
329
|
+
# Usage.from_provider_usage(some_usage_hash)
|
|
330
|
+
def self.from_provider_usage(usage_hash)
|
|
331
|
+
return nil unless usage_hash.is_a?(Hash)
|
|
332
|
+
|
|
333
|
+
usage = usage_hash.deep_symbolize_keys
|
|
334
|
+
|
|
335
|
+
# Detect Ollama by presence of nanosecond duration fields
|
|
336
|
+
if usage.key?(:total_duration)
|
|
337
|
+
from_ollama(usage_hash)
|
|
338
|
+
# Detect Anthropic by presence of cache_creation or service_tier
|
|
339
|
+
elsif usage.key?(:cache_creation) || usage.key?(:service_tier)
|
|
340
|
+
from_anthropic(usage_hash)
|
|
341
|
+
# Detect OpenAI Responses API by input_tokens/output_tokens with details
|
|
342
|
+
elsif usage.key?(:input_tokens) && usage.key?(:input_tokens_details)
|
|
343
|
+
from_openai_responses(usage_hash)
|
|
344
|
+
# Detect OpenAI Chat/OpenRouter by prompt_tokens/completion_tokens
|
|
345
|
+
elsif usage.key?(:completion_tokens)
|
|
346
|
+
from_openai_chat(usage_hash)
|
|
347
|
+
# Detect OpenAI Embedding by prompt_tokens without completion_tokens
|
|
348
|
+
elsif usage.key?(:prompt_tokens)
|
|
349
|
+
from_openai_embedding(usage_hash)
|
|
350
|
+
# Default to raw initialization
|
|
351
|
+
else
|
|
352
|
+
new(usage_hash)
|
|
353
|
+
end
|
|
354
|
+
end
|
|
355
|
+
|
|
356
|
+
private
|
|
357
|
+
|
|
358
|
+
# @param a [Integer, nil]
|
|
359
|
+
# @param b [Integer, nil]
|
|
360
|
+
# @return [Integer, nil] nil if both inputs are nil
|
|
361
|
+
def sum_optional(a, b)
|
|
362
|
+
return nil if a.nil? && b.nil?
|
|
363
|
+
(a || 0) + (b || 0)
|
|
364
|
+
end
|
|
365
|
+
|
|
366
|
+
# @param nanoseconds [Integer, nil]
|
|
367
|
+
# @return [Integer, nil]
|
|
368
|
+
def self.convert_nanoseconds_to_ms(nanoseconds)
|
|
369
|
+
return nil unless nanoseconds
|
|
370
|
+
|
|
371
|
+
(nanoseconds / 1_000_000.0).round
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
# @param tokens [Integer, nil]
|
|
375
|
+
# @param duration_ns [Integer, nil]
|
|
376
|
+
# @return [Float, nil]
|
|
377
|
+
def self.calculate_tokens_per_second(tokens, duration_ns)
|
|
378
|
+
return nil unless tokens && duration_ns && duration_ns > 0
|
|
379
|
+
|
|
380
|
+
(tokens.to_f / (duration_ns / 1_000_000_000.0)).round(2)
|
|
381
|
+
end
|
|
382
|
+
end
|
|
383
|
+
end
|
|
384
|
+
end
|
|
385
|
+
end
|