From 46463966205fa2a05387f3b9c98a90ecf3f45412 Mon Sep 17 00:00:00 2001 From: Neil Jobbins <3514122+TheRealNeil@users.noreply.github.com> Date: Sat, 31 Jan 2026 18:55:57 -0800 Subject: [PATCH 1/2] Add Azure OpenAI provider with configuration options and tests --- lib/active_agent/concerns/provider.rb | 6 +- lib/active_agent/providers/azure/_types.rb | 5 + lib/active_agent/providers/azure/options.rb | 84 +++++++++ .../providers/azure_open_ai_provider.rb | 2 + .../providers/azure_openai_provider.rb | 2 + lib/active_agent/providers/azure_provider.rb | 133 +++++++++++++ .../providers/azureopenai_provider.rb | 2 + .../instructions_test/developer_message.yml | 84 +++++++++ .../multiple_instructions.yml | 86 +++++++++ .../instructions_test/single_instruction.yml | 89 +++++++++ .../instructions_test/system_with_user.yml | 88 +++++++++ .../common_format/messages_test/text_bare.yml | 85 +++++++++ .../messages_test/text_message_bare.yml | 83 +++++++++ .../messages_test/text_message_object.yml | 83 +++++++++ .../messages_test/text_messages_object.yml | 84 +++++++++ .../tools_test/multiple_tools.yml | 172 +++++++++++++++++ .../tools_test/tool_choice_auto.yml | 166 +++++++++++++++++ .../tools_test/tool_choice_required.yml | 169 +++++++++++++++++ .../tools_test/tool_with_parameters.yml | 171 +++++++++++++++++ .../azure/response_test/hello_world.yml | 82 ++++++++ .../azure/response_test/simple_prompt.yml | 84 +++++++++ .../integration/azure/response_test/usage.yml | 84 +++++++++ .../azure/common_format/instructions_test.rb | 103 +++++++++++ .../azure/common_format/messages_test.rb | 127 +++++++++++++ .../azure/common_format/tools_test.rb | 175 ++++++++++++++++++ test/integration/azure/response_test.rb | 76 ++++++++ test/providers/azure/azure_provider_test.rb | 72 +++++++ test/providers/azure/options_test.rb | 143 ++++++++++++++ test/providers/azure/provider_loading_test.rb | 47 +++++ test/test_helper.rb | 18 ++ 30 files changed, 2603 insertions(+), 2 deletions(-) create mode 100644 lib/active_agent/providers/azure/_types.rb create mode 100644 lib/active_agent/providers/azure/options.rb create mode 100644 lib/active_agent/providers/azure_open_ai_provider.rb create mode 100644 lib/active_agent/providers/azure_openai_provider.rb create mode 100644 lib/active_agent/providers/azure_provider.rb create mode 100644 lib/active_agent/providers/azureopenai_provider.rb create mode 100644 test/fixtures/vcr_cassettes/integration/azure/common_format/instructions_test/developer_message.yml create mode 100644 test/fixtures/vcr_cassettes/integration/azure/common_format/instructions_test/multiple_instructions.yml create mode 100644 test/fixtures/vcr_cassettes/integration/azure/common_format/instructions_test/single_instruction.yml create mode 100644 test/fixtures/vcr_cassettes/integration/azure/common_format/instructions_test/system_with_user.yml create mode 100644 test/fixtures/vcr_cassettes/integration/azure/common_format/messages_test/text_bare.yml create mode 100644 test/fixtures/vcr_cassettes/integration/azure/common_format/messages_test/text_message_bare.yml create mode 100644 test/fixtures/vcr_cassettes/integration/azure/common_format/messages_test/text_message_object.yml create mode 100644 test/fixtures/vcr_cassettes/integration/azure/common_format/messages_test/text_messages_object.yml create mode 100644 test/fixtures/vcr_cassettes/integration/azure/common_format/tools_test/multiple_tools.yml create mode 100644 test/fixtures/vcr_cassettes/integration/azure/common_format/tools_test/tool_choice_auto.yml create mode 100644 test/fixtures/vcr_cassettes/integration/azure/common_format/tools_test/tool_choice_required.yml create mode 100644 test/fixtures/vcr_cassettes/integration/azure/common_format/tools_test/tool_with_parameters.yml create mode 100644 test/fixtures/vcr_cassettes/integration/azure/response_test/hello_world.yml create mode 100644 test/fixtures/vcr_cassettes/integration/azure/response_test/simple_prompt.yml create mode 100644 test/fixtures/vcr_cassettes/integration/azure/response_test/usage.yml create mode 100644 test/integration/azure/common_format/instructions_test.rb create mode 100644 test/integration/azure/common_format/messages_test.rb create mode 100644 test/integration/azure/common_format/tools_test.rb create mode 100644 test/integration/azure/response_test.rb create mode 100644 test/providers/azure/azure_provider_test.rb create mode 100644 test/providers/azure/options_test.rb create mode 100644 test/providers/azure/provider_loading_test.rb diff --git a/lib/active_agent/concerns/provider.rb b/lib/active_agent/concerns/provider.rb index 53a824b2..1ef7abd9 100644 --- a/lib/active_agent/concerns/provider.rb +++ b/lib/active_agent/concerns/provider.rb @@ -9,8 +9,10 @@ module Provider # "Your tacky and I hate you" - Billy, https://youtu.be/dsheboxJNgQ?si=tzDlJ7sdSxM4RjSD PROVIDER_SERVICE_NAMES_REMAPS = { - "Openrouter" => "OpenRouter", - "Openai" => "OpenAI" + "Openrouter" => "OpenRouter", + "Openai" => "OpenAI", + "AzureOpenai" => "AzureOpenAI", + "Azureopenai" => "AzureOpenAI" } included do diff --git a/lib/active_agent/providers/azure/_types.rb b/lib/active_agent/providers/azure/_types.rb new file mode 100644 index 00000000..728a9b1e --- /dev/null +++ b/lib/active_agent/providers/azure/_types.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +require_relative "options" +require_relative "../open_ai/chat/_types" +require_relative "../open_ai/embedding/_types" diff --git a/lib/active_agent/providers/azure/options.rb b/lib/active_agent/providers/azure/options.rb new file mode 100644 index 00000000..35a0b9b0 --- /dev/null +++ b/lib/active_agent/providers/azure/options.rb @@ -0,0 +1,84 @@ +# frozen_string_literal: true + +require_relative "../open_ai/options" + +module ActiveAgent + module Providers + module Azure + # Configuration options for Azure OpenAI Service. + # + # Azure OpenAI uses a different authentication and endpoint structure than standard OpenAI: + # - Endpoint: https://{resource}.openai.azure.com/openai/deployments/{deployment}/ + # - Authentication: api-key header instead of Authorization: Bearer + # - API Version: Required query parameter + # + # @example Configuration + # options = Azure::Options.new( + # api_key: ENV["AZURE_OPENAI_API_KEY"], + # azure_resource: "mycompany", + # deployment_id: "gpt-4-deployment", + # api_version: "2024-10-21" + # ) + class Options < ActiveAgent::Providers::OpenAI::Options + DEFAULT_API_VERSION = "2024-10-21" + + attribute :azure_resource, :string + attribute :deployment_id, :string + attribute :api_version, :string, fallback: DEFAULT_API_VERSION + + validates :azure_resource, presence: true + validates :deployment_id, presence: true + + def initialize(kwargs = {}) + kwargs = kwargs.deep_symbolize_keys if kwargs.respond_to?(:deep_symbolize_keys) + kwargs[:api_version] ||= resolve_api_version(kwargs) + super(kwargs) + end + + # Returns Azure-specific headers for authentication. + # + # Azure uses api-key header instead of Authorization: Bearer. + # + # @return [Hash] headers including api-key + def extra_headers + { "api-key" => api_key } + end + + # Returns Azure-specific query parameters. + # + # Azure requires api-version as a query parameter. + # + # @return [Hash] query parameters including api-version + def extra_query + { "api-version" => api_version } + end + + # Builds the base URL for Azure OpenAI API requests. + # + # @return [String] the Azure OpenAI endpoint URL + def base_url + "https://#{azure_resource}.openai.azure.com/openai/deployments/#{deployment_id}" + end + + private + + def resolve_api_key(kwargs) + kwargs[:api_key] || + kwargs[:access_token] || + ENV["AZURE_OPENAI_API_KEY"] || + ENV["AZURE_OPENAI_ACCESS_TOKEN"] + end + + def resolve_api_version(kwargs) + kwargs[:api_version] || + ENV["AZURE_OPENAI_API_VERSION"] || + DEFAULT_API_VERSION + end + + # Not used as part of Azure OpenAI + def resolve_organization_id(_settings) = nil + def resolve_project_id(_settings) = nil + end + end + end +end diff --git a/lib/active_agent/providers/azure_open_ai_provider.rb b/lib/active_agent/providers/azure_open_ai_provider.rb new file mode 100644 index 00000000..5a30fcc5 --- /dev/null +++ b/lib/active_agent/providers/azure_open_ai_provider.rb @@ -0,0 +1,2 @@ +# Azure OpenAI, alias for AzureOpenAI service name resolution +require_relative "azure_provider" diff --git a/lib/active_agent/providers/azure_openai_provider.rb b/lib/active_agent/providers/azure_openai_provider.rb new file mode 100644 index 00000000..ede5051c --- /dev/null +++ b/lib/active_agent/providers/azure_openai_provider.rb @@ -0,0 +1,2 @@ +# Azure OpenAI, alias for :azure_openai provider reference +require_relative "azure_provider" diff --git a/lib/active_agent/providers/azure_provider.rb b/lib/active_agent/providers/azure_provider.rb new file mode 100644 index 00000000..2e6ca1fa --- /dev/null +++ b/lib/active_agent/providers/azure_provider.rb @@ -0,0 +1,133 @@ +require_relative "_base_provider" + +require_gem!(:openai, __FILE__) + +require_relative "open_ai_provider" +require_relative "azure/_types" + +module ActiveAgent + module Providers + # Provider for Azure OpenAI Service via OpenAI-compatible API. + # + # Azure OpenAI uses the same API structure as OpenAI but with different + # authentication (api-key header) and endpoint configuration (resource + deployment). + # + # @example Configuration in active_agent.yml + # azure_openai: + # service: "AzureOpenAI" + # api_key: <%= ENV["AZURE_OPENAI_API_KEY"] %> + # azure_resource: "mycompany" + # deployment_id: "gpt-4-deployment" + # api_version: "2024-10-21" + # + # @see OpenAI::ChatProvider + class AzureProvider < OpenAI::ChatProvider + # @return [String] + def self.service_name + "AzureOpenAI" + end + + # @return [Class] + def self.options_klass + Azure::Options + end + + # @return [ActiveModel::Type::Value] + def self.prompt_request_type + OpenAI::Chat::RequestType.new + end + + # @return [ActiveModel::Type::Value] + def self.embed_request_type + OpenAI::Embedding::RequestType.new + end + + # Returns a configured Azure OpenAI client. + # + # Uses a custom client subclass that handles Azure-specific authentication + # (api-key header instead of Authorization: Bearer). + # + # @return [AzureClient] the configured Azure client + def client + @client ||= AzureClient.new( + api_key: options.api_key, + base_url: options.base_url, + api_version: options.api_version, + max_retries: options.max_retries, + timeout: options.timeout, + initial_retry_delay: options.initial_retry_delay, + max_retry_delay: options.max_retry_delay + ) + end + + # Custom OpenAI client for Azure OpenAI Service. + # + # Azure uses different authentication headers (api-key instead of Authorization: Bearer) + # and requires api-version as a query parameter on all requests. + class AzureClient < ::OpenAI::Client + # @return [String] + attr_reader :api_version + + # Creates a new Azure OpenAI client. + # + # @param api_key [String] Azure OpenAI API key + # @param base_url [String] Azure endpoint URL + # @param api_version [String] API version (e.g., "2024-10-21") + # @param max_retries [Integer] Maximum retry attempts + # @param timeout [Float] Request timeout in seconds + # @param initial_retry_delay [Float] Initial delay between retries + # @param max_retry_delay [Float] Maximum delay between retries + def initialize( + api_key:, + base_url:, + api_version:, + max_retries: self.class::DEFAULT_MAX_RETRIES, + timeout: self.class::DEFAULT_TIMEOUT_IN_SECONDS, + initial_retry_delay: self.class::DEFAULT_INITIAL_RETRY_DELAY, + max_retry_delay: self.class::DEFAULT_MAX_RETRY_DELAY + ) + @api_version = api_version + + super( + api_key: api_key, + base_url: base_url, + max_retries: max_retries, + timeout: timeout, + initial_retry_delay: initial_retry_delay, + max_retry_delay: max_retry_delay + ) + end + + private + + # Azure uses api-key header instead of Authorization: Bearer. + # + # @return [Hash{String=>String}] + def auth_headers + return {} if @api_key.nil? + + { "api-key" => @api_key } + end + + # Builds request with Azure-specific query parameters. + # + # Injects api-version into extra_query for all requests. + # + # @param req [Hash] Request parameters + # @param opts [Hash] Request options + # @return [Hash] Built request + def build_request(req, opts) + # Inject api-version into extra_query + opts = opts.dup + opts[:extra_query] = (opts[:extra_query] || {}).merge("api-version" => @api_version) + + super(req, opts) + end + end + end + + # Aliases for provider loading with different service name variations + AzureOpenAIProvider = AzureProvider + AzureOpenaiProvider = AzureProvider + end +end diff --git a/lib/active_agent/providers/azureopenai_provider.rb b/lib/active_agent/providers/azureopenai_provider.rb new file mode 100644 index 00000000..8635a669 --- /dev/null +++ b/lib/active_agent/providers/azureopenai_provider.rb @@ -0,0 +1,2 @@ +# Azure OpenAI, alternative naming for consistency with other provider aliases +require_relative "azure_provider" diff --git a/test/fixtures/vcr_cassettes/integration/azure/common_format/instructions_test/developer_message.yml b/test/fixtures/vcr_cassettes/integration/azure/common_format/instructions_test/developer_message.yml new file mode 100644 index 00000000..94b7f1bb --- /dev/null +++ b/test/fixtures/vcr_cassettes/integration/azure/common_format/instructions_test/developer_message.yml @@ -0,0 +1,84 @@ +--- +http_interactions: +- request: + method: post + uri: https://AZURE_RESOURCE.openai.azure.com/openai/deployments/AZURE_DEPLOYMENT/chat/completions?api-version=2025-01-01-preview + body: + encoding: UTF-8 + string: '{"model":"gpt-4","messages":[{"role":"developer","content":"Respond + only with JSON."},{"role":"user","content":"List 3 colours"}]}' + headers: + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - application/json + User-Agent: + - ActiveAgent::Providers::AzureProvider::AzureClient/Ruby 0.43.0 + Host: + - AZURE_RESOURCE.openai.azure.com + X-Stainless-Arch: + - arm64 + X-Stainless-Lang: + - ruby + X-Stainless-Os: + - MacOS + X-Stainless-Package-Version: + - 0.43.0 + X-Stainless-Runtime: + - ruby + X-Stainless-Runtime-Version: + - 3.3.8 + Content-Type: + - application/json + Api-Key: + - AZURE_API_KEY + X-Stainless-Retry-Count: + - '0' + X-Stainless-Timeout: + - '600.0' + Content-Length: + - '130' + response: + status: + code: 200 + message: OK + headers: + Content-Length: + - '1249' + Content-Type: + - application/json + Apim-Request-Id: + - 82c4625a-7066-43e1-8b8c-82a14aef1c17 + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + X-Content-Type-Options: + - nosniff + X-Ms-Region: + - West Europe + X-Ratelimit-Remaining-Requests: + - '43' + X-Ratelimit-Limit-Requests: + - '50' + X-Ratelimit-Remaining-Tokens: + - '49895' + X-Ratelimit-Limit-Tokens: + - '50000' + Azureml-Model-Session: + - d20260129130029-90b0c3ab1d00 + X-Accel-Buffering: + - 'no' + X-Ms-Rai-Invoked: + - 'true' + X-Request-Id: + - 0e26e132-b2a3-4c16-bf24-fc1cd3aa500f + X-Ms-Deployment-Name: + - AZURE_DEPLOYMENT + Date: + - Sun, 01 Feb 2026 02:49:49 GMT + body: + encoding: UTF-8 + string: '{"choices":[{"content_filter_results":{"hate":{"filtered":false,"severity":"safe"},"protected_material_code":{"filtered":false,"detected":false},"protected_material_text":{"filtered":false,"detected":false},"self_harm":{"filtered":false,"severity":"safe"},"sexual":{"filtered":false,"severity":"safe"},"violence":{"filtered":false,"severity":"safe"}},"finish_reason":"stop","index":0,"logprobs":null,"message":{"annotations":[],"content":"[\n \"red\",\n \"blue\",\n \"green\"\n]","refusal":null,"role":"assistant"}}],"created":1769914189,"id":"chatcmpl-D4HozcdGsuf3k1PwnGE7FJXGSXzYL","model":"AZURE_DEPLOYMENT-2025-04-14","object":"chat.completion","prompt_filter_results":[{"prompt_index":0,"content_filter_results":{"hate":{"filtered":false,"severity":"safe"},"jailbreak":{"filtered":false,"detected":false},"self_harm":{"filtered":false,"severity":"safe"},"sexual":{"filtered":false,"severity":"safe"},"violence":{"filtered":false,"severity":"safe"}}}],"system_fingerprint":"fp_f99638a8d7","usage":{"completion_tokens":15,"completion_tokens_details":{"accepted_prediction_tokens":0,"audio_tokens":0,"reasoning_tokens":0,"rejected_prediction_tokens":0},"prompt_tokens":20,"prompt_tokens_details":{"audio_tokens":0,"cached_tokens":0},"total_tokens":35}} + + ' + recorded_at: Sun, 01 Feb 2026 02:49:50 GMT +recorded_with: VCR 6.4.0 diff --git a/test/fixtures/vcr_cassettes/integration/azure/common_format/instructions_test/multiple_instructions.yml b/test/fixtures/vcr_cassettes/integration/azure/common_format/instructions_test/multiple_instructions.yml new file mode 100644 index 00000000..d66d632f --- /dev/null +++ b/test/fixtures/vcr_cassettes/integration/azure/common_format/instructions_test/multiple_instructions.yml @@ -0,0 +1,86 @@ +--- +http_interactions: +- request: + method: post + uri: https://AZURE_RESOURCE.openai.azure.com/openai/deployments/AZURE_DEPLOYMENT/chat/completions?api-version=2025-01-01-preview + body: + encoding: UTF-8 + string: '{"model":"gpt-4","messages":[{"role":"developer","content":[{"type":"text","text":"You + are a helpful assistant."},{"type":"text","text":"Always respond in exactly + 3 words."}]},{"role":"user","content":"What is 2+2?"}]}' + headers: + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - application/json + User-Agent: + - ActiveAgent::Providers::AzureProvider::AzureClient/Ruby 0.43.0 + Host: + - AZURE_RESOURCE.openai.azure.com + X-Stainless-Arch: + - arm64 + X-Stainless-Lang: + - ruby + X-Stainless-Os: + - MacOS + X-Stainless-Package-Version: + - 0.43.0 + X-Stainless-Runtime: + - ruby + X-Stainless-Runtime-Version: + - 3.3.8 + Content-Type: + - application/json + Api-Key: + - AZURE_API_KEY + X-Stainless-Retry-Count: + - '0' + X-Stainless-Timeout: + - '600.0' + Content-Length: + - '218' + response: + status: + code: 200 + message: OK + headers: + Content-Length: + - '1222' + Content-Type: + - application/json + Apim-Request-Id: + - 2fb42ae7-5e95-4417-aad4-fcb5c4a12d49 + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + X-Content-Type-Options: + - nosniff + X-Ms-Region: + - West Europe + X-Ratelimit-Remaining-Requests: + - '42' + X-Ratelimit-Limit-Requests: + - '50' + X-Ratelimit-Remaining-Tokens: + - '49874' + X-Ratelimit-Limit-Tokens: + - '50000' + Azureml-Model-Session: + - d20260129130029-90b0c3ab1d00 + X-Accel-Buffering: + - 'no' + X-Ms-Rai-Invoked: + - 'true' + X-Request-Id: + - d78e25c9-aa07-4c94-8706-b2459777304d + X-Ms-Deployment-Name: + - AZURE_DEPLOYMENT + Date: + - Sun, 01 Feb 2026 02:49:51 GMT + body: + encoding: UTF-8 + string: '{"choices":[{"content_filter_results":{"hate":{"filtered":false,"severity":"safe"},"protected_material_code":{"filtered":false,"detected":false},"protected_material_text":{"filtered":false,"detected":false},"self_harm":{"filtered":false,"severity":"safe"},"sexual":{"filtered":false,"severity":"safe"},"violence":{"filtered":false,"severity":"safe"}},"finish_reason":"stop","index":0,"logprobs":null,"message":{"annotations":[],"content":"Four, + of course!","refusal":null,"role":"assistant"}}],"created":1769914191,"id":"chatcmpl-D4Hp1FL0Xdo43qceVzeKFGj0IynL9","model":"AZURE_DEPLOYMENT-2025-04-14","object":"chat.completion","prompt_filter_results":[{"prompt_index":0,"content_filter_results":{"hate":{"filtered":false,"severity":"safe"},"jailbreak":{"filtered":false,"detected":false},"self_harm":{"filtered":false,"severity":"safe"},"sexual":{"filtered":false,"severity":"safe"},"violence":{"filtered":false,"severity":"safe"}}}],"system_fingerprint":"fp_f99638a8d7","usage":{"completion_tokens":6,"completion_tokens_details":{"accepted_prediction_tokens":0,"audio_tokens":0,"reasoning_tokens":0,"rejected_prediction_tokens":0},"prompt_tokens":32,"prompt_tokens_details":{"audio_tokens":0,"cached_tokens":0},"total_tokens":38}} + + ' + recorded_at: Sun, 01 Feb 2026 02:49:52 GMT +recorded_with: VCR 6.4.0 diff --git a/test/fixtures/vcr_cassettes/integration/azure/common_format/instructions_test/single_instruction.yml b/test/fixtures/vcr_cassettes/integration/azure/common_format/instructions_test/single_instruction.yml new file mode 100644 index 00000000..3ab1c62f --- /dev/null +++ b/test/fixtures/vcr_cassettes/integration/azure/common_format/instructions_test/single_instruction.yml @@ -0,0 +1,89 @@ +--- +http_interactions: +- request: + method: post + uri: https://AZURE_RESOURCE.openai.azure.com/openai/deployments/AZURE_DEPLOYMENT/chat/completions?api-version=2025-01-01-preview + body: + encoding: UTF-8 + string: '{"model":"gpt-4","messages":[{"role":"developer","content":"You are + a helpful assistant that speaks like a pirate."},{"role":"user","content":"What + is the capital of France?"}]}' + headers: + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - application/json + User-Agent: + - ActiveAgent::Providers::AzureProvider::AzureClient/Ruby 0.43.0 + Host: + - AZURE_RESOURCE.openai.azure.com + X-Stainless-Arch: + - arm64 + X-Stainless-Lang: + - ruby + X-Stainless-Os: + - MacOS + X-Stainless-Package-Version: + - 0.43.0 + X-Stainless-Runtime: + - ruby + X-Stainless-Runtime-Version: + - 3.3.8 + Content-Type: + - application/json + Api-Key: + - AZURE_API_KEY + X-Stainless-Retry-Count: + - '0' + X-Stainless-Timeout: + - '600.0' + Content-Length: + - '177' + response: + status: + code: 200 + message: OK + headers: + Content-Length: + - '1190' + Content-Type: + - application/json + Apim-Request-Id: + - d5ce5d6c-6f80-4772-963f-835e52e7146d + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + X-Content-Type-Options: + - nosniff + X-Ms-Region: + - West Europe + X-Ratelimit-Remaining-Requests: + - '44' + X-Ratelimit-Limit-Requests: + - '50' + X-Ratelimit-Remaining-Tokens: + - '49905' + X-Ratelimit-Limit-Tokens: + - '50000' + Azureml-Model-Session: + - d20260129130029-90b0c3ab1d00 + X-Accel-Buffering: + - 'no' + X-Ms-Rai-Invoked: + - 'true' + X-Request-Id: + - 7812cb84-6e31-4f1e-9be7-a8c1c24270ea + X-Ms-Deployment-Name: + - AZURE_DEPLOYMENT + Date: + - Sun, 01 Feb 2026 02:49:47 GMT + body: + encoding: UTF-8 + string: '{"choices":[{"content_filter_result":{"error":{"code":"content_filter_error","message":"The + contents are not filtered"}},"content_filter_results":{},"finish_reason":"stop","index":0,"logprobs":null,"message":{"annotations":[],"content":"Arrr + matey! The capital of France be Paris, a grand city known fer its fancy lights + and delicious vittles. Raise yer spyglass and set yer course fer Paris, if + adventure be what ye seek!","refusal":null,"role":"assistant"}}],"created":1769914186,"id":"chatcmpl-D4HowwXnVc2QqZXQSI2eaHBnZNWE5","model":"AZURE_DEPLOYMENT-2025-04-14","object":"chat.completion","prompt_filter_results":[{"prompt_index":0,"content_filter_results":{"hate":{"filtered":false,"severity":"safe"},"jailbreak":{"filtered":false,"detected":false},"self_harm":{"filtered":false,"severity":"safe"},"sexual":{"filtered":false,"severity":"safe"},"violence":{"filtered":false,"severity":"safe"}}}],"system_fingerprint":"fp_f99638a8d7","usage":{"completion_tokens":44,"completion_tokens_details":{"accepted_prediction_tokens":0,"audio_tokens":0,"reasoning_tokens":0,"rejected_prediction_tokens":0},"prompt_tokens":29,"prompt_tokens_details":{"audio_tokens":0,"cached_tokens":0},"total_tokens":73}} + + ' + recorded_at: Sun, 01 Feb 2026 02:49:48 GMT +recorded_with: VCR 6.4.0 diff --git a/test/fixtures/vcr_cassettes/integration/azure/common_format/instructions_test/system_with_user.yml b/test/fixtures/vcr_cassettes/integration/azure/common_format/instructions_test/system_with_user.yml new file mode 100644 index 00000000..d8eb1de3 --- /dev/null +++ b/test/fixtures/vcr_cassettes/integration/azure/common_format/instructions_test/system_with_user.yml @@ -0,0 +1,88 @@ +--- +http_interactions: +- request: + method: post + uri: https://AZURE_RESOURCE.openai.azure.com/openai/deployments/AZURE_DEPLOYMENT/chat/completions?api-version=2025-01-01-preview + body: + encoding: UTF-8 + string: '{"model":"gpt-4","messages":[{"role":"system","content":"You are a + code reviewer. Be concise."},{"role":"user","content":"Review this: puts ''hello''"}]}' + headers: + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - application/json + User-Agent: + - ActiveAgent::Providers::AzureProvider::AzureClient/Ruby 0.43.0 + Host: + - AZURE_RESOURCE.openai.azure.com + X-Stainless-Arch: + - arm64 + X-Stainless-Lang: + - ruby + X-Stainless-Os: + - MacOS + X-Stainless-Package-Version: + - 0.43.0 + X-Stainless-Runtime: + - ruby + X-Stainless-Runtime-Version: + - 3.3.8 + Content-Type: + - application/json + Api-Key: + - AZURE_API_KEY + X-Stainless-Retry-Count: + - '0' + X-Stainless-Timeout: + - '600.0' + Content-Length: + - '151' + response: + status: + code: 200 + message: OK + headers: + Content-Length: + - '1498' + Content-Type: + - application/json + Apim-Request-Id: + - 948e02fb-2ca0-4211-8911-dc9b0d096ee4 + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + X-Content-Type-Options: + - nosniff + X-Ms-Region: + - West Europe + X-Ratelimit-Remaining-Requests: + - '45' + X-Ratelimit-Limit-Requests: + - '50' + X-Ratelimit-Remaining-Tokens: + - '49927' + X-Ratelimit-Limit-Tokens: + - '50000' + Azureml-Model-Session: + - d20260129130029-90b0c3ab1d00 + X-Accel-Buffering: + - 'no' + X-Ms-Rai-Invoked: + - 'true' + X-Request-Id: + - ee68197d-5fbc-42b0-9982-2f431c2ca08a + X-Ms-Deployment-Name: + - AZURE_DEPLOYMENT + Date: + - Sun, 01 Feb 2026 02:49:45 GMT + body: + encoding: UTF-8 + string: '{"choices":[{"content_filter_results":{"hate":{"filtered":false,"severity":"safe"},"protected_material_code":{"filtered":false,"detected":false},"protected_material_text":{"filtered":false,"detected":false},"self_harm":{"filtered":false,"severity":"safe"},"sexual":{"filtered":false,"severity":"safe"},"violence":{"filtered":false,"severity":"safe"}},"finish_reason":"stop","index":0,"logprobs":null,"message":{"annotations":[],"content":"This + code is clear and functional for printing text in Ruby.\n\n**Suggestions:**\n- + If this is meant as a script or example, it''s fine.\n- For more complex applications, + consider adding context or using logging. \n- If you want no newline, use + `print` instead of `puts`.\n\nNo issues found.","refusal":null,"role":"assistant"}}],"created":1769914184,"id":"chatcmpl-D4Hou4C5A9aEnfHDeQk1FNJq1rIot","model":"AZURE_DEPLOYMENT-2025-04-14","object":"chat.completion","prompt_filter_results":[{"prompt_index":0,"content_filter_results":{"hate":{"filtered":false,"severity":"safe"},"jailbreak":{"filtered":false,"detected":false},"self_harm":{"filtered":false,"severity":"safe"},"sexual":{"filtered":false,"severity":"safe"},"violence":{"filtered":false,"severity":"safe"}}}],"system_fingerprint":"fp_f99638a8d7","usage":{"completion_tokens":65,"completion_tokens_details":{"accepted_prediction_tokens":0,"audio_tokens":0,"reasoning_tokens":0,"rejected_prediction_tokens":0},"prompt_tokens":27,"prompt_tokens_details":{"audio_tokens":0,"cached_tokens":0},"total_tokens":92}} + + ' + recorded_at: Sun, 01 Feb 2026 02:49:46 GMT +recorded_with: VCR 6.4.0 diff --git a/test/fixtures/vcr_cassettes/integration/azure/common_format/messages_test/text_bare.yml b/test/fixtures/vcr_cassettes/integration/azure/common_format/messages_test/text_bare.yml new file mode 100644 index 00000000..f8106645 --- /dev/null +++ b/test/fixtures/vcr_cassettes/integration/azure/common_format/messages_test/text_bare.yml @@ -0,0 +1,85 @@ +--- +http_interactions: +- request: + method: post + uri: https://AZURE_RESOURCE.openai.azure.com/openai/deployments/AZURE_DEPLOYMENT/chat/completions?api-version=2025-01-01-preview + body: + encoding: UTF-8 + string: '{"model":"gpt-4","messages":[{"role":"user","content":"What is the + capital of France?"}]}' + headers: + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - application/json + User-Agent: + - ActiveAgent::Providers::AzureProvider::AzureClient/Ruby 0.43.0 + Host: + - AZURE_RESOURCE.openai.azure.com + X-Stainless-Arch: + - arm64 + X-Stainless-Lang: + - ruby + X-Stainless-Os: + - MacOS + X-Stainless-Package-Version: + - 0.43.0 + X-Stainless-Runtime: + - ruby + X-Stainless-Runtime-Version: + - 3.3.8 + Content-Type: + - application/json + Api-Key: + - AZURE_API_KEY + X-Stainless-Retry-Count: + - '0' + X-Stainless-Timeout: + - '600.0' + Content-Length: + - '89' + response: + status: + code: 200 + message: OK + headers: + Content-Length: + - '1237' + Content-Type: + - application/json + Apim-Request-Id: + - 3bc79bfe-3653-43c0-aa03-c2fca119526b + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + X-Content-Type-Options: + - nosniff + X-Ms-Region: + - West Europe + X-Ratelimit-Remaining-Requests: + - '47' + X-Ratelimit-Limit-Requests: + - '50' + X-Ratelimit-Remaining-Tokens: + - '49955' + X-Ratelimit-Limit-Tokens: + - '50000' + Azureml-Model-Session: + - d20260129130029-90b0c3ab1d00 + X-Accel-Buffering: + - 'no' + X-Ms-Rai-Invoked: + - 'true' + X-Request-Id: + - 1936d9cc-4a09-4d5a-9081-a43a508675c2 + X-Ms-Deployment-Name: + - AZURE_DEPLOYMENT + Date: + - Sun, 01 Feb 2026 02:49:39 GMT + body: + encoding: UTF-8 + string: '{"choices":[{"content_filter_results":{"hate":{"filtered":false,"severity":"safe"},"protected_material_code":{"filtered":false,"detected":false},"protected_material_text":{"filtered":false,"detected":false},"self_harm":{"filtered":false,"severity":"safe"},"sexual":{"filtered":false,"severity":"safe"},"violence":{"filtered":false,"severity":"safe"}},"finish_reason":"stop","index":0,"logprobs":null,"message":{"annotations":[],"content":"The + capital of France is Paris.","refusal":null,"role":"assistant"}}],"created":1769914179,"id":"chatcmpl-D4HopW7aeg95DpGCMyhObUD1RpQbS","model":"AZURE_DEPLOYMENT-2025-04-14","object":"chat.completion","prompt_filter_results":[{"prompt_index":0,"content_filter_results":{"hate":{"filtered":false,"severity":"safe"},"jailbreak":{"filtered":false,"detected":false},"self_harm":{"filtered":false,"severity":"safe"},"sexual":{"filtered":false,"severity":"safe"},"violence":{"filtered":false,"severity":"safe"}}}],"system_fingerprint":"fp_f99638a8d7","usage":{"completion_tokens":8,"completion_tokens_details":{"accepted_prediction_tokens":0,"audio_tokens":0,"reasoning_tokens":0,"rejected_prediction_tokens":0},"prompt_tokens":14,"prompt_tokens_details":{"audio_tokens":0,"cached_tokens":0},"total_tokens":22}} + + ' + recorded_at: Sun, 01 Feb 2026 02:49:40 GMT +recorded_with: VCR 6.4.0 diff --git a/test/fixtures/vcr_cassettes/integration/azure/common_format/messages_test/text_message_bare.yml b/test/fixtures/vcr_cassettes/integration/azure/common_format/messages_test/text_message_bare.yml new file mode 100644 index 00000000..ffbf0f1a --- /dev/null +++ b/test/fixtures/vcr_cassettes/integration/azure/common_format/messages_test/text_message_bare.yml @@ -0,0 +1,83 @@ +--- +http_interactions: +- request: + method: post + uri: https://AZURE_RESOURCE.openai.azure.com/openai/deployments/AZURE_DEPLOYMENT/chat/completions?api-version=2025-01-01-preview + body: + encoding: UTF-8 + string: '{"model":"gpt-4","messages":[{"role":"user","content":"Explain quantum + computing in simple terms."}]}' + headers: + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - application/json + User-Agent: + - ActiveAgent::Providers::AzureProvider::AzureClient/Ruby 0.43.0 + Host: + - AZURE_RESOURCE.openai.azure.com + X-Stainless-Arch: + - arm64 + X-Stainless-Lang: + - ruby + X-Stainless-Os: + - MacOS + X-Stainless-Package-Version: + - 0.43.0 + X-Stainless-Runtime: + - ruby + X-Stainless-Runtime-Version: + - 3.3.8 + Content-Type: + - application/json + Api-Key: + - AZURE_API_KEY + X-Stainless-Retry-Count: + - '0' + X-Stainless-Timeout: + - '600.0' + Content-Length: + - '101' + response: + status: + code: 200 + message: OK + headers: + Content-Length: + - '2312' + Content-Type: + - application/json + Apim-Request-Id: + - 885cc1dd-d44e-44d7-822e-fd2d78127e8d + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + X-Content-Type-Options: + - nosniff + X-Ms-Region: + - West Europe + X-Ratelimit-Remaining-Requests: + - '46' + X-Ratelimit-Limit-Requests: + - '50' + X-Ratelimit-Remaining-Tokens: + - '49944' + X-Ratelimit-Limit-Tokens: + - '50000' + Azureml-Model-Session: + - d20260129130029-90b0c3ab1d00 + X-Accel-Buffering: + - 'no' + X-Ms-Rai-Invoked: + - 'true' + X-Request-Id: + - d956f59c-d9a1-4f88-a77a-bcdb90a8fdad + X-Ms-Deployment-Name: + - AZURE_DEPLOYMENT + Date: + - Sun, 01 Feb 2026 02:49:43 GMT + body: + encoding: ASCII-8BIT + string: !binary |- + eyJjaG9pY2VzIjpbeyJjb250ZW50X2ZpbHRlcl9yZXN1bHRzIjp7ImhhdGUiOnsiZmlsdGVyZWQiOmZhbHNlLCJzZXZlcml0eSI6InNhZmUifSwicHJvdGVjdGVkX21hdGVyaWFsX2NvZGUiOnsiZmlsdGVyZWQiOmZhbHNlLCJkZXRlY3RlZCI6ZmFsc2V9LCJwcm90ZWN0ZWRfbWF0ZXJpYWxfdGV4dCI6eyJmaWx0ZXJlZCI6ZmFsc2UsImRldGVjdGVkIjpmYWxzZX0sInNlbGZfaGFybSI6eyJmaWx0ZXJlZCI6ZmFsc2UsInNldmVyaXR5Ijoic2FmZSJ9LCJzZXh1YWwiOnsiZmlsdGVyZWQiOmZhbHNlLCJzZXZlcml0eSI6InNhZmUifSwidmlvbGVuY2UiOnsiZmlsdGVyZWQiOmZhbHNlLCJzZXZlcml0eSI6InNhZmUifX0sImZpbmlzaF9yZWFzb24iOiJzdG9wIiwiaW5kZXgiOjAsImxvZ3Byb2JzIjpudWxsLCJtZXNzYWdlIjp7ImFubm90YXRpb25zIjpbXSwiY29udGVudCI6IlN1cmUhIEhlcmXigJlzIGEgc2ltcGxlIGV4cGxhbmF0aW9uIG9mICoqcXVhbnR1bSBjb21wdXRpbmcqKjpcblxuLSAqKkNsYXNzaWNhbCBjb21wdXRlcnMqKiAod2hhdCB3ZSBoYXZlIG5vdykgdXNlICoqYml0cyoqLCB3aGljaCBjYW4gYmUgZWl0aGVyIGEgMCBvciBhIDEuIFRoZXkgc3RvcmUgYW5kIHByb2Nlc3MgaW5mb3JtYXRpb24gYXMgYSBzZXJpZXMgb2YgdGhlc2UgYml0cy5cbi0gKipRdWFudHVtIGNvbXB1dGVycyoqIHVzZSAqKnF1YW50dW0gYml0cyoqLCBvciAqKnF1Yml0cyoqLiBUaGFua3MgdG8gdGhlIHN0cmFuZ2UgcnVsZXMgb2YgcXVhbnR1bSBwaHlzaWNzLCBhIHF1Yml0IGNhbiBiZSBhIDAsIGEgMSwgKm9yKiBib3RoIGF0IHRoZSBzYW1lIHRpbWUgKHRoaXMgaXMgY2FsbGVkICoqc3VwZXJwb3NpdGlvbioqKS5cbi0gUXViaXRzIGNhbiBhbHNvIGJlICoqZW50YW5nbGVkKiosIHdoaWNoIG1lYW5zIGNoYW5naW5nIG9uZSBxdWJpdCBjYW4gaW5zdGFudGx5IGFmZmVjdCBhbm90aGVyLCBubyBtYXR0ZXIgaG93IGZhciBhcGFydCB0aGV5IGFyZS5cblxuQmVjYXVzZSBxdWJpdHMgY2FuIGhhbmRsZSBtdWNoIG1vcmUgaW5mb3JtYXRpb24gYXQgb25jZSBkdWUgdG8gc3VwZXJwb3NpdGlvbiBhbmQgZW50YW5nbGVtZW50LCAqKnF1YW50dW0gY29tcHV0ZXJzKiogY2FuIHBvdGVudGlhbGx5IHNvbHZlIGNlcnRhaW4gcHJvYmxlbXMgbXVjaCBmYXN0ZXIgdGhhbiBjbGFzc2ljYWwgY29tcHV0ZXJzLlxuXG4qKlNpbXBsZSBhbmFsb2d5OioqICBcbkltYWdpbmUgYSBjbGFzc2ljYWwgY29tcHV0ZXIgaXMgbGlrZSB0cnlpbmcgZXZlcnkgcG9zc2libGUgY29tYmluYXRpb24gb2YgYSBsb2NrLCBvbmUgYnkgb25lLiBBIHF1YW50dW0gY29tcHV0ZXIgY2FuIHRyeSAqKm1hbnkgY29tYmluYXRpb25zIGF0IHRoZSBzYW1lIHRpbWUqKiwgbWFraW5nIGl0IG11Y2ggZmFzdGVyIGZvciBzb21lIHRhc2tzLlxuXG5RdWFudHVtIGNvbXB1dGluZyBpcyBzdGlsbCBpbiBpdHMgZWFybHkgZGF5cywgYnV0IGl0IG1heSBvbmUgZGF5IHJldm9sdXRpb25pemUgZmllbGRzIGxpa2UgY3J5cHRvZ3JhcGh5LCBkcnVnIGRpc2NvdmVyeSwgYW5kIG1vcmUhIiwicmVmdXNhbCI6bnVsbCwicm9sZSI6ImFzc2lzdGFudCJ9fV0sImNyZWF0ZWQiOjE3Njk5MTQxODAsImlkIjoiY2hhdGNtcGwtRDRIb3FNSG0wTXpNWFI0OFNYWmZIVjRnRjMxU1kiLCJtb2RlbCI6IkFaVVJFX0RFUExPWU1FTlQtMjAyNS0wNC0xNCIsIm9iamVjdCI6ImNoYXQuY29tcGxldGlvbiIsInByb21wdF9maWx0ZXJfcmVzdWx0cyI6W3sicHJvbXB0X2luZGV4IjowLCJjb250ZW50X2ZpbHRlcl9yZXN1bHRzIjp7ImhhdGUiOnsiZmlsdGVyZWQiOmZhbHNlLCJzZXZlcml0eSI6InNhZmUifSwiamFpbGJyZWFrIjp7ImZpbHRlcmVkIjpmYWxzZSwiZGV0ZWN0ZWQiOmZhbHNlfSwic2VsZl9oYXJtIjp7ImZpbHRlcmVkIjpmYWxzZSwic2V2ZXJpdHkiOiJzYWZlIn0sInNleHVhbCI6eyJmaWx0ZXJlZCI6ZmFsc2UsInNldmVyaXR5Ijoic2FmZSJ9LCJ2aW9sZW5jZSI6eyJmaWx0ZXJlZCI6ZmFsc2UsInNldmVyaXR5Ijoic2FmZSJ9fX1dLCJzeXN0ZW1fZmluZ2VycHJpbnQiOiJmcF9mOTk2MzhhOGQ3IiwidXNhZ2UiOnsiY29tcGxldGlvbl90b2tlbnMiOjI0NywiY29tcGxldGlvbl90b2tlbnNfZGV0YWlscyI6eyJhY2NlcHRlZF9wcmVkaWN0aW9uX3Rva2VucyI6MCwiYXVkaW9fdG9rZW5zIjowLCJyZWFzb25pbmdfdG9rZW5zIjowLCJyZWplY3RlZF9wcmVkaWN0aW9uX3Rva2VucyI6MH0sInByb21wdF90b2tlbnMiOjE0LCJwcm9tcHRfdG9rZW5zX2RldGFpbHMiOnsiYXVkaW9fdG9rZW5zIjowLCJjYWNoZWRfdG9rZW5zIjowfSwidG90YWxfdG9rZW5zIjoyNjF9fQo= + recorded_at: Sun, 01 Feb 2026 02:49:43 GMT +recorded_with: VCR 6.4.0 diff --git a/test/fixtures/vcr_cassettes/integration/azure/common_format/messages_test/text_message_object.yml b/test/fixtures/vcr_cassettes/integration/azure/common_format/messages_test/text_message_object.yml new file mode 100644 index 00000000..c542a7bd --- /dev/null +++ b/test/fixtures/vcr_cassettes/integration/azure/common_format/messages_test/text_message_object.yml @@ -0,0 +1,83 @@ +--- +http_interactions: +- request: + method: post + uri: https://AZURE_RESOURCE.openai.azure.com/openai/deployments/AZURE_DEPLOYMENT/chat/completions?api-version=2025-01-01-preview + body: + encoding: UTF-8 + string: '{"model":"gpt-4","messages":[{"role":"user","content":"What are the + main differences between Ruby and Python?"}]}' + headers: + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - application/json + User-Agent: + - ActiveAgent::Providers::AzureProvider::AzureClient/Ruby 0.43.0 + Host: + - AZURE_RESOURCE.openai.azure.com + X-Stainless-Arch: + - arm64 + X-Stainless-Lang: + - ruby + X-Stainless-Os: + - MacOS + X-Stainless-Package-Version: + - 0.43.0 + X-Stainless-Runtime: + - ruby + X-Stainless-Runtime-Version: + - 3.3.8 + Content-Type: + - application/json + Api-Key: + - AZURE_API_KEY + X-Stainless-Retry-Count: + - '0' + X-Stainless-Timeout: + - '600.0' + Content-Length: + - '113' + response: + status: + code: 200 + message: OK + headers: + Content-Length: + - '4722' + Content-Type: + - application/json + Apim-Request-Id: + - 3c374989-2ce2-41bf-84f9-e0aff796304e + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + X-Content-Type-Options: + - nosniff + X-Ms-Region: + - West Europe + X-Ratelimit-Remaining-Requests: + - '48' + X-Ratelimit-Limit-Requests: + - '50' + X-Ratelimit-Remaining-Tokens: + - '49963' + X-Ratelimit-Limit-Tokens: + - '50000' + Azureml-Model-Session: + - d20260129130029-90b0c3ab1d00 + X-Accel-Buffering: + - 'no' + X-Ms-Rai-Invoked: + - 'true' + X-Request-Id: + - d269fae6-7c5f-423a-bf63-b6776adc7afc + X-Ms-Deployment-Name: + - AZURE_DEPLOYMENT + Date: + - Sun, 01 Feb 2026 02:49:37 GMT + body: + encoding: ASCII-8BIT + string: !binary |- + eyJjaG9pY2VzIjpbeyJjb250ZW50X2ZpbHRlcl9yZXN1bHRzIjp7ImhhdGUiOnsiZmlsdGVyZWQiOmZhbHNlLCJzZXZlcml0eSI6InNhZmUifSwicHJvdGVjdGVkX21hdGVyaWFsX2NvZGUiOnsiZmlsdGVyZWQiOmZhbHNlLCJkZXRlY3RlZCI6ZmFsc2V9LCJwcm90ZWN0ZWRfbWF0ZXJpYWxfdGV4dCI6eyJmaWx0ZXJlZCI6ZmFsc2UsImRldGVjdGVkIjpmYWxzZX0sInNlbGZfaGFybSI6eyJmaWx0ZXJlZCI6ZmFsc2UsInNldmVyaXR5Ijoic2FmZSJ9LCJzZXh1YWwiOnsiZmlsdGVyZWQiOmZhbHNlLCJzZXZlcml0eSI6InNhZmUifSwidmlvbGVuY2UiOnsiZmlsdGVyZWQiOmZhbHNlLCJzZXZlcml0eSI6InNhZmUifX0sImZpbmlzaF9yZWFzb24iOiJzdG9wIiwiaW5kZXgiOjAsImxvZ3Byb2JzIjpudWxsLCJtZXNzYWdlIjp7ImFubm90YXRpb25zIjpbXSwiY29udGVudCI6IioqUnVieSoqIGFuZCAqKlB5dGhvbioqIGFyZSBib3RoIGhpZ2gtbGV2ZWwsIGludGVycHJldGVkLCBkeW5hbWljYWxseS10eXBlZCwgZ2VuZXJhbC1wdXJwb3NlIGxhbmd1YWdlcywgYnV0IHRoZXkgZGlmZmVyIGNvbnNpZGVyYWJseSBpbiBwaGlsb3NvcGh5LCBkZXNpZ24sIGFuZCB0eXBpY2FsIHVzZSBjYXNlcy4gSGVyZSBhcmUgdGhlIG1haW4gZGlmZmVyZW5jZXM6XG5cbi0tLVxuXG4jIyMgMS4gKipQaGlsb3NvcGh5IGFuZCBEZXNpZ24qKlxuXG4tICoqUHl0aG9uOioqXG4gIC0gRW1waGFzaXplcyAqKnNpbXBsaWNpdHkqKiBhbmQgKipyZWFkYWJpbGl0eSoqLlxuICAtIOKAnFRoZXJlIHNob3VsZCBiZSBvbmXigJRhbmQgcHJlZmVyYWJseSBvbmx5IG9uZeKAlG9idmlvdXMgd2F5IHRvIGRvIGl0LuKAnSAoVGhlIFplbiBvZiBQeXRob24pXG4gIC0gVXNlcyBpbmRlbnRhdGlvbiBzdHJpY3RseSBmb3IgY29kZSBibG9ja3MgKHNpZ25pZmljYW50IHdoaXRlc3BhY2UpLlxuICAtIENvZGUgc3R5bGUgYW5kIGlkaW9tcyBhcmUgY29uc2lzdGVudCBhbmQgZW5mb3JjZWQgKFBFUCA4KS5cblxuLSAqKlJ1Ynk6KipcbiAgLSBGb2N1c2VzIG9uICoqZmxleGliaWxpdHkqKiBhbmQgKipkZXZlbG9wZXIgaGFwcGluZXNzKiouXG4gIC0g4oCcVGhlcmXigJlzIG1vcmUgdGhhbiBvbmUgd2F5IHRvIGRvIGl0LuKAnVxuICAtIFVzZXMgYGVuZGAgdG8gY2xvc2UgY29kZSBibG9ja3MuXG4gIC0gQWxsb3dzIG1vcmUgZnJlZWRvbSBpbiBjb2RlIHN0eWxlIGFuZCBtdWx0aXBsZSB3YXlzIHRvIGFjaGlldmUgdGhlIHNhbWUgcmVzdWx0LlxuXG4tLS1cblxuIyMjIDIuICoqU3ludGF4KipcblxuLSAqKlB5dGhvbjoqKiBTeW50YXggaXMgc3RyYWlnaHRmb3J3YXJkLCBvZnRlbiBkZXNjcmliZWQgYXMgXCJleGVjdXRhYmxlIHBzZXVkb2NvZGVcIi4gSGVhdnkgcmVsaWFuY2Ugb24gaW5kZW50YXRpb24uXG4gICAgYGBgcHl0aG9uXG4gICAgZGVmIGhlbGxvKG5hbWUpOlxuICAgICAgICBwcmludChcIkhlbGxvLCBcIiArIG5hbWUpXG4gICAgYGBgXG5cbi0gKipSdWJ5OioqIFN5bnRheCBpcyBleHByZXNzaXZlIGFuZCBjb25jaXNlLCBzb21ldGltZXMgbW9yZSBzaW1pbGFyIHRvIG5hdHVyYWwgbGFuZ3VhZ2Ugb3IgUGVybC5cbiAgICBgYGBydWJ5XG4gICAgZGVmIGhlbGxvKG5hbWUpXG4gICAgICBwdXRzIFwiSGVsbG8sIFwiICsgbmFtZVxuICAgIGVuZFxuICAgIGBgYFxuXG4tLS1cblxuIyMjIDMuICoqQ29tbXVuaXR5IGFuZCBFY29zeXN0ZW0qKlxuXG4tICoqUHl0aG9uOioqIFdpZGVseSB1c2VkIGluIHNjaWVudGlmaWMgY29tcHV0aW5nLCBkYXRhIGFuYWx5c2lzLCBtYWNoaW5lIGxlYXJuaW5nLCBhdXRvbWF0aW9uLCBzY3JpcHRpbmcsIHdlYiBkZXZlbG9wbWVudCwgYW5kIG1vcmUuXG4gICAgLSBQb3B1bGFyIGZyYW1ld29ya3M6IERqYW5nbywgRmxhc2sgKHdlYik7IE51bVB5LCBwYW5kYXMsIFRlbnNvckZsb3cgKGRhdGEgc2NpZW5jZSkuXG5cbi0gKipSdWJ5OioqIEJlc3Qga25vd24gZm9yIHdlYiBkZXZlbG9wbWVudCwgZXNwZWNpYWxseSAqKlJ1Ynkgb24gUmFpbHMqKiBmcmFtZXdvcmsuXG4gICAgLSBMZXNzIGNvbW1vbiBpbiBkYXRhIHNjaWVuY2Ugb3Igc3lzdGVtIHNjcmlwdGluZyB0aGFuIFB5dGhvbi5cblxuLS0tXG5cbiMjIyA0LiAqKk9iamVjdC1PcmllbnRhdGlvbioqXG5cbi0gKipSdWJ5OioqIFB1cmUgb2JqZWN0LW9yaWVudGVkLiBFdmVyeXRoaW5nIChpbmNsdWRpbmcgbnVtYmVycyBhbmQgbmlsKSBpcyBhbiBvYmplY3QuXG4tICoqUHl0aG9uOioqIE9iamVjdC1vcmllbnRlZCwgYnV0IG5vdCBzdHJpY3RseTsgdXNlcyBib3RoIG9iamVjdHMgYW5kIHByaW1pdGl2ZSB0eXBlcy5cblxuLS0tXG5cbiMjIyA1LiAqKkVycm9yIEhhbmRsaW5nKipcblxuLSAqKlB5dGhvbjoqKiBVc2VzIGB0cnlgL2BleGNlcHRgLlxuLSAqKlJ1Ynk6KiogVXNlcyBgYmVnaW5gL2ByZXNjdWVgLlxuXG4tLS1cblxuIyMjIDYuICoqUG9wdWxhcml0eSBcdTAwMjYgTGVhcm5pbmcgQ3VydmUqKlxuXG4tICoqUHl0aG9uOioqIE1vcmUgcG9wdWxhciBnbG9iYWxseS4gQ29uc2lkZXJlZCBzbGlnaHRseSBlYXNpZXIgZm9yIGJlZ2lubmVycyBkdWUgdG8gc3RyaWN0bmVzcyBhbmQgcmVhZGFiaWxpdHkuXG4tICoqUnVieToqKiBLbm93biBmb3IgZGV2ZWxvcGVyIGhhcHBpbmVzcyBhbmQgcmFwaWQgd2ViIGRldmVsb3BtZW50LCBidXQgaXRzIHBvcHVsYXJpdHkgaGFzIHBsYXRlYXVlZCBpbiByZWNlbnQgeWVhcnMuXG5cbi0tLVxuXG4jIyMgNy4gKipQZXJmb3JtYW5jZSoqXG5cbi0gQm90aCBhcmUgaW50ZXJwcmV0ZWQgbGFuZ3VhZ2VzLCBzaW1pbGFyIGluIHBlcmZvcm1hbmNlLCBidXQgdGhlcmUgY2FuIGJlIGRpZmZlcmVuY2VzIGluIHNvbWUgd29ya2xvYWRzLlxuXG4tLS1cblxuIyMjICoqU3VtbWFyeSBUYWJsZSoqXG5cbnwgRmVhdHVyZSAgICAgICAgICAgfCBQeXRob24gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8IFJ1YnkgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfFxufC0tLS0tLS0tLS0tLS0tLS0tLS18LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLXwtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS18XG58IFBoaWxvc29waHkgICAgICAgIHwgU2ltcGxpY2l0eSwgb25lIHdheSB0byBkbyBpdCAgICAgICAgICAgfCBGbGV4aWJpbGl0eSwgbWFueSB3YXlzIHRvIGRvIGl0ICAgICAgIHxcbnwgU3R5bGUgICAgICAgICAgICAgfCBJbmRlbnRhdGlvbi1iYXNlZCAgICAgICAgICAgICAgICAgICAgICB8IGVuZCBzdGF0ZW1lbnRzIGZvciBibG9ja3MgICAgICAgICAgICAgfFxufCBNYWluIHVzZSBjYXNlcyAgICB8IERhdGEgc2NpZW5jZSwgc2NyaXB0aW5nLCB3ZWIsIE1MICAgICAgIHwgV2ViIGRldmVsb3BtZW50IChSdWJ5IG9uIFJhaWxzKSAgICAgICB8XG58IENvbW11bml0eSAgICAgICAgIHwgTGFyZ2UsIGRpdmVyc2UgICAgICAgICAgICAgICAgICAgICAgICAgfCBTbWFsbGVyLCBSYWlscy1mb2N1c2VkICAgICAgICAgICAgICAgIHxcbnwgT09QICAgICAgICAgICAgICAgfCBOb3QgZXZlcnl0aGluZyBpcyBhbiBvYmplY3QgICAgICAgICAgICB8IEV2ZXJ5dGhpbmcgaXMgYW4gb2JqZWN0ICAgICAgICAgICAgICAgfFxufCBFcnJvciBoYW5kbGluZyAgICB8IHRyeS9leGNlcHQgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwgYmVnaW4vcmVzY3VlICAgICAgICAgICAgICAgICAgICAgICAgICB8XG5cbi0tLVxuXG4qKkluIHN1bW1hcnk6KiogIFxuLSBQeXRob24gaXMgYmVzdCBmb3IgZ2VuZXJhbC1wdXJwb3NlIHByb2dyYW1taW5nLCBkYXRhIHNjaWVuY2UsIGFuZCB3aGVyZSByZWFkYWJpbGl0eSBtYXR0ZXJzIG1vc3QuICBcbi0gUnVieSBleGNlbHMgaW4gcmFwaWQgd2ViIGRldmVsb3BtZW50LCBlc3BlY2lhbGx5IHdpdGggUmFpbHMsIG9mZmVyaW5nIG1vcmUgZmxleGliaWxpdHkgaW4gY29kaW5nIHN0eWxlLiIsInJlZnVzYWwiOm51bGwsInJvbGUiOiJhc3Npc3RhbnQifX1dLCJjcmVhdGVkIjoxNzY5OTE0MTcxLCJpZCI6ImNoYXRjbXBsLUQ0SG9oNnlvRnQ3UnJ5eFN3dmZRR3JzUldJOUx4IiwibW9kZWwiOiJBWlVSRV9ERVBMT1lNRU5ULTIwMjUtMDQtMTQiLCJvYmplY3QiOiJjaGF0LmNvbXBsZXRpb24iLCJwcm9tcHRfZmlsdGVyX3Jlc3VsdHMiOlt7InByb21wdF9pbmRleCI6MCwiY29udGVudF9maWx0ZXJfcmVzdWx0cyI6eyJoYXRlIjp7ImZpbHRlcmVkIjpmYWxzZSwic2V2ZXJpdHkiOiJzYWZlIn0sImphaWxicmVhayI6eyJmaWx0ZXJlZCI6ZmFsc2UsImRldGVjdGVkIjpmYWxzZX0sInNlbGZfaGFybSI6eyJmaWx0ZXJlZCI6ZmFsc2UsInNldmVyaXR5Ijoic2FmZSJ9LCJzZXh1YWwiOnsiZmlsdGVyZWQiOmZhbHNlLCJzZXZlcml0eSI6InNhZmUifSwidmlvbGVuY2UiOnsiZmlsdGVyZWQiOmZhbHNlLCJzZXZlcml0eSI6InNhZmUifX19XSwic3lzdGVtX2ZpbmdlcnByaW50IjoiZnBfZjk5NjM4YThkNyIsInVzYWdlIjp7ImNvbXBsZXRpb25fdG9rZW5zIjo3MjIsImNvbXBsZXRpb25fdG9rZW5zX2RldGFpbHMiOnsiYWNjZXB0ZWRfcHJlZGljdGlvbl90b2tlbnMiOjAsImF1ZGlvX3Rva2VucyI6MCwicmVhc29uaW5nX3Rva2VucyI6MCwicmVqZWN0ZWRfcHJlZGljdGlvbl90b2tlbnMiOjB9LCJwcm9tcHRfdG9rZW5zIjoxNywicHJvbXB0X3Rva2Vuc19kZXRhaWxzIjp7ImF1ZGlvX3Rva2VucyI6MCwiY2FjaGVkX3Rva2VucyI6MH0sInRvdGFsX3Rva2VucyI6NzM5fX0K + recorded_at: Sun, 01 Feb 2026 02:49:38 GMT +recorded_with: VCR 6.4.0 diff --git a/test/fixtures/vcr_cassettes/integration/azure/common_format/messages_test/text_messages_object.yml b/test/fixtures/vcr_cassettes/integration/azure/common_format/messages_test/text_messages_object.yml new file mode 100644 index 00000000..9de2fdd1 --- /dev/null +++ b/test/fixtures/vcr_cassettes/integration/azure/common_format/messages_test/text_messages_object.yml @@ -0,0 +1,84 @@ +--- +http_interactions: +- request: + method: post + uri: https://AZURE_RESOURCE.openai.azure.com/openai/deployments/AZURE_DEPLOYMENT/chat/completions?api-version=2025-01-01-preview + body: + encoding: UTF-8 + string: '{"model":"gpt-4","messages":[{"role":"assistant","content":"I can help + you with programming questions."},{"role":"user","content":"What are the benefits + of using ActiveRecord?"}]}' + headers: + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - application/json + User-Agent: + - ActiveAgent::Providers::AzureProvider::AzureClient/Ruby 0.43.0 + Host: + - AZURE_RESOURCE.openai.azure.com + X-Stainless-Arch: + - arm64 + X-Stainless-Lang: + - ruby + X-Stainless-Os: + - MacOS + X-Stainless-Package-Version: + - 0.43.0 + X-Stainless-Runtime: + - ruby + X-Stainless-Runtime-Version: + - 3.3.8 + Content-Type: + - application/json + Api-Key: + - AZURE_API_KEY + X-Stainless-Retry-Count: + - '0' + X-Stainless-Timeout: + - '600.0' + Content-Length: + - '179' + response: + status: + code: 200 + message: OK + headers: + Content-Length: + - '4149' + Content-Type: + - application/json + Apim-Request-Id: + - 23ff5eee-d1c2-4cdb-8ab1-58ec90588488 + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + X-Content-Type-Options: + - nosniff + X-Ms-Region: + - West Europe + X-Ratelimit-Remaining-Requests: + - '49' + X-Ratelimit-Limit-Requests: + - '50' + X-Ratelimit-Remaining-Tokens: + - '49977' + X-Ratelimit-Limit-Tokens: + - '50000' + Azureml-Model-Session: + - d20260129130029-90b0c3ab1d00 + X-Accel-Buffering: + - 'no' + X-Ms-Rai-Invoked: + - 'true' + X-Request-Id: + - fcd387c1-505b-41d6-ad99-7396ed0cca90 + X-Ms-Deployment-Name: + - AZURE_DEPLOYMENT + Date: + - Sun, 01 Feb 2026 02:49:30 GMT + body: + encoding: ASCII-8BIT + string: !binary |- + eyJjaG9pY2VzIjpbeyJjb250ZW50X2ZpbHRlcl9yZXN1bHRzIjp7ImhhdGUiOnsiZmlsdGVyZWQiOmZhbHNlLCJzZXZlcml0eSI6InNhZmUifSwicHJvdGVjdGVkX21hdGVyaWFsX2NvZGUiOnsiZmlsdGVyZWQiOmZhbHNlLCJkZXRlY3RlZCI6ZmFsc2V9LCJwcm90ZWN0ZWRfbWF0ZXJpYWxfdGV4dCI6eyJmaWx0ZXJlZCI6ZmFsc2UsImRldGVjdGVkIjpmYWxzZX0sInNlbGZfaGFybSI6eyJmaWx0ZXJlZCI6ZmFsc2UsInNldmVyaXR5Ijoic2FmZSJ9LCJzZXh1YWwiOnsiZmlsdGVyZWQiOmZhbHNlLCJzZXZlcml0eSI6InNhZmUifSwidmlvbGVuY2UiOnsiZmlsdGVyZWQiOmZhbHNlLCJzZXZlcml0eSI6InNhZmUifX0sImZpbmlzaF9yZWFzb24iOiJzdG9wIiwiaW5kZXgiOjAsImxvZ3Byb2JzIjpudWxsLCJtZXNzYWdlIjp7ImFubm90YXRpb25zIjpbXSwiY29udGVudCI6IioqQWN0aXZlUmVjb3JkKiogaXMgYW4gT2JqZWN0IFJlbGF0aW9uYWwgTWFwcGluZyAoT1JNKSBzeXN0ZW0gdXNlZCBpbiBSdWJ5IG9uIFJhaWxzLCBidXQgc2ltaWxhciBjb25jZXB0cyBleGlzdCBpbiBvdGhlciBsYW5ndWFnZXMuIFVzaW5nIEFjdGl2ZVJlY29yZCBvZmZlcnMgc2V2ZXJhbCBiZW5lZml0czpcblxuIyMjIDEuICoqQWJzdHJhY3Rpb24gb2YgRGF0YWJhc2UgT3BlcmF0aW9ucyoqXG4gICAtIFlvdSBpbnRlcmFjdCB3aXRoIGRhdGFiYXNlIHRhYmxlcyBhcyBSdWJ5IGNsYXNzZXMgYW5kIHJvd3MgYXMgb2JqZWN0cy5cbiAgIC0gQWJzdHJhY3RzIGF3YXkgU1FMIGZvciBtb3N0IHF1ZXJpZXPigJR1c2UgUnVieSBjb2RlIHRvIHJlYWQsIHdyaXRlLCB1cGRhdGUsIGFuZCBkZWxldGUgcmVjb3Jkcy5cblxuIyMjIDIuICoqUHJvZHVjdGl2aXR5IGFuZCBMZXNzIEJvaWxlcnBsYXRlKipcbiAgIC0gQXV0by1nZW5lcmF0ZXMgY29kZSBmb3IgQ1JVRCBvcGVyYXRpb25zLlxuICAgLSBIYW5kbGVzIHNjaGVtYSBpbnRyb3NwZWN0aW9uOiBrbm93cyBjb2x1bW5zLCB0eXBlcywgdmFsaWRhdGlvbnMsIGFuZCBhc3NvY2lhdGlvbnMuXG4gICAtIFJlZHVjZXMgcmVwZXRpdGl2ZSBjb2RlIGFuZCBpbXByb3ZlcyBkZXZlbG9wZXIgc3BlZWQuXG5cbiMjIyAzLiAqKkFzc29jaWF0aW9ucyBhbmQgUmVsYXRpb25zaGlwcyoqXG4gICAtIFByb3ZpZGVzIGVhc3kgZGVjbGFyYXRpb24gb2YgcmVsYXRpb25zaGlwcyAoYmVsb25nc190bywgaGFzX21hbnksIGV0YykuXG4gICAtIEF1dG9tYXRpY2FsbHkgaGFuZGxlcyBmb3JlaWduIGtleXMsIGpvaW4gdGFibGVzLCBhbmQgbmVzdGVkIHF1ZXJpZXMuXG5cbiMjIyA0LiAqKlZhbGlkYXRpb25zIGFuZCBDYWxsYmFja3MqKlxuICAgLSBFYXNpbHkgZGVmaW5lIGlucHV0IHZhbGlkYXRpb25zIGFuZCBsaWZlY3ljbGUgY2FsbGJhY2tzIChiZWZvcmVfc2F2ZSwgYWZ0ZXJfY3JlYXRlLCBldGMpLlxuICAgLSBDZW50cmFsaXplcyBhbmQgc2ltcGxpZmllcyBkYXRhIGludGVncml0eSBlbmZvcmNlbWVudC5cblxuIyMjIDUuICoqQ29udmVudGlvbiBvdmVyIENvbmZpZ3VyYXRpb24qKlxuICAgLSBVc2VzIHNlbnNpYmxlIGRlZmF1bHRz4oCUbmFtaW5nIGNvbnZlbnRpb25zIGZvciB0YWJsZXMsIGtleXMsIGV0Yy5cbiAgIC0gUmVkdWNlcyB0aGUgYW1vdW50IG9mIG1hbnVhbCBjb25maWd1cmF0aW9uIG5lZWRlZC5cblxuIyMjIDYuICoqQ2hhaW5hYmxlIFF1ZXJ5IEludGVyZmFjZSoqXG4gICAtIFdyaXRlIGNvbXBsZXggcXVlcmllcyB1c2luZyBhIFJ1YnkgRFNMIChzY29wZSwgd2hlcmUsIG9yZGVyLCBldGMpLlxuICAgLSBTdXBwb3J0cyBsYXp5IGxvYWRpbmcgYW5kIGNvbXBvc2FibGUgcXVlcmllcy5cbiAgIFxuIyMjIDcuICoqRGF0YWJhc2UtQWdub3N0aWMgQ29kZSoqXG4gICAtIFdyaXRlIGNvZGUgdGhhdCB3b3JrcyBvbiBtdWx0aXBsZSBkYXRhYmFzZSBzeXN0ZW1zIChNeVNRTCwgUG9zdGdyZVNRTCwgU1FMaXRlLCBldGMpLlxuXG4jIyMgOC4gKipNaWdyYXRpb24gU3VwcG9ydCoqXG4gICAtIEJ1aWx0LWluIG1pZ3JhdGlvbnMgaGVscCBldm9sdmUgeW91ciBkYXRhYmFzZSBzY2hlbWEgb3ZlciB0aW1lIGluIGEgc3RydWN0dXJlZCwgdmVyc2lvbi1jb250cm9sbGVkIHdheS5cblxuIyMjIDkuICoqQ29tbXVuaXR5IGFuZCBFY29zeXN0ZW0qKlxuICAgLSBXaWRlbHkgdXNlZCwgd2VsbC1kb2N1bWVudGVkLCBhbmQgc3VwcG9ydGVkIGJ5IGEgbGFyZ2UgY29tbXVuaXR5LlxuXG4tLS1cblxuKipTdW1tYXJ5IFRhYmxlOioqXG5cbnwgQmVuZWZpdCAgICAgICAgICAgICAgICAgICB8IERlc2NyaXB0aW9uICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8XG58LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tfC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tfFxufCBBYnN0cmFjdGlvbiAgICAgICAgICAgICAgIHwgV29yayB3aXRoIG9iamVjdHMsIG5vdCByYXcgU1FMICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHxcbnwgUHJvZHVjdGl2aXR5ICAgICAgICAgICAgICB8IExlc3MgYm9pbGVycGxhdGU7IGZhc3RlciBkZXZlbG9wbWVudCAgICAgICAgICAgICAgICAgICAgICAgICAgICB8XG58IEFzc29jaWF0aW9ucyAgICAgICAgICAgICAgfCBTaW1wbGUgcmVsYXRpb25zaGlwIG1hbmFnZW1lbnQgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfFxufCBWYWxpZGF0aW9ucyBcdTAwMjYgQ2FsbGJhY2tzICAgfCBCdWlsdC1pbiBkYXRhIGNoZWNrcyBhbmQgaG9va3MgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfFxufCBDb252ZW50aW9uICAgICAgICAgICAgICAgIHwgXCJDb252ZW50aW9uIG92ZXIgY29uZmlndXJhdGlvblwiIHBoaWxvc29waHkgICAgICAgICAgICAgICAgICAgICAgfFxufCBDaGFpbmFibGUgUXVlcmllcyAgICAgICAgIHwgQ29tcG9zYWJsZSwgcmVhZGFibGUgcXVlcnkgc3ludGF4ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHxcbnwgRGF0YWJhc2UgQWdub3N0aWNpc20gICAgICB8IFdvcmtzIHdpdGggbXVsdGlwbGUgREIgYmFja2VuZHMgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8XG58IE1pZ3JhdGlvbnMgICAgICAgICAgICAgICAgfCBTY2hlbWEgY2hhbmdlcyBtYW5hZ2VkIGluIGNvZGUgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfFxufCBDb21tdW5pdHkgICAgICAgICAgICAgICAgIHwgU3Ryb25nIGRvY3VtZW50YXRpb24gYW5kIHN1cHBvcnQgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHxcblxuSWYgeW91IGhhdmUgYSBzcGVjaWZpYyB1c2UtY2FzZSBvciB3YW50IGNvZGUgZXhhbXBsZXMsIGxldCBtZSBrbm93ISIsInJlZnVzYWwiOm51bGwsInJvbGUiOiJhc3Npc3RhbnQifX1dLCJjcmVhdGVkIjoxNzY5OTE0MTY0LCJpZCI6ImNoYXRjbXBsLUQ0SG9hVG5EOG9IbmNuY3dDc3FIYnlaSmo0M3p5IiwibW9kZWwiOiJBWlVSRV9ERVBMT1lNRU5ULTIwMjUtMDQtMTQiLCJvYmplY3QiOiJjaGF0LmNvbXBsZXRpb24iLCJwcm9tcHRfZmlsdGVyX3Jlc3VsdHMiOlt7InByb21wdF9pbmRleCI6MCwiY29udGVudF9maWx0ZXJfcmVzdWx0cyI6eyJoYXRlIjp7ImZpbHRlcmVkIjpmYWxzZSwic2V2ZXJpdHkiOiJzYWZlIn0sImphaWxicmVhayI6eyJmaWx0ZXJlZCI6ZmFsc2UsImRldGVjdGVkIjpmYWxzZX0sInNlbGZfaGFybSI6eyJmaWx0ZXJlZCI6ZmFsc2UsInNldmVyaXR5Ijoic2FmZSJ9LCJzZXh1YWwiOnsiZmlsdGVyZWQiOmZhbHNlLCJzZXZlcml0eSI6InNhZmUifSwidmlvbGVuY2UiOnsiZmlsdGVyZWQiOmZhbHNlLCJzZXZlcml0eSI6InNhZmUifX19XSwic3lzdGVtX2ZpbmdlcnByaW50IjoiZnBfZjk5NjM4YThkNyIsInVzYWdlIjp7ImNvbXBsZXRpb25fdG9rZW5zIjo1MjcsImNvbXBsZXRpb25fdG9rZW5zX2RldGFpbHMiOnsiYWNjZXB0ZWRfcHJlZGljdGlvbl90b2tlbnMiOjAsImF1ZGlvX3Rva2VucyI6MCwicmVhc29uaW5nX3Rva2VucyI6MCwicmVqZWN0ZWRfcHJlZGljdGlvbl90b2tlbnMiOjB9LCJwcm9tcHRfdG9rZW5zIjoyOCwicHJvbXB0X3Rva2Vuc19kZXRhaWxzIjp7ImF1ZGlvX3Rva2VucyI6MCwiY2FjaGVkX3Rva2VucyI6MH0sInRvdGFsX3Rva2VucyI6NTU1fX0K + recorded_at: Sun, 01 Feb 2026 02:49:30 GMT +recorded_with: VCR 6.4.0 diff --git a/test/fixtures/vcr_cassettes/integration/azure/common_format/tools_test/multiple_tools.yml b/test/fixtures/vcr_cassettes/integration/azure/common_format/tools_test/multiple_tools.yml new file mode 100644 index 00000000..8998beb8 --- /dev/null +++ b/test/fixtures/vcr_cassettes/integration/azure/common_format/tools_test/multiple_tools.yml @@ -0,0 +1,172 @@ +--- +http_interactions: +- request: + method: post + uri: https://AZURE_RESOURCE.openai.azure.com/openai/deployments/AZURE_DEPLOYMENT/chat/completions?api-version=2025-01-01-preview + body: + encoding: UTF-8 + string: '{"model":"gpt-4","messages":[{"role":"user","content":"What''s the + weather in NYC and what''s 5 plus 3?"}],"tools":[{"type":"function","function":{"name":"get_weather","description":"Get + the current weather","parameters":{"type":"object","properties":{"location":{"type":"string"}},"required":["location"]}}},{"type":"function","function":{"name":"calculate","description":"Perform + basic arithmetic","parameters":{"type":"object","properties":{"operation":{"type":"string","enum":["add","subtract","multiply","divide"]},"a":{"type":"number"},"b":{"type":"number"}},"required":["operation","a","b"]}}}]}' + headers: + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - application/json + User-Agent: + - ActiveAgent::Providers::AzureProvider::AzureClient/Ruby 0.43.0 + Host: + - AZURE_RESOURCE.openai.azure.com + X-Stainless-Arch: + - arm64 + X-Stainless-Lang: + - ruby + X-Stainless-Os: + - MacOS + X-Stainless-Package-Version: + - 0.43.0 + X-Stainless-Runtime: + - ruby + X-Stainless-Runtime-Version: + - 3.3.8 + Content-Type: + - application/json + Api-Key: + - AZURE_API_KEY + X-Stainless-Retry-Count: + - '0' + X-Stainless-Timeout: + - '600.0' + Content-Length: + - '600' + response: + status: + code: 200 + message: OK + headers: + Content-Length: + - '1198' + Content-Type: + - application/json + Apim-Request-Id: + - a10fed9c-0fad-4d8d-ad0c-4e4b08924fd3 + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + X-Content-Type-Options: + - nosniff + X-Ms-Region: + - West Europe + X-Ratelimit-Remaining-Requests: + - '34' + X-Ratelimit-Limit-Requests: + - '50' + X-Ratelimit-Remaining-Tokens: + - '49428' + X-Ratelimit-Limit-Tokens: + - '50000' + Azureml-Model-Session: + - d20251214200739-08ebc6b0d423474c + X-Accel-Buffering: + - 'no' + X-Ms-Rai-Invoked: + - 'true' + X-Request-Id: + - 2ad0146d-eeba-4196-b5c0-a765053e3d4c + X-Ms-Deployment-Name: + - AZURE_DEPLOYMENT + Date: + - Sun, 01 Feb 2026 02:50:05 GMT + body: + encoding: UTF-8 + string: '{"choices":[{"content_filter_results":{},"finish_reason":"tool_calls","index":0,"logprobs":null,"message":{"annotations":[],"content":null,"refusal":null,"role":"assistant","tool_calls":[{"function":{"arguments":"{\"location\": + \"NYC\"}","name":"get_weather"},"id":"call_rrvKMElYhSFBqmbV6ro5xVnN","type":"function"},{"function":{"arguments":"{\"operation\": + \"add\", \"a\": 5, \"b\": 3}","name":"calculate"},"id":"call_CXz31MJ2GeKT7cLZqisPjYZy","type":"function"}]}}],"created":1769914205,"id":"chatcmpl-D4HpFjOJjQyV4hkHdQEBvO9bkbvj3","model":"AZURE_DEPLOYMENT-2025-04-14","object":"chat.completion","prompt_filter_results":[{"prompt_index":0,"content_filter_results":{"hate":{"filtered":false,"severity":"safe"},"jailbreak":{"filtered":false,"detected":false},"self_harm":{"filtered":false,"severity":"safe"},"sexual":{"filtered":false,"severity":"safe"},"violence":{"filtered":false,"severity":"safe"}}}],"system_fingerprint":"fp_f99638a8d7","usage":{"completion_tokens":53,"completion_tokens_details":{"accepted_prediction_tokens":0,"audio_tokens":0,"reasoning_tokens":0,"rejected_prediction_tokens":0},"prompt_tokens":93,"prompt_tokens_details":{"audio_tokens":0,"cached_tokens":0},"total_tokens":146}} + + ' + recorded_at: Sun, 01 Feb 2026 02:50:05 GMT +- request: + method: post + uri: https://AZURE_RESOURCE.openai.azure.com/openai/deployments/AZURE_DEPLOYMENT/chat/completions?api-version=2025-01-01-preview + body: + encoding: UTF-8 + string: '{"model":"gpt-4","messages":[{"role":"user","content":"What''s the + weather in NYC and what''s 5 plus 3?"},{"role":"assistant","refusal":null,"annotations":[],"tool_calls":[{"id":"call_rrvKMElYhSFBqmbV6ro5xVnN","function":{"arguments":"{\"location\": + \"NYC\"}","name":"get_weather"},"type":"function"},{"id":"call_CXz31MJ2GeKT7cLZqisPjYZy","function":{"arguments":"{\"operation\": + \"add\", \"a\": 5, \"b\": 3}","name":"calculate"},"type":"function"}]},{"role":"tool","content":"{\"location\":\"NYC\",\"temperature\":\"72°F\",\"conditions\":\"sunny\"}","tool_call_id":"call_rrvKMElYhSFBqmbV6ro5xVnN"},{"role":"tool","content":"{\"operation\":\"add\",\"a\":5,\"b\":3,\"result\":8}","tool_call_id":"call_CXz31MJ2GeKT7cLZqisPjYZy"}],"tools":[{"type":"function","function":{"name":"get_weather","description":"Get + the current weather","parameters":{"type":"object","properties":{"location":{"type":"string"}},"required":["location"]}}},{"type":"function","function":{"name":"calculate","description":"Perform + basic arithmetic","parameters":{"type":"object","properties":{"operation":{"type":"string","enum":["add","subtract","multiply","divide"]},"a":{"type":"number"},"b":{"type":"number"}},"required":["operation","a","b"]}}}]}' + headers: + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - application/json + User-Agent: + - ActiveAgent::Providers::AzureProvider::AzureClient/Ruby 0.43.0 + Host: + - AZURE_RESOURCE.openai.azure.com + X-Stainless-Arch: + - arm64 + X-Stainless-Lang: + - ruby + X-Stainless-Os: + - MacOS + X-Stainless-Package-Version: + - 0.43.0 + X-Stainless-Runtime: + - ruby + X-Stainless-Runtime-Version: + - 3.3.8 + Content-Type: + - application/json + Api-Key: + - AZURE_API_KEY + X-Stainless-Retry-Count: + - '0' + X-Stainless-Timeout: + - '600.0' + Content-Length: + - '1222' + response: + status: + code: 200 + message: OK + headers: + Content-Length: + - '1272' + Content-Type: + - application/json + Apim-Request-Id: + - 435ad41b-8166-432a-a734-2d5c89912eee + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + X-Content-Type-Options: + - nosniff + X-Ms-Region: + - West Europe + X-Ratelimit-Remaining-Requests: + - '33' + X-Ratelimit-Limit-Requests: + - '50' + X-Ratelimit-Remaining-Tokens: + - '49267' + X-Ratelimit-Limit-Tokens: + - '50000' + Azureml-Model-Session: + - d20251214200739-08ebc6b0d423474c + X-Accel-Buffering: + - 'no' + X-Ms-Rai-Invoked: + - 'true' + X-Request-Id: + - c6e7f0a8-8aec-4fb6-a33b-1153ba496744 + X-Ms-Deployment-Name: + - AZURE_DEPLOYMENT + Date: + - Sun, 01 Feb 2026 02:50:06 GMT + body: + encoding: ASCII-8BIT + string: !binary |- + eyJjaG9pY2VzIjpbeyJjb250ZW50X2ZpbHRlcl9yZXN1bHRzIjp7ImhhdGUiOnsiZmlsdGVyZWQiOmZhbHNlLCJzZXZlcml0eSI6InNhZmUifSwicHJvdGVjdGVkX21hdGVyaWFsX2NvZGUiOnsiZmlsdGVyZWQiOmZhbHNlLCJkZXRlY3RlZCI6ZmFsc2V9LCJwcm90ZWN0ZWRfbWF0ZXJpYWxfdGV4dCI6eyJmaWx0ZXJlZCI6ZmFsc2UsImRldGVjdGVkIjpmYWxzZX0sInNlbGZfaGFybSI6eyJmaWx0ZXJlZCI6ZmFsc2UsInNldmVyaXR5Ijoic2FmZSJ9LCJzZXh1YWwiOnsiZmlsdGVyZWQiOmZhbHNlLCJzZXZlcml0eSI6InNhZmUifSwidmlvbGVuY2UiOnsiZmlsdGVyZWQiOmZhbHNlLCJzZXZlcml0eSI6InNhZmUifX0sImZpbmlzaF9yZWFzb24iOiJzdG9wIiwiaW5kZXgiOjAsImxvZ3Byb2JzIjpudWxsLCJtZXNzYWdlIjp7ImFubm90YXRpb25zIjpbXSwiY29udGVudCI6IlRoZSB3ZWF0aGVyIGluIE5ZQyBpcyA3MsKwRiBhbmQgc3VubnkuIEFsc28sIDUgcGx1cyAzIGVxdWFscyA4LiIsInJlZnVzYWwiOm51bGwsInJvbGUiOiJhc3Npc3RhbnQifX1dLCJjcmVhdGVkIjoxNzY5OTE0MjA2LCJpZCI6ImNoYXRjbXBsLUQ0SHBHUExxWjRGTTduNE9KbFZ6NnY4d1dFcEJwIiwibW9kZWwiOiJBWlVSRV9ERVBMT1lNRU5ULTIwMjUtMDQtMTQiLCJvYmplY3QiOiJjaGF0LmNvbXBsZXRpb24iLCJwcm9tcHRfZmlsdGVyX3Jlc3VsdHMiOlt7InByb21wdF9pbmRleCI6MCwiY29udGVudF9maWx0ZXJfcmVzdWx0cyI6eyJoYXRlIjp7ImZpbHRlcmVkIjpmYWxzZSwic2V2ZXJpdHkiOiJzYWZlIn0sImphaWxicmVhayI6eyJmaWx0ZXJlZCI6ZmFsc2UsImRldGVjdGVkIjpmYWxzZX0sInNlbGZfaGFybSI6eyJmaWx0ZXJlZCI6ZmFsc2UsInNldmVyaXR5Ijoic2FmZSJ9LCJzZXh1YWwiOnsiZmlsdGVyZWQiOmZhbHNlLCJzZXZlcml0eSI6InNhZmUifSwidmlvbGVuY2UiOnsiZmlsdGVyZWQiOmZhbHNlLCJzZXZlcml0eSI6InNhZmUifX19XSwic3lzdGVtX2ZpbmdlcnByaW50IjoiZnBfZjk5NjM4YThkNyIsInVzYWdlIjp7ImNvbXBsZXRpb25fdG9rZW5zIjoyNCwiY29tcGxldGlvbl90b2tlbnNfZGV0YWlscyI6eyJhY2NlcHRlZF9wcmVkaWN0aW9uX3Rva2VucyI6MCwiYXVkaW9fdG9rZW5zIjowLCJyZWFzb25pbmdfdG9rZW5zIjowLCJyZWplY3RlZF9wcmVkaWN0aW9uX3Rva2VucyI6MH0sInByb21wdF90b2tlbnMiOjIwNSwicHJvbXB0X3Rva2Vuc19kZXRhaWxzIjp7ImF1ZGlvX3Rva2VucyI6MCwiY2FjaGVkX3Rva2VucyI6MH0sInRvdGFsX3Rva2VucyI6MjI5fX0K + recorded_at: Sun, 01 Feb 2026 02:50:06 GMT +recorded_with: VCR 6.4.0 diff --git a/test/fixtures/vcr_cassettes/integration/azure/common_format/tools_test/tool_choice_auto.yml b/test/fixtures/vcr_cassettes/integration/azure/common_format/tools_test/tool_choice_auto.yml new file mode 100644 index 00000000..31fae788 --- /dev/null +++ b/test/fixtures/vcr_cassettes/integration/azure/common_format/tools_test/tool_choice_auto.yml @@ -0,0 +1,166 @@ +--- +http_interactions: +- request: + method: post + uri: https://AZURE_RESOURCE.openai.azure.com/openai/deployments/AZURE_DEPLOYMENT/chat/completions?api-version=2025-01-01-preview + body: + encoding: UTF-8 + string: '{"model":"gpt-4","messages":[{"role":"user","content":"What''s the + weather in London?"}],"tools":[{"type":"function","function":{"name":"get_weather","description":"Get + weather","parameters":{"type":"object","properties":{"location":{"type":"string"}},"required":["location"]}}}],"tool_choice":"auto"}' + headers: + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - application/json + User-Agent: + - ActiveAgent::Providers::AzureProvider::AzureClient/Ruby 0.43.0 + Host: + - AZURE_RESOURCE.openai.azure.com + X-Stainless-Arch: + - arm64 + X-Stainless-Lang: + - ruby + X-Stainless-Os: + - MacOS + X-Stainless-Package-Version: + - 0.43.0 + X-Stainless-Runtime: + - ruby + X-Stainless-Runtime-Version: + - 3.3.8 + Content-Type: + - application/json + Api-Key: + - AZURE_API_KEY + X-Stainless-Retry-Count: + - '0' + X-Stainless-Timeout: + - '600.0' + Content-Length: + - '300' + response: + status: + code: 200 + message: OK + headers: + Content-Length: + - '1051' + Content-Type: + - application/json + Apim-Request-Id: + - c3daeae9-0d5b-424c-84f6-99fa610b555f + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + X-Content-Type-Options: + - nosniff + X-Ms-Region: + - West Europe + X-Ratelimit-Remaining-Requests: + - '32' + X-Ratelimit-Limit-Requests: + - '50' + X-Ratelimit-Remaining-Tokens: + - '49213' + X-Ratelimit-Limit-Tokens: + - '50000' + Azureml-Model-Session: + - d20260129130029-90b0c3ab1d00 + X-Accel-Buffering: + - 'no' + X-Ms-Rai-Invoked: + - 'true' + X-Request-Id: + - 4fe248d1-7739-49dd-90c0-c00fb2f8ee5a + X-Ms-Deployment-Name: + - AZURE_DEPLOYMENT + Date: + - Sun, 01 Feb 2026 02:50:08 GMT + body: + encoding: UTF-8 + string: '{"choices":[{"content_filter_results":{},"finish_reason":"tool_calls","index":0,"logprobs":null,"message":{"annotations":[],"content":null,"refusal":null,"role":"assistant","tool_calls":[{"function":{"arguments":"{\"location\":\"London\"}","name":"get_weather"},"id":"call_ltwh5k4W8MD05hbsz7UbvlRU","type":"function"}]}}],"created":1769914208,"id":"chatcmpl-D4HpIfHD6phpIAeoLpecLo9o2xBTn","model":"AZURE_DEPLOYMENT-2025-04-14","object":"chat.completion","prompt_filter_results":[{"prompt_index":0,"content_filter_results":{"hate":{"filtered":false,"severity":"safe"},"jailbreak":{"filtered":false,"detected":false},"self_harm":{"filtered":false,"severity":"safe"},"sexual":{"filtered":false,"severity":"safe"},"violence":{"filtered":false,"severity":"safe"}}}],"system_fingerprint":"fp_f99638a8d7","usage":{"completion_tokens":15,"completion_tokens_details":{"accepted_prediction_tokens":0,"audio_tokens":0,"reasoning_tokens":0,"rejected_prediction_tokens":0},"prompt_tokens":46,"prompt_tokens_details":{"audio_tokens":0,"cached_tokens":0},"total_tokens":61}} + + ' + recorded_at: Sun, 01 Feb 2026 02:50:09 GMT +- request: + method: post + uri: https://AZURE_RESOURCE.openai.azure.com/openai/deployments/AZURE_DEPLOYMENT/chat/completions?api-version=2025-01-01-preview + body: + encoding: UTF-8 + string: '{"model":"gpt-4","messages":[{"role":"user","content":"What''s the + weather in London?"},{"role":"assistant","refusal":null,"annotations":[],"tool_calls":[{"id":"call_ltwh5k4W8MD05hbsz7UbvlRU","function":{"arguments":"{\"location\":\"London\"}","name":"get_weather"},"type":"function"}]},{"role":"tool","content":"{\"location\":\"London\",\"temperature\":\"72°F\",\"conditions\":\"sunny\"}","tool_call_id":"call_ltwh5k4W8MD05hbsz7UbvlRU"}],"tools":[{"type":"function","function":{"name":"get_weather","description":"Get + weather","parameters":{"type":"object","properties":{"location":{"type":"string"}},"required":["location"]}}}],"tool_choice":"auto"}' + headers: + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - application/json + User-Agent: + - ActiveAgent::Providers::AzureProvider::AzureClient/Ruby 0.43.0 + Host: + - AZURE_RESOURCE.openai.azure.com + X-Stainless-Arch: + - arm64 + X-Stainless-Lang: + - ruby + X-Stainless-Os: + - MacOS + X-Stainless-Package-Version: + - 0.43.0 + X-Stainless-Runtime: + - ruby + X-Stainless-Runtime-Version: + - 3.3.8 + Content-Type: + - application/json + Api-Key: + - AZURE_API_KEY + X-Stainless-Retry-Count: + - '0' + X-Stainless-Timeout: + - '600.0' + Content-Length: + - '651' + response: + status: + code: 200 + message: OK + headers: + Content-Length: + - '1277' + Content-Type: + - application/json + Apim-Request-Id: + - 9af90898-e13c-4c9b-a885-9747cc7009c9 + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + X-Content-Type-Options: + - nosniff + X-Ms-Region: + - West Europe + X-Ratelimit-Remaining-Requests: + - '31' + X-Ratelimit-Limit-Requests: + - '50' + X-Ratelimit-Remaining-Tokens: + - '49143' + X-Ratelimit-Limit-Tokens: + - '50000' + Azureml-Model-Session: + - d20251215034721-7a4c04dd8d9b4209 + X-Accel-Buffering: + - 'no' + X-Ms-Rai-Invoked: + - 'true' + X-Request-Id: + - 8964d9bf-9831-43ae-a6ff-6445e410814e + X-Ms-Deployment-Name: + - AZURE_DEPLOYMENT + Date: + - Sun, 01 Feb 2026 02:50:09 GMT + body: + encoding: ASCII-8BIT + string: !binary |- + eyJjaG9pY2VzIjpbeyJjb250ZW50X2ZpbHRlcl9yZXN1bHRzIjp7ImhhdGUiOnsiZmlsdGVyZWQiOmZhbHNlLCJzZXZlcml0eSI6InNhZmUifSwicHJvdGVjdGVkX21hdGVyaWFsX2NvZGUiOnsiZmlsdGVyZWQiOmZhbHNlLCJkZXRlY3RlZCI6ZmFsc2V9LCJwcm90ZWN0ZWRfbWF0ZXJpYWxfdGV4dCI6eyJmaWx0ZXJlZCI6ZmFsc2UsImRldGVjdGVkIjpmYWxzZX0sInNlbGZfaGFybSI6eyJmaWx0ZXJlZCI6ZmFsc2UsInNldmVyaXR5Ijoic2FmZSJ9LCJzZXh1YWwiOnsiZmlsdGVyZWQiOmZhbHNlLCJzZXZlcml0eSI6InNhZmUifSwidmlvbGVuY2UiOnsiZmlsdGVyZWQiOmZhbHNlLCJzZXZlcml0eSI6InNhZmUifX0sImZpbmlzaF9yZWFzb24iOiJzdG9wIiwiaW5kZXgiOjAsImxvZ3Byb2JzIjpudWxsLCJtZXNzYWdlIjp7ImFubm90YXRpb25zIjpbXSwiY29udGVudCI6IlRoZSB3ZWF0aGVyIGluIExvbmRvbiBpcyBjdXJyZW50bHkgc3Vubnkgd2l0aCBhIHRlbXBlcmF0dXJlIG9mIDcywrBGLiIsInJlZnVzYWwiOm51bGwsInJvbGUiOiJhc3Npc3RhbnQifX1dLCJjcmVhdGVkIjoxNzY5OTE0MjA5LCJpZCI6ImNoYXRjbXBsLUQ0SHBKcEpuaEhPQWxDMUwxV2lWYmVQRXlRYVY2IiwibW9kZWwiOiJBWlVSRV9ERVBMT1lNRU5ULTIwMjUtMDQtMTQiLCJvYmplY3QiOiJjaGF0LmNvbXBsZXRpb24iLCJwcm9tcHRfZmlsdGVyX3Jlc3VsdHMiOlt7InByb21wdF9pbmRleCI6MCwiY29udGVudF9maWx0ZXJfcmVzdWx0cyI6eyJoYXRlIjp7ImZpbHRlcmVkIjpmYWxzZSwic2V2ZXJpdHkiOiJzYWZlIn0sImphaWxicmVhayI6eyJmaWx0ZXJlZCI6ZmFsc2UsImRldGVjdGVkIjpmYWxzZX0sInNlbGZfaGFybSI6eyJmaWx0ZXJlZCI6ZmFsc2UsInNldmVyaXR5Ijoic2FmZSJ9LCJzZXh1YWwiOnsiZmlsdGVyZWQiOmZhbHNlLCJzZXZlcml0eSI6InNhZmUifSwidmlvbGVuY2UiOnsiZmlsdGVyZWQiOmZhbHNlLCJzZXZlcml0eSI6InNhZmUifX19XSwic3lzdGVtX2ZpbmdlcnByaW50IjoiZnBfZjk5NjM4YThkNyIsInVzYWdlIjp7ImNvbXBsZXRpb25fdG9rZW5zIjoxNywiY29tcGxldGlvbl90b2tlbnNfZGV0YWlscyI6eyJhY2NlcHRlZF9wcmVkaWN0aW9uX3Rva2VucyI6MCwiYXVkaW9fdG9rZW5zIjowLCJyZWFzb25pbmdfdG9rZW5zIjowLCJyZWplY3RlZF9wcmVkaWN0aW9uX3Rva2VucyI6MH0sInByb21wdF90b2tlbnMiOjgzLCJwcm9tcHRfdG9rZW5zX2RldGFpbHMiOnsiYXVkaW9fdG9rZW5zIjowLCJjYWNoZWRfdG9rZW5zIjowfSwidG90YWxfdG9rZW5zIjoxMDB9fQo= + recorded_at: Sun, 01 Feb 2026 02:50:10 GMT +recorded_with: VCR 6.4.0 diff --git a/test/fixtures/vcr_cassettes/integration/azure/common_format/tools_test/tool_choice_required.yml b/test/fixtures/vcr_cassettes/integration/azure/common_format/tools_test/tool_choice_required.yml new file mode 100644 index 00000000..6f7e1176 --- /dev/null +++ b/test/fixtures/vcr_cassettes/integration/azure/common_format/tools_test/tool_choice_required.yml @@ -0,0 +1,169 @@ +--- +http_interactions: +- request: + method: post + uri: https://AZURE_RESOURCE.openai.azure.com/openai/deployments/AZURE_DEPLOYMENT/chat/completions?api-version=2025-01-01-preview + body: + encoding: UTF-8 + string: '{"model":"gpt-4","messages":[{"role":"user","content":"What''s the + weather?"}],"tools":[{"type":"function","function":{"name":"get_weather","description":"Get + weather","parameters":{"type":"object","properties":{"location":{"type":"string"}},"required":["location"]}}}],"tool_choice":"required"}' + headers: + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - application/json + User-Agent: + - ActiveAgent::Providers::AzureProvider::AzureClient/Ruby 0.43.0 + Host: + - AZURE_RESOURCE.openai.azure.com + X-Stainless-Arch: + - arm64 + X-Stainless-Lang: + - ruby + X-Stainless-Os: + - MacOS + X-Stainless-Package-Version: + - 0.43.0 + X-Stainless-Runtime: + - ruby + X-Stainless-Runtime-Version: + - 3.3.8 + Content-Type: + - application/json + Api-Key: + - AZURE_API_KEY + X-Stainless-Retry-Count: + - '0' + X-Stainless-Timeout: + - '600.0' + Content-Length: + - '294' + response: + status: + code: 200 + message: OK + headers: + Content-Length: + - '1061' + Content-Type: + - application/json + Apim-Request-Id: + - 0b9aa0f4-38ae-4591-b387-cca52ddae735 + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + X-Content-Type-Options: + - nosniff + X-Ms-Region: + - West Europe + X-Ratelimit-Remaining-Requests: + - '36' + X-Ratelimit-Limit-Requests: + - '50' + X-Ratelimit-Remaining-Tokens: + - '49632' + X-Ratelimit-Limit-Tokens: + - '50000' + Azureml-Model-Session: + - d20260129130029-90b0c3ab1d00 + X-Accel-Buffering: + - 'no' + X-Ms-Rai-Invoked: + - 'true' + X-Request-Id: + - 6042e12c-7140-4cb8-95da-67f3edeef9da + X-Ms-Deployment-Name: + - AZURE_DEPLOYMENT + Date: + - Sun, 01 Feb 2026 02:50:02 GMT + body: + encoding: UTF-8 + string: '{"choices":[{"content_filter_results":{},"finish_reason":"tool_calls","index":0,"logprobs":null,"message":{"annotations":[],"content":null,"refusal":null,"role":"assistant","tool_calls":[{"function":{"arguments":"{\"location\":\"current + location\"}","name":"get_weather"},"id":"call_6KDnnZh71gGnGbHovoRGFD2b","type":"function"}]}}],"created":1769914201,"id":"chatcmpl-D4HpBU9E2qYRpDECSwHgk96ObZPhC","model":"AZURE_DEPLOYMENT-2025-04-14","object":"chat.completion","prompt_filter_results":[{"prompt_index":0,"content_filter_results":{"hate":{"filtered":false,"severity":"safe"},"jailbreak":{"filtered":false,"detected":false},"self_harm":{"filtered":false,"severity":"safe"},"sexual":{"filtered":false,"severity":"safe"},"violence":{"filtered":false,"severity":"safe"}}}],"system_fingerprint":"fp_f99638a8d7","usage":{"completion_tokens":16,"completion_tokens_details":{"accepted_prediction_tokens":0,"audio_tokens":0,"reasoning_tokens":0,"rejected_prediction_tokens":0},"prompt_tokens":44,"prompt_tokens_details":{"audio_tokens":0,"cached_tokens":0},"total_tokens":60}} + + ' + recorded_at: Sun, 01 Feb 2026 02:50:02 GMT +- request: + method: post + uri: https://AZURE_RESOURCE.openai.azure.com/openai/deployments/AZURE_DEPLOYMENT/chat/completions?api-version=2025-01-01-preview + body: + encoding: UTF-8 + string: '{"model":"gpt-4","messages":[{"role":"user","content":"What''s the + weather?"},{"role":"assistant","refusal":null,"annotations":[],"tool_calls":[{"id":"call_6KDnnZh71gGnGbHovoRGFD2b","function":{"arguments":"{\"location\":\"current + location\"}","name":"get_weather"},"type":"function"}]},{"role":"tool","content":"{\"location\":\"current + location\",\"temperature\":\"72°F\",\"conditions\":\"sunny\"}","tool_call_id":"call_6KDnnZh71gGnGbHovoRGFD2b"}],"tools":[{"type":"function","function":{"name":"get_weather","description":"Get + weather","parameters":{"type":"object","properties":{"location":{"type":"string"}},"required":["location"]}}}],"tool_choice":null}' + headers: + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - application/json + User-Agent: + - ActiveAgent::Providers::AzureProvider::AzureClient/Ruby 0.43.0 + Host: + - AZURE_RESOURCE.openai.azure.com + X-Stainless-Arch: + - arm64 + X-Stainless-Lang: + - ruby + X-Stainless-Os: + - MacOS + X-Stainless-Package-Version: + - 0.43.0 + X-Stainless-Runtime: + - ruby + X-Stainless-Runtime-Version: + - 3.3.8 + Content-Type: + - application/json + Api-Key: + - AZURE_API_KEY + X-Stainless-Retry-Count: + - '0' + X-Stainless-Timeout: + - '600.0' + Content-Length: + - '659' + response: + status: + code: 200 + message: OK + headers: + Content-Length: + - '1355' + Content-Type: + - application/json + Apim-Request-Id: + - 36f637d5-8e76-4b18-9dcc-b6e7d1949e70 + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + X-Content-Type-Options: + - nosniff + X-Ms-Region: + - West Europe + X-Ratelimit-Remaining-Requests: + - '35' + X-Ratelimit-Limit-Requests: + - '50' + X-Ratelimit-Remaining-Tokens: + - '49562' + X-Ratelimit-Limit-Tokens: + - '50000' + Azureml-Model-Session: + - d20251215222729-a15472d07c2a4df5 + X-Accel-Buffering: + - 'no' + X-Ms-Rai-Invoked: + - 'true' + X-Request-Id: + - 0dba9e04-5b8c-4b63-8f5b-792117ea8dc3 + X-Ms-Deployment-Name: + - AZURE_DEPLOYMENT + Date: + - Sun, 01 Feb 2026 02:50:03 GMT + body: + encoding: ASCII-8BIT + string: !binary |- + eyJjaG9pY2VzIjpbeyJjb250ZW50X2ZpbHRlcl9yZXN1bHRzIjp7ImhhdGUiOnsiZmlsdGVyZWQiOmZhbHNlLCJzZXZlcml0eSI6InNhZmUifSwicHJvdGVjdGVkX21hdGVyaWFsX2NvZGUiOnsiZmlsdGVyZWQiOmZhbHNlLCJkZXRlY3RlZCI6ZmFsc2V9LCJwcm90ZWN0ZWRfbWF0ZXJpYWxfdGV4dCI6eyJmaWx0ZXJlZCI6ZmFsc2UsImRldGVjdGVkIjpmYWxzZX0sInNlbGZfaGFybSI6eyJmaWx0ZXJlZCI6ZmFsc2UsInNldmVyaXR5Ijoic2FmZSJ9LCJzZXh1YWwiOnsiZmlsdGVyZWQiOmZhbHNlLCJzZXZlcml0eSI6InNhZmUifSwidmlvbGVuY2UiOnsiZmlsdGVyZWQiOmZhbHNlLCJzZXZlcml0eSI6InNhZmUifX0sImZpbmlzaF9yZWFzb24iOiJzdG9wIiwiaW5kZXgiOjAsImxvZ3Byb2JzIjpudWxsLCJtZXNzYWdlIjp7ImFubm90YXRpb25zIjpbXSwiY29udGVudCI6IlRoZSB3ZWF0aGVyIGF0IHlvdXIgY3VycmVudCBsb2NhdGlvbiBpcyBzdW5ueSB3aXRoIGEgdGVtcGVyYXR1cmUgb2YgNzLCsEYuIElmIHlvdSB3YW50IGEgZm9yZWNhc3QgZm9yIGEgc3BlY2lmaWMgY2l0eSBvciBtb3JlIGRldGFpbHMsIGxldCBtZSBrbm93ISIsInJlZnVzYWwiOm51bGwsInJvbGUiOiJhc3Npc3RhbnQifX1dLCJjcmVhdGVkIjoxNzY5OTE0MjAzLCJpZCI6ImNoYXRjbXBsLUQ0SHBEdVVZU2drM3ExdHVnR081cFVYbVh5TFlwIiwibW9kZWwiOiJBWlVSRV9ERVBMT1lNRU5ULTIwMjUtMDQtMTQiLCJvYmplY3QiOiJjaGF0LmNvbXBsZXRpb24iLCJwcm9tcHRfZmlsdGVyX3Jlc3VsdHMiOlt7InByb21wdF9pbmRleCI6MCwiY29udGVudF9maWx0ZXJfcmVzdWx0cyI6eyJoYXRlIjp7ImZpbHRlcmVkIjpmYWxzZSwic2V2ZXJpdHkiOiJzYWZlIn0sImphaWxicmVhayI6eyJmaWx0ZXJlZCI6ZmFsc2UsImRldGVjdGVkIjpmYWxzZX0sInNlbGZfaGFybSI6eyJmaWx0ZXJlZCI6ZmFsc2UsInNldmVyaXR5Ijoic2FmZSJ9LCJzZXh1YWwiOnsiZmlsdGVyZWQiOmZhbHNlLCJzZXZlcml0eSI6InNhZmUifSwidmlvbGVuY2UiOnsiZmlsdGVyZWQiOmZhbHNlLCJzZXZlcml0eSI6InNhZmUifX19XSwic3lzdGVtX2ZpbmdlcnByaW50IjoiZnBfYjkwNDFlMTBiNCIsInVzYWdlIjp7ImNvbXBsZXRpb25fdG9rZW5zIjozNSwiY29tcGxldGlvbl90b2tlbnNfZGV0YWlscyI6eyJhY2NlcHRlZF9wcmVkaWN0aW9uX3Rva2VucyI6MCwiYXVkaW9fdG9rZW5zIjowLCJyZWFzb25pbmdfdG9rZW5zIjowLCJyZWplY3RlZF9wcmVkaWN0aW9uX3Rva2VucyI6MH0sInByb21wdF90b2tlbnMiOjgzLCJwcm9tcHRfdG9rZW5zX2RldGFpbHMiOnsiYXVkaW9fdG9rZW5zIjowLCJjYWNoZWRfdG9rZW5zIjowfSwidG90YWxfdG9rZW5zIjoxMTh9fQo= + recorded_at: Sun, 01 Feb 2026 02:50:04 GMT +recorded_with: VCR 6.4.0 diff --git a/test/fixtures/vcr_cassettes/integration/azure/common_format/tools_test/tool_with_parameters.yml b/test/fixtures/vcr_cassettes/integration/azure/common_format/tools_test/tool_with_parameters.yml new file mode 100644 index 00000000..2df88c20 --- /dev/null +++ b/test/fixtures/vcr_cassettes/integration/azure/common_format/tools_test/tool_with_parameters.yml @@ -0,0 +1,171 @@ +--- +http_interactions: +- request: + method: post + uri: https://AZURE_RESOURCE.openai.azure.com/openai/deployments/AZURE_DEPLOYMENT/chat/completions?api-version=2025-01-01-preview + body: + encoding: UTF-8 + string: '{"model":"gpt-4","messages":[{"role":"user","content":"What''s the + weather in San Francisco?"}],"tools":[{"type":"function","function":{"name":"get_weather","description":"Get + the current weather in a given location","parameters":{"type":"object","properties":{"location":{"type":"string","description":"The + city and state, e.g. San Francisco, CA"}},"required":["location"]}}}]}' + headers: + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - application/json + User-Agent: + - ActiveAgent::Providers::AzureProvider::AzureClient/Ruby 0.43.0 + Host: + - AZURE_RESOURCE.openai.azure.com + X-Stainless-Arch: + - arm64 + X-Stainless-Lang: + - ruby + X-Stainless-Os: + - MacOS + X-Stainless-Package-Version: + - 0.43.0 + X-Stainless-Runtime: + - ruby + X-Stainless-Runtime-Version: + - 3.3.8 + Content-Type: + - application/json + Api-Key: + - AZURE_API_KEY + X-Stainless-Retry-Count: + - '0' + X-Stainless-Timeout: + - '600.0' + Content-Length: + - '377' + response: + status: + code: 200 + message: OK + headers: + Content-Length: + - '1062' + Content-Type: + - application/json + Apim-Request-Id: + - f9153a12-bf9d-466f-9983-ca60e0f96b11 + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + X-Content-Type-Options: + - nosniff + X-Ms-Region: + - West Europe + X-Ratelimit-Remaining-Requests: + - '38' + X-Ratelimit-Limit-Requests: + - '50' + X-Ratelimit-Remaining-Tokens: + - '49781' + X-Ratelimit-Limit-Tokens: + - '50000' + Azureml-Model-Session: + - d20251214141937-53ba87ed3e94459f + X-Accel-Buffering: + - 'no' + X-Ms-Rai-Invoked: + - 'true' + X-Request-Id: + - 7dc32455-df3c-4394-981a-fb578dbd95ae + X-Ms-Deployment-Name: + - AZURE_DEPLOYMENT + Date: + - Sun, 01 Feb 2026 02:49:59 GMT + body: + encoding: UTF-8 + string: '{"choices":[{"content_filter_results":{},"finish_reason":"tool_calls","index":0,"logprobs":null,"message":{"annotations":[],"content":null,"refusal":null,"role":"assistant","tool_calls":[{"function":{"arguments":"{\"location\":\"San + Francisco, CA\"}","name":"get_weather"},"id":"call_03H1cFeRDjA8pwesA1qZ9yBS","type":"function"}]}}],"created":1769914198,"id":"chatcmpl-D4Hp8cpvPpD9gG1DvZHEkk770QARV","model":"AZURE_DEPLOYMENT-2025-04-14","object":"chat.completion","prompt_filter_results":[{"prompt_index":0,"content_filter_results":{"hate":{"filtered":false,"severity":"safe"},"jailbreak":{"filtered":false,"detected":false},"self_harm":{"filtered":false,"severity":"safe"},"sexual":{"filtered":false,"severity":"safe"},"violence":{"filtered":false,"severity":"safe"}}}],"system_fingerprint":"fp_f99638a8d7","usage":{"completion_tokens":18,"completion_tokens_details":{"accepted_prediction_tokens":0,"audio_tokens":0,"reasoning_tokens":0,"rejected_prediction_tokens":0},"prompt_tokens":67,"prompt_tokens_details":{"audio_tokens":0,"cached_tokens":0},"total_tokens":85}} + + ' + recorded_at: Sun, 01 Feb 2026 02:49:59 GMT +- request: + method: post + uri: https://AZURE_RESOURCE.openai.azure.com/openai/deployments/AZURE_DEPLOYMENT/chat/completions?api-version=2025-01-01-preview + body: + encoding: UTF-8 + string: '{"model":"gpt-4","messages":[{"role":"user","content":"What''s the + weather in San Francisco?"},{"role":"assistant","refusal":null,"annotations":[],"tool_calls":[{"id":"call_03H1cFeRDjA8pwesA1qZ9yBS","function":{"arguments":"{\"location\":\"San + Francisco, CA\"}","name":"get_weather"},"type":"function"}]},{"role":"tool","content":"{\"location\":\"San + Francisco, CA\",\"temperature\":\"72°F\",\"conditions\":\"sunny\"}","tool_call_id":"call_03H1cFeRDjA8pwesA1qZ9yBS"}],"tools":[{"type":"function","function":{"name":"get_weather","description":"Get + the current weather in a given location","parameters":{"type":"object","properties":{"location":{"type":"string","description":"The + city and state, e.g. San Francisco, CA"}},"required":["location"]}}}]}' + headers: + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - application/json + User-Agent: + - ActiveAgent::Providers::AzureProvider::AzureClient/Ruby 0.43.0 + Host: + - AZURE_RESOURCE.openai.azure.com + X-Stainless-Arch: + - arm64 + X-Stainless-Lang: + - ruby + X-Stainless-Os: + - MacOS + X-Stainless-Package-Version: + - 0.43.0 + X-Stainless-Runtime: + - ruby + X-Stainless-Runtime-Version: + - 3.3.8 + Content-Type: + - application/json + Api-Key: + - AZURE_API_KEY + X-Stainless-Retry-Count: + - '0' + X-Stainless-Timeout: + - '600.0' + Content-Length: + - '750' + response: + status: + code: 200 + message: OK + headers: + Content-Length: + - '1285' + Content-Type: + - application/json + Apim-Request-Id: + - 698cb5f4-b0ec-4304-ad90-d10b30c1fcfb + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + X-Content-Type-Options: + - nosniff + X-Ms-Region: + - West Europe + X-Ratelimit-Remaining-Requests: + - '37' + X-Ratelimit-Limit-Requests: + - '50' + X-Ratelimit-Remaining-Tokens: + - '49683' + X-Ratelimit-Limit-Tokens: + - '50000' + Azureml-Model-Session: + - d20251214141937-53ba87ed3e94459f + X-Accel-Buffering: + - 'no' + X-Ms-Rai-Invoked: + - 'true' + X-Request-Id: + - dab11c59-7138-4480-980a-afdb354c5f7b + X-Ms-Deployment-Name: + - AZURE_DEPLOYMENT + Date: + - Sun, 01 Feb 2026 02:50:00 GMT + body: + encoding: ASCII-8BIT + string: !binary |- + eyJjaG9pY2VzIjpbeyJjb250ZW50X2ZpbHRlcl9yZXN1bHRzIjp7ImhhdGUiOnsiZmlsdGVyZWQiOmZhbHNlLCJzZXZlcml0eSI6InNhZmUifSwicHJvdGVjdGVkX21hdGVyaWFsX2NvZGUiOnsiZmlsdGVyZWQiOmZhbHNlLCJkZXRlY3RlZCI6ZmFsc2V9LCJwcm90ZWN0ZWRfbWF0ZXJpYWxfdGV4dCI6eyJmaWx0ZXJlZCI6ZmFsc2UsImRldGVjdGVkIjpmYWxzZX0sInNlbGZfaGFybSI6eyJmaWx0ZXJlZCI6ZmFsc2UsInNldmVyaXR5Ijoic2FmZSJ9LCJzZXh1YWwiOnsiZmlsdGVyZWQiOmZhbHNlLCJzZXZlcml0eSI6InNhZmUifSwidmlvbGVuY2UiOnsiZmlsdGVyZWQiOmZhbHNlLCJzZXZlcml0eSI6InNhZmUifX0sImZpbmlzaF9yZWFzb24iOiJzdG9wIiwiaW5kZXgiOjAsImxvZ3Byb2JzIjpudWxsLCJtZXNzYWdlIjp7ImFubm90YXRpb25zIjpbXSwiY29udGVudCI6IlRoZSB3ZWF0aGVyIGluIFNhbiBGcmFuY2lzY28gaXMgY3VycmVudGx5IHN1bm55IHdpdGggYSB0ZW1wZXJhdHVyZSBvZiA3MsKwRi4iLCJyZWZ1c2FsIjpudWxsLCJyb2xlIjoiYXNzaXN0YW50In19XSwiY3JlYXRlZCI6MTc2OTkxNDE5OSwiaWQiOiJjaGF0Y21wbC1ENEhwOVpYaFlOY2E2S01KS25IM1dmQUZoTlBWMSIsIm1vZGVsIjoiQVpVUkVfREVQTE9ZTUVOVC0yMDI1LTA0LTE0Iiwib2JqZWN0IjoiY2hhdC5jb21wbGV0aW9uIiwicHJvbXB0X2ZpbHRlcl9yZXN1bHRzIjpbeyJwcm9tcHRfaW5kZXgiOjAsImNvbnRlbnRfZmlsdGVyX3Jlc3VsdHMiOnsiaGF0ZSI6eyJmaWx0ZXJlZCI6ZmFsc2UsInNldmVyaXR5Ijoic2FmZSJ9LCJqYWlsYnJlYWsiOnsiZmlsdGVyZWQiOmZhbHNlLCJkZXRlY3RlZCI6ZmFsc2V9LCJzZWxmX2hhcm0iOnsiZmlsdGVyZWQiOmZhbHNlLCJzZXZlcml0eSI6InNhZmUifSwic2V4dWFsIjp7ImZpbHRlcmVkIjpmYWxzZSwic2V2ZXJpdHkiOiJzYWZlIn0sInZpb2xlbmNlIjp7ImZpbHRlcmVkIjpmYWxzZSwic2V2ZXJpdHkiOiJzYWZlIn19fV0sInN5c3RlbV9maW5nZXJwcmludCI6ImZwX2Y5OTYzOGE4ZDciLCJ1c2FnZSI6eyJjb21wbGV0aW9uX3Rva2VucyI6MTgsImNvbXBsZXRpb25fdG9rZW5zX2RldGFpbHMiOnsiYWNjZXB0ZWRfcHJlZGljdGlvbl90b2tlbnMiOjAsImF1ZGlvX3Rva2VucyI6MCwicmVhc29uaW5nX3Rva2VucyI6MCwicmVqZWN0ZWRfcHJlZGljdGlvbl90b2tlbnMiOjB9LCJwcm9tcHRfdG9rZW5zIjoxMTAsInByb21wdF90b2tlbnNfZGV0YWlscyI6eyJhdWRpb190b2tlbnMiOjAsImNhY2hlZF90b2tlbnMiOjB9LCJ0b3RhbF90b2tlbnMiOjEyOH19Cg== + recorded_at: Sun, 01 Feb 2026 02:50:00 GMT +recorded_with: VCR 6.4.0 diff --git a/test/fixtures/vcr_cassettes/integration/azure/response_test/hello_world.yml b/test/fixtures/vcr_cassettes/integration/azure/response_test/hello_world.yml new file mode 100644 index 00000000..231c5593 --- /dev/null +++ b/test/fixtures/vcr_cassettes/integration/azure/response_test/hello_world.yml @@ -0,0 +1,82 @@ +--- +http_interactions: +- request: + method: post + uri: https://AZURE_RESOURCE.openai.azure.com/openai/deployments/AZURE_DEPLOYMENT/chat/completions?api-version=2025-01-01-preview + body: + encoding: UTF-8 + string: '{"model":"gpt-4","messages":[{"role":"user","content":"Say hello world"}]}' + headers: + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - application/json + User-Agent: + - ActiveAgent::Providers::AzureProvider::AzureClient/Ruby 0.43.0 + Host: + - AZURE_RESOURCE.openai.azure.com + X-Stainless-Arch: + - arm64 + X-Stainless-Lang: + - ruby + X-Stainless-Os: + - MacOS + X-Stainless-Package-Version: + - 0.43.0 + X-Stainless-Runtime: + - ruby + X-Stainless-Runtime-Version: + - 3.3.8 + Content-Type: + - application/json + Api-Key: + - AZURE_API_KEY + X-Stainless-Retry-Count: + - '0' + X-Stainless-Timeout: + - '600.0' + Content-Length: + - '74' + response: + status: + code: 200 + message: OK + headers: + Content-Length: + - '1224' + Content-Type: + - application/json + Apim-Request-Id: + - 3c26ea3e-4688-4e89-b261-0a43472fc3aa + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + X-Content-Type-Options: + - nosniff + X-Ms-Region: + - West Europe + X-Ratelimit-Remaining-Requests: + - '40' + X-Ratelimit-Limit-Requests: + - '50' + X-Ratelimit-Remaining-Tokens: + - '49865' + X-Ratelimit-Limit-Tokens: + - '50000' + Azureml-Model-Session: + - d20260129130029-90b0c3ab1d00 + X-Accel-Buffering: + - 'no' + X-Ms-Rai-Invoked: + - 'true' + X-Request-Id: + - 245ed0a1-1184-4ec0-8229-a63104d2284f + X-Ms-Deployment-Name: + - AZURE_DEPLOYMENT + Date: + - Sun, 01 Feb 2026 02:49:55 GMT + body: + encoding: ASCII-8BIT + string: !binary |- + eyJjaG9pY2VzIjpbeyJjb250ZW50X2ZpbHRlcl9yZXN1bHRzIjp7ImhhdGUiOnsiZmlsdGVyZWQiOmZhbHNlLCJzZXZlcml0eSI6InNhZmUifSwicHJvdGVjdGVkX21hdGVyaWFsX2NvZGUiOnsiZmlsdGVyZWQiOmZhbHNlLCJkZXRlY3RlZCI6ZmFsc2V9LCJwcm90ZWN0ZWRfbWF0ZXJpYWxfdGV4dCI6eyJmaWx0ZXJlZCI6ZmFsc2UsImRldGVjdGVkIjpmYWxzZX0sInNlbGZfaGFybSI6eyJmaWx0ZXJlZCI6ZmFsc2UsInNldmVyaXR5Ijoic2FmZSJ9LCJzZXh1YWwiOnsiZmlsdGVyZWQiOmZhbHNlLCJzZXZlcml0eSI6InNhZmUifSwidmlvbGVuY2UiOnsiZmlsdGVyZWQiOmZhbHNlLCJzZXZlcml0eSI6InNhZmUifX0sImZpbmlzaF9yZWFzb24iOiJzdG9wIiwiaW5kZXgiOjAsImxvZ3Byb2JzIjpudWxsLCJtZXNzYWdlIjp7ImFubm90YXRpb25zIjpbXSwiY29udGVudCI6IkhlbGxvLCB3b3JsZCEg8J+MjSIsInJlZnVzYWwiOm51bGwsInJvbGUiOiJhc3Npc3RhbnQifX1dLCJjcmVhdGVkIjoxNzY5OTE0MTk1LCJpZCI6ImNoYXRjbXBsLUQ0SHA1bGtUWkh6bnF1UmJRZTNHZWhzc1g0c2JaIiwibW9kZWwiOiJBWlVSRV9ERVBMT1lNRU5ULTIwMjUtMDQtMTQiLCJvYmplY3QiOiJjaGF0LmNvbXBsZXRpb24iLCJwcm9tcHRfZmlsdGVyX3Jlc3VsdHMiOlt7InByb21wdF9pbmRleCI6MCwiY29udGVudF9maWx0ZXJfcmVzdWx0cyI6eyJoYXRlIjp7ImZpbHRlcmVkIjpmYWxzZSwic2V2ZXJpdHkiOiJzYWZlIn0sImphaWxicmVhayI6eyJmaWx0ZXJlZCI6ZmFsc2UsImRldGVjdGVkIjpmYWxzZX0sInNlbGZfaGFybSI6eyJmaWx0ZXJlZCI6ZmFsc2UsInNldmVyaXR5Ijoic2FmZSJ9LCJzZXh1YWwiOnsiZmlsdGVyZWQiOmZhbHNlLCJzZXZlcml0eSI6InNhZmUifSwidmlvbGVuY2UiOnsiZmlsdGVyZWQiOmZhbHNlLCJzZXZlcml0eSI6InNhZmUifX19XSwic3lzdGVtX2ZpbmdlcnByaW50IjoiZnBfZjk5NjM4YThkNyIsInVzYWdlIjp7ImNvbXBsZXRpb25fdG9rZW5zIjo3LCJjb21wbGV0aW9uX3Rva2Vuc19kZXRhaWxzIjp7ImFjY2VwdGVkX3ByZWRpY3Rpb25fdG9rZW5zIjowLCJhdWRpb190b2tlbnMiOjAsInJlYXNvbmluZ190b2tlbnMiOjAsInJlamVjdGVkX3ByZWRpY3Rpb25fdG9rZW5zIjowfSwicHJvbXB0X3Rva2VucyI6MTAsInByb21wdF90b2tlbnNfZGV0YWlscyI6eyJhdWRpb190b2tlbnMiOjAsImNhY2hlZF90b2tlbnMiOjB9LCJ0b3RhbF90b2tlbnMiOjE3fX0K + recorded_at: Sun, 01 Feb 2026 02:49:56 GMT +recorded_with: VCR 6.4.0 diff --git a/test/fixtures/vcr_cassettes/integration/azure/response_test/simple_prompt.yml b/test/fixtures/vcr_cassettes/integration/azure/response_test/simple_prompt.yml new file mode 100644 index 00000000..b79c1efe --- /dev/null +++ b/test/fixtures/vcr_cassettes/integration/azure/response_test/simple_prompt.yml @@ -0,0 +1,84 @@ +--- +http_interactions: +- request: + method: post + uri: https://AZURE_RESOURCE.openai.azure.com/openai/deployments/AZURE_DEPLOYMENT/chat/completions?api-version=2025-01-01-preview + body: + encoding: UTF-8 + string: '{"model":"gpt-4","messages":[{"role":"user","content":"Say ''test'' + once."}],"max_completion_tokens":10}' + headers: + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - application/json + User-Agent: + - ActiveAgent::Providers::AzureProvider::AzureClient/Ruby 0.43.0 + Host: + - AZURE_RESOURCE.openai.azure.com + X-Stainless-Arch: + - arm64 + X-Stainless-Lang: + - ruby + X-Stainless-Os: + - MacOS + X-Stainless-Package-Version: + - 0.43.0 + X-Stainless-Runtime: + - ruby + X-Stainless-Runtime-Version: + - 3.3.8 + Content-Type: + - application/json + Api-Key: + - AZURE_API_KEY + X-Stainless-Retry-Count: + - '0' + X-Stainless-Timeout: + - '600.0' + Content-Length: + - '102' + response: + status: + code: 200 + message: OK + headers: + Content-Length: + - '1210' + Content-Type: + - application/json + Apim-Request-Id: + - 455a416f-7ff1-4319-9cab-b05224bf95c7 + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + X-Content-Type-Options: + - nosniff + X-Ms-Region: + - West Europe + X-Ratelimit-Remaining-Requests: + - '39' + X-Ratelimit-Limit-Requests: + - '50' + X-Ratelimit-Remaining-Tokens: + - '49860' + X-Ratelimit-Limit-Tokens: + - '50000' + Azureml-Model-Session: + - d20260129130029-90b0c3ab1d00 + X-Accel-Buffering: + - 'no' + X-Ms-Rai-Invoked: + - 'true' + X-Request-Id: + - caa65f45-a892-4f87-8906-059e12359248 + X-Ms-Deployment-Name: + - AZURE_DEPLOYMENT + Date: + - Sun, 01 Feb 2026 02:49:57 GMT + body: + encoding: UTF-8 + string: '{"choices":[{"content_filter_results":{"hate":{"filtered":false,"severity":"safe"},"protected_material_code":{"filtered":false,"detected":false},"protected_material_text":{"filtered":false,"detected":false},"self_harm":{"filtered":false,"severity":"safe"},"sexual":{"filtered":false,"severity":"safe"},"violence":{"filtered":false,"severity":"safe"}},"finish_reason":"stop","index":0,"logprobs":null,"message":{"annotations":[],"content":"test","refusal":null,"role":"assistant"}}],"created":1769914196,"id":"chatcmpl-D4Hp6luPR9bsYIFkduSGMqqbGnXXG","model":"AZURE_DEPLOYMENT-2025-04-14","object":"chat.completion","prompt_filter_results":[{"prompt_index":0,"content_filter_results":{"hate":{"filtered":false,"severity":"safe"},"jailbreak":{"filtered":false,"detected":false},"self_harm":{"filtered":false,"severity":"safe"},"sexual":{"filtered":false,"severity":"safe"},"violence":{"filtered":false,"severity":"safe"}}}],"system_fingerprint":"fp_f99638a8d7","usage":{"completion_tokens":2,"completion_tokens_details":{"accepted_prediction_tokens":0,"audio_tokens":0,"reasoning_tokens":0,"rejected_prediction_tokens":0},"prompt_tokens":13,"prompt_tokens_details":{"audio_tokens":0,"cached_tokens":0},"total_tokens":15}} + + ' + recorded_at: Sun, 01 Feb 2026 02:49:57 GMT +recorded_with: VCR 6.4.0 diff --git a/test/fixtures/vcr_cassettes/integration/azure/response_test/usage.yml b/test/fixtures/vcr_cassettes/integration/azure/response_test/usage.yml new file mode 100644 index 00000000..6e2675aa --- /dev/null +++ b/test/fixtures/vcr_cassettes/integration/azure/response_test/usage.yml @@ -0,0 +1,84 @@ +--- +http_interactions: +- request: + method: post + uri: https://AZURE_RESOURCE.openai.azure.com/openai/deployments/AZURE_DEPLOYMENT/chat/completions?api-version=2025-01-01-preview + body: + encoding: UTF-8 + string: '{"model":"gpt-4","messages":[{"role":"user","content":"Say ''test'' + once."}],"max_completion_tokens":10}' + headers: + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - application/json + User-Agent: + - ActiveAgent::Providers::AzureProvider::AzureClient/Ruby 0.43.0 + Host: + - AZURE_RESOURCE.openai.azure.com + X-Stainless-Arch: + - arm64 + X-Stainless-Lang: + - ruby + X-Stainless-Os: + - MacOS + X-Stainless-Package-Version: + - 0.43.0 + X-Stainless-Runtime: + - ruby + X-Stainless-Runtime-Version: + - 3.3.8 + Content-Type: + - application/json + Api-Key: + - AZURE_API_KEY + X-Stainless-Retry-Count: + - '0' + X-Stainless-Timeout: + - '600.0' + Content-Length: + - '102' + response: + status: + code: 200 + message: OK + headers: + Content-Length: + - '1210' + Content-Type: + - application/json + Apim-Request-Id: + - 0566e3f9-cc5d-45ef-997c-f88caca819e3 + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + X-Content-Type-Options: + - nosniff + X-Ms-Region: + - West Europe + X-Ratelimit-Remaining-Requests: + - '41' + X-Ratelimit-Limit-Requests: + - '50' + X-Ratelimit-Remaining-Tokens: + - '49869' + X-Ratelimit-Limit-Tokens: + - '50000' + Azureml-Model-Session: + - d20260129130029-90b0c3ab1d00 + X-Accel-Buffering: + - 'no' + X-Ms-Rai-Invoked: + - 'true' + X-Request-Id: + - 854d8ec4-a353-4c26-bfe5-d0ca9de93378 + X-Ms-Deployment-Name: + - AZURE_DEPLOYMENT + Date: + - Sun, 01 Feb 2026 02:49:53 GMT + body: + encoding: UTF-8 + string: '{"choices":[{"content_filter_results":{"hate":{"filtered":false,"severity":"safe"},"protected_material_code":{"filtered":false,"detected":false},"protected_material_text":{"filtered":false,"detected":false},"self_harm":{"filtered":false,"severity":"safe"},"sexual":{"filtered":false,"severity":"safe"},"violence":{"filtered":false,"severity":"safe"}},"finish_reason":"stop","index":0,"logprobs":null,"message":{"annotations":[],"content":"test","refusal":null,"role":"assistant"}}],"created":1769914193,"id":"chatcmpl-D4Hp3l5ZIrlTTRAYMlQZ1WxUorXNy","model":"AZURE_DEPLOYMENT-2025-04-14","object":"chat.completion","prompt_filter_results":[{"prompt_index":0,"content_filter_results":{"hate":{"filtered":false,"severity":"safe"},"jailbreak":{"filtered":false,"detected":false},"self_harm":{"filtered":false,"severity":"safe"},"sexual":{"filtered":false,"severity":"safe"},"violence":{"filtered":false,"severity":"safe"}}}],"system_fingerprint":"fp_f99638a8d7","usage":{"completion_tokens":2,"completion_tokens_details":{"accepted_prediction_tokens":0,"audio_tokens":0,"reasoning_tokens":0,"rejected_prediction_tokens":0},"prompt_tokens":13,"prompt_tokens_details":{"audio_tokens":0,"cached_tokens":0},"total_tokens":15}} + + ' + recorded_at: Sun, 01 Feb 2026 02:49:54 GMT +recorded_with: VCR 6.4.0 diff --git a/test/integration/azure/common_format/instructions_test.rb b/test/integration/azure/common_format/instructions_test.rb new file mode 100644 index 00000000..b3c9a06a --- /dev/null +++ b/test/integration/azure/common_format/instructions_test.rb @@ -0,0 +1,103 @@ +# frozen_string_literal: true + +require_relative "../../test_helper" + +module Integration + module Azure + module CommonFormat + class InstructionsTest < ActiveSupport::TestCase + include Integration::TestHelper + + class TestAgent < ActiveAgent::Base + generate_with :azure_openai, + api_key: ENV["AZURE_OPENAI_API_KEY"], + azure_resource: ENV["AZURE_OPENAI_RESOURCE"], + deployment_id: ENV["AZURE_OPENAI_DEPLOYMENT_ID"], + model: ENV.fetch("AZURE_OPENAI_MODEL", "gpt-4") + + # Single instruction as string + def single_instruction + prompt( + instructions: "You are a helpful assistant that speaks like a pirate.", + message: "What is the capital of France?" + ) + end + + # Multiple instructions as array + def multiple_instructions + prompt( + instructions: [ + "You are a helpful assistant.", + "Always respond in exactly 3 words." + ], + message: "What is 2+2?" + ) + end + + # System message with user message + def system_with_user + prompt( + messages: [ + { role: "system", content: "You are a code reviewer. Be concise." }, + { role: "user", content: "Review this: puts 'hello'" } + ] + ) + end + + # Developer message (OpenAI-style system) + def developer_message + prompt( + messages: [ + { role: "developer", content: "Respond only with JSON." }, + { role: "user", content: "List 3 colours" } + ] + ) + end + end + + setup do + skip "Azure OpenAI credentials not configured" unless has_azure_openai_credentials? + end + + test "single instruction is applied to response" do + VCR.use_cassette("integration/azure/common_format/instructions_test/single_instruction") do + response = TestAgent.single_instruction.generate_now + + assert response.success? + assert_not_nil response.message.content + # Should mention Paris and possibly have pirate-speak + content = response.message.content.downcase + assert_includes content, "paris" + end + end + + test "multiple instructions are applied" do + VCR.use_cassette("integration/azure/common_format/instructions_test/multiple_instructions") do + response = TestAgent.multiple_instructions.generate_now + + assert response.success? + assert_not_nil response.message.content + end + end + + test "system message sets context" do + VCR.use_cassette("integration/azure/common_format/instructions_test/system_with_user") do + response = TestAgent.system_with_user.generate_now + + assert response.success? + assert_not_nil response.message.content + end + end + + test "developer message works as system" do + VCR.use_cassette("integration/azure/common_format/instructions_test/developer_message") do + response = TestAgent.developer_message.generate_now + + assert response.success? + assert_not_nil response.message.content + end + end + end + end + end +end diff --git a/test/integration/azure/common_format/messages_test.rb b/test/integration/azure/common_format/messages_test.rb new file mode 100644 index 00000000..7dc55e9b --- /dev/null +++ b/test/integration/azure/common_format/messages_test.rb @@ -0,0 +1,127 @@ +# frozen_string_literal: true + +require_relative "../../test_helper" + +module Integration + module Azure + module CommonFormat + class MessagesTest < ActiveSupport::TestCase + include Integration::TestHelper + + class TestAgent < ActiveAgent::Base + generate_with :azure_openai, + api_key: ENV["AZURE_OPENAI_API_KEY"], + azure_resource: ENV["AZURE_OPENAI_RESOURCE"], + deployment_id: ENV["AZURE_OPENAI_DEPLOYMENT_ID"], + model: ENV.fetch("AZURE_OPENAI_MODEL", "gpt-4") + + TEXT_BARE = { + model: ENV.fetch("AZURE_OPENAI_MODEL", "gpt-4"), + messages: [ + { + role: "user", + content: "What is the capital of France?" + } + ] + } + def text_bare + prompt("What is the capital of France?") + end + + TEXT_MESSAGE_BARE = { + model: ENV.fetch("AZURE_OPENAI_MODEL", "gpt-4"), + messages: [ + { + role: "user", + content: "Explain quantum computing in simple terms." + } + ] + } + def text_message_bare + prompt(message: "Explain quantum computing in simple terms.") + end + + TEXT_MESSAGE_OBJECT = { + model: ENV.fetch("AZURE_OPENAI_MODEL", "gpt-4"), + messages: [ + { + role: "user", + content: "What are the main differences between Ruby and Python?" + } + ] + } + def text_message_object + prompt(message: { text: "What are the main differences between Ruby and Python?" }) + end + + TEXT_MESSAGES_OBJECT = { + model: ENV.fetch("AZURE_OPENAI_MODEL", "gpt-4"), + messages: [ + { + role: "assistant", + content: "I can help you with programming questions." + }, + { + role: "user", + content: "What are the benefits of using ActiveRecord?" + } + ] + } + def text_messages_object + prompt(messages: [ + { + role: "assistant", + text: "I can help you with programming questions." + }, + { + text: "What are the benefits of using ActiveRecord?" + } + ]) + end + end + + setup do + skip "Azure OpenAI credentials not configured" unless has_azure_openai_credentials? + end + + test "text_bare creates correct request and returns response" do + VCR.use_cassette("integration/azure/common_format/messages_test/text_bare") do + response = TestAgent.text_bare.generate_now + + assert response.success? + assert_not_nil response.message.content + # Should mention Paris + assert_includes response.message.content.downcase, "paris" + end + end + + test "text_message_bare creates correct request and returns response" do + VCR.use_cassette("integration/azure/common_format/messages_test/text_message_bare") do + response = TestAgent.text_message_bare.generate_now + + assert response.success? + assert_not_nil response.message.content + end + end + + test "text_message_object creates correct request and returns response" do + VCR.use_cassette("integration/azure/common_format/messages_test/text_message_object") do + response = TestAgent.text_message_object.generate_now + + assert response.success? + assert_not_nil response.message.content + end + end + + test "text_messages_object preserves conversation context" do + VCR.use_cassette("integration/azure/common_format/messages_test/text_messages_object") do + response = TestAgent.text_messages_object.generate_now + + assert response.success? + assert_not_nil response.message.content + end + end + end + end + end +end diff --git a/test/integration/azure/common_format/tools_test.rb b/test/integration/azure/common_format/tools_test.rb new file mode 100644 index 00000000..00356682 --- /dev/null +++ b/test/integration/azure/common_format/tools_test.rb @@ -0,0 +1,175 @@ +# frozen_string_literal: true + +require_relative "../../test_helper" + +module Integration + module Azure + module CommonFormat + class ToolsTest < ActiveSupport::TestCase + include Integration::TestHelper + + class TestAgent < ActiveAgent::Base + generate_with :azure_openai, + api_key: ENV["AZURE_OPENAI_API_KEY"], + azure_resource: ENV["AZURE_OPENAI_RESOURCE"], + deployment_id: ENV["AZURE_OPENAI_DEPLOYMENT_ID"], + model: ENV.fetch("AZURE_OPENAI_MODEL", "gpt-4") + + def get_weather(location:) + { location: location, temperature: "72°F", conditions: "sunny" } + end + + def calculate(operation:, a:, b:) + result = case operation + when "add" then a + b + when "subtract" then a - b + when "multiply" then a * b + when "divide" then a / b + end + { operation: operation, a: a, b: b, result: result } + end + + # Simple tool with parameters + def tool_with_parameters + prompt( + message: "What's the weather in San Francisco?", + tools: [ + { + name: "get_weather", + description: "Get the current weather in a given location", + parameters: { + type: "object", + properties: { + location: { + type: "string", + description: "The city and state, e.g. San Francisco, CA" + } + }, + required: ["location"] + } + } + ] + ) + end + + # Multiple tools + def multiple_tools + prompt( + message: "What's the weather in NYC and what's 5 plus 3?", + tools: [ + { + name: "get_weather", + description: "Get the current weather", + parameters: { + type: "object", + properties: { + location: { type: "string" } + }, + required: ["location"] + } + }, + { + name: "calculate", + description: "Perform basic arithmetic", + parameters: { + type: "object", + properties: { + operation: { type: "string", enum: ["add", "subtract", "multiply", "divide"] }, + a: { type: "number" }, + b: { type: "number" } + }, + required: ["operation", "a", "b"] + } + } + ] + ) + end + + # Tool choice auto + def tool_choice_auto + prompt( + message: "What's the weather in London?", + tools: [ + { + name: "get_weather", + description: "Get weather", + parameters: { + type: "object", + properties: { + location: { type: "string" } + }, + required: ["location"] + } + } + ], + tool_choice: "auto" + ) + end + + # Tool choice required + def tool_choice_required + prompt( + message: "What's the weather?", + tools: [ + { + name: "get_weather", + description: "Get weather", + parameters: { + type: "object", + properties: { + location: { type: "string" } + }, + required: ["location"] + } + } + ], + tool_choice: "required" + ) + end + end + + setup do + skip "Azure OpenAI credentials not configured" unless has_azure_openai_credentials? + end + + test "tool with parameters triggers function call" do + VCR.use_cassette("integration/azure/common_format/tools_test/tool_with_parameters") do + response = TestAgent.tool_with_parameters.generate_now + + assert response.success? + # The response should either have tool calls or a message about weather + assert_not_nil response.message + end + end + + test "multiple tools can be provided" do + VCR.use_cassette("integration/azure/common_format/tools_test/multiple_tools") do + response = TestAgent.multiple_tools.generate_now + + assert response.success? + assert_not_nil response.message + end + end + + test "tool_choice auto allows model to decide" do + VCR.use_cassette("integration/azure/common_format/tools_test/tool_choice_auto") do + response = TestAgent.tool_choice_auto.generate_now + + assert response.success? + assert_not_nil response.message + end + end + + test "tool_choice required forces tool use" do + VCR.use_cassette("integration/azure/common_format/tools_test/tool_choice_required") do + response = TestAgent.tool_choice_required.generate_now + + assert response.success? + # With required, the model must use a tool + assert_not_nil response.message + end + end + end + end + end +end diff --git a/test/integration/azure/response_test.rb b/test/integration/azure/response_test.rb new file mode 100644 index 00000000..c085248e --- /dev/null +++ b/test/integration/azure/response_test.rb @@ -0,0 +1,76 @@ +# frozen_string_literal: true + +require_relative "../test_helper" + +module Integration + module Azure + class ResponseTest < ActiveSupport::TestCase + include Integration::TestHelper + + # Azure requires these environment variables: + # - AZURE_OPENAI_API_KEY: Your Azure OpenAI API key + # - AZURE_OPENAI_RESOURCE: Your Azure resource name (e.g., "mycompany") + # - AZURE_OPENAI_DEPLOYMENT_ID: Your deployment name (e.g., "gpt-4-deployment") + + class PromptAgent < ActiveAgent::Base + generate_with :azure_openai, + api_key: ENV["AZURE_OPENAI_API_KEY"], + azure_resource: ENV["AZURE_OPENAI_RESOURCE"], + deployment_id: ENV["AZURE_OPENAI_DEPLOYMENT_ID"], + model: ENV.fetch("AZURE_OPENAI_MODEL", "gpt-4") + + def simple_prompt + prompt( + messages: [{ role: "user", content: "Say 'test' once." }], + max_completion_tokens: 10 + ) + end + + def hello_world + prompt("Say hello world") + end + end + + setup do + skip "Azure OpenAI credentials not configured" unless has_azure_openai_credentials? + end + + test "chat completion response has correct structure" do + VCR.use_cassette("integration/azure/response_test/simple_prompt") do + response = PromptAgent.simple_prompt.generate_now + + # Validate response structure + assert_instance_of ActiveAgent::Providers::Common::Responses::Prompt, response + assert response.success? + assert_not_nil response.raw_response + + # Validate messages + assert_operator response.messages.length, :>, 0 + assert_instance_of ActiveAgent::Providers::Common::Messages::Assistant, response.message + end + end + + test "basic hello world prompt works" do + VCR.use_cassette("integration/azure/response_test/hello_world") do + response = PromptAgent.hello_world.generate_now + + assert response.success? + assert_not_nil response.message.content + assert_includes response.message.content.downcase, "hello" + end + end + + test "response includes usage information" do + VCR.use_cassette("integration/azure/response_test/usage") do + response = PromptAgent.simple_prompt.generate_now + + assert response.success? + assert_not_nil response.raw_response + assert_not_nil response.raw_response[:usage] + assert_not_nil response.raw_response[:usage][:prompt_tokens] + assert_not_nil response.raw_response[:usage][:completion_tokens] + end + end + end + end +end diff --git a/test/providers/azure/azure_provider_test.rb b/test/providers/azure/azure_provider_test.rb new file mode 100644 index 00000000..a366bd56 --- /dev/null +++ b/test/providers/azure/azure_provider_test.rb @@ -0,0 +1,72 @@ +# frozen_string_literal: true + +require "test_helper" +require_relative "../../../lib/active_agent/providers/azure_provider" + +class AzureProviderTest < ActiveSupport::TestCase + setup do + @valid_config = { + service: "AzureOpenAI", + api_key: "test-api-key", + azure_resource: "mycompany", + deployment_id: "gpt-4-deployment", + api_version: "2024-10-21", + messages: [{ role: "user", content: "Hello" }] + } + end + + test "service_name returns AzureOpenAI" do + assert_equal "AzureOpenAI", ActiveAgent::Providers::AzureProvider.service_name + end + + test "options_klass returns Azure::Options" do + assert_equal( + ActiveAgent::Providers::Azure::Options, + ActiveAgent::Providers::AzureProvider.options_klass + ) + end + + test "prompt_request_type returns OpenAI::Chat::RequestType" do + request_type = ActiveAgent::Providers::AzureProvider.prompt_request_type + + assert_instance_of ActiveAgent::Providers::OpenAI::Chat::RequestType, request_type + end + + test "embed_request_type returns OpenAI::Embedding::RequestType" do + request_type = ActiveAgent::Providers::AzureProvider.embed_request_type + + assert_instance_of ActiveAgent::Providers::OpenAI::Embedding::RequestType, request_type + end + + test "initializes provider with valid configuration" do + provider = ActiveAgent::Providers::AzureProvider.new(@valid_config) + + assert_instance_of ActiveAgent::Providers::AzureProvider, provider + end + + test "client is configured with Azure-specific settings" do + provider = ActiveAgent::Providers::AzureProvider.new(@valid_config) + client = provider.client + + assert_kind_of ::OpenAI::Client, client + assert_instance_of ActiveAgent::Providers::AzureProvider::AzureClient, client + end + + test "inherits from OpenAI::ChatProvider" do + assert ActiveAgent::Providers::AzureProvider < ActiveAgent::Providers::OpenAI::ChatProvider + end + + test "AzureClient stores api_version" do + provider = ActiveAgent::Providers::AzureProvider.new(@valid_config) + client = provider.client + + assert_equal "2024-10-21", client.api_version + end + + test "AzureOpenAIProvider is an alias for AzureProvider" do + assert_equal( + ActiveAgent::Providers::AzureProvider, + ActiveAgent::Providers::AzureOpenAIProvider + ) + end +end diff --git a/test/providers/azure/options_test.rb b/test/providers/azure/options_test.rb new file mode 100644 index 00000000..41889eb9 --- /dev/null +++ b/test/providers/azure/options_test.rb @@ -0,0 +1,143 @@ +# frozen_string_literal: true + +require "test_helper" +require_relative "../../../lib/active_agent/providers/azure_provider" + +class AzureOptionsTest < ActiveSupport::TestCase + setup do + @valid_options = { + api_key: "test-api-key", + azure_resource: "mycompany", + deployment_id: "gpt-4-deployment" + } + end + + test "initializes with valid options" do + original_version = ENV["AZURE_OPENAI_API_VERSION"] + ENV.delete("AZURE_OPENAI_API_VERSION") + + options = ActiveAgent::Providers::Azure::Options.new(@valid_options) + + assert_equal "test-api-key", options.api_key + assert_equal "mycompany", options.azure_resource + assert_equal "gpt-4-deployment", options.deployment_id + assert_equal "2024-10-21", options.api_version + ensure + ENV["AZURE_OPENAI_API_VERSION"] = original_version + end + + test "allows custom api_version" do + options = ActiveAgent::Providers::Azure::Options.new( + @valid_options.merge(api_version: "2024-02-15-preview") + ) + + assert_equal "2024-02-15-preview", options.api_version + end + + test "builds correct base_url" do + options = ActiveAgent::Providers::Azure::Options.new(@valid_options) + + assert_equal( + "https://mycompany.openai.azure.com/openai/deployments/gpt-4-deployment", + options.base_url + ) + end + + test "returns correct extra_headers with api-key" do + options = ActiveAgent::Providers::Azure::Options.new(@valid_options) + + assert_equal({ "api-key" => "test-api-key" }, options.extra_headers) + end + + test "returns correct extra_query with api-version" do + original_version = ENV["AZURE_OPENAI_API_VERSION"] + ENV.delete("AZURE_OPENAI_API_VERSION") + + options = ActiveAgent::Providers::Azure::Options.new(@valid_options) + + assert_equal({ "api-version" => "2024-10-21" }, options.extra_query) + ensure + ENV["AZURE_OPENAI_API_VERSION"] = original_version + end + + test "validates presence of api_key" do + original_key = ENV["AZURE_OPENAI_API_KEY"] + ENV.delete("AZURE_OPENAI_API_KEY") + + options = ActiveAgent::Providers::Azure::Options.new( + @valid_options.except(:api_key) + ) + + assert_not options.valid? + assert_includes options.errors[:api_key], "can't be blank" + ensure + ENV["AZURE_OPENAI_API_KEY"] = original_key + end + + test "validates presence of azure_resource" do + options = ActiveAgent::Providers::Azure::Options.new( + @valid_options.except(:azure_resource) + ) + + assert_not options.valid? + assert_includes options.errors[:azure_resource], "can't be blank" + end + + test "validates presence of deployment_id" do + options = ActiveAgent::Providers::Azure::Options.new( + @valid_options.except(:deployment_id) + ) + + assert_not options.valid? + assert_includes options.errors[:deployment_id], "can't be blank" + end + + test "resolves api_key from environment variable" do + original_key = ENV["AZURE_OPENAI_API_KEY"] + ENV["AZURE_OPENAI_API_KEY"] = "env-api-key" + + options = ActiveAgent::Providers::Azure::Options.new( + azure_resource: "mycompany", + deployment_id: "gpt-4" + ) + + assert_equal "env-api-key", options.api_key + ensure + ENV["AZURE_OPENAI_API_KEY"] = original_key + end + + test "prefers explicit api_key over environment variable" do + original_key = ENV["AZURE_OPENAI_API_KEY"] + ENV["AZURE_OPENAI_API_KEY"] = "env-api-key" + + options = ActiveAgent::Providers::Azure::Options.new(@valid_options) + + assert_equal "test-api-key", options.api_key + ensure + ENV["AZURE_OPENAI_API_KEY"] = original_key + end + + test "resolves api_version from environment variable" do + original_version = ENV["AZURE_OPENAI_API_VERSION"] + ENV["AZURE_OPENAI_API_VERSION"] = "2025-01-01-preview" + + options = ActiveAgent::Providers::Azure::Options.new(@valid_options) + + assert_equal "2025-01-01-preview", options.api_version + ensure + ENV["AZURE_OPENAI_API_VERSION"] = original_version + end + + test "prefers explicit api_version over environment variable" do + original_version = ENV["AZURE_OPENAI_API_VERSION"] + ENV["AZURE_OPENAI_API_VERSION"] = "2025-01-01-preview" + + options = ActiveAgent::Providers::Azure::Options.new( + @valid_options.merge(api_version: "2024-02-15-preview") + ) + + assert_equal "2024-02-15-preview", options.api_version + ensure + ENV["AZURE_OPENAI_API_VERSION"] = original_version + end +end diff --git a/test/providers/azure/provider_loading_test.rb b/test/providers/azure/provider_loading_test.rb new file mode 100644 index 00000000..013a19dc --- /dev/null +++ b/test/providers/azure/provider_loading_test.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +require "test_helper" + +class AzureProviderLoadingTest < ActiveSupport::TestCase + test "loads AzureProvider via azure_provider path" do + require "active_agent/providers/azure_provider" + + assert defined?(ActiveAgent::Providers::AzureProvider) + assert defined?(ActiveAgent::Providers::Azure::Options) + end + + test "loads AzureProvider via azure_open_ai_provider path" do + require "active_agent/providers/azure_open_ai_provider" + + assert defined?(ActiveAgent::Providers::AzureProvider) + end + + test "loads AzureProvider via azureopenai_provider path" do + require "active_agent/providers/azureopenai_provider" + + assert defined?(ActiveAgent::Providers::AzureProvider) + end + + test "provider concern loads AzureOpenAI service correctly" do + # Simulate how the provider concern loads providers + service_name = "AzureOpenAI" + require "active_agent/providers/#{service_name.underscore}_provider" + + # Check the remap works + remaps = ActiveAgent::Provider::PROVIDER_SERVICE_NAMES_REMAPS + remapped = Hash.new(service_name).merge!(remaps)[service_name] + + assert_equal "AzureOpenAI", remapped + + # The provider concern looks up "#{service_name.camelize}Provider" + provider_class = ActiveAgent::Providers.const_get("#{remapped.camelize}Provider") + assert_equal ActiveAgent::Providers::AzureProvider, provider_class + end + + test "service name remap handles AzureOpenai variation" do + remaps = ActiveAgent::Provider::PROVIDER_SERVICE_NAMES_REMAPS + + assert_equal "AzureOpenAI", remaps["AzureOpenai"] + assert_equal "AzureOpenAI", remaps["Azureopenai"] + end +end diff --git a/test/test_helper.rb b/test/test_helper.rb index 55047d33..483dde8e 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -107,6 +107,16 @@ def doc_example_output(example = nil, test_name = nil) config.filter_sensitive_data("ACCESS_TOKEN") { ENV["OPEN_ROUTER_ACCESS_TOKEN"] } config.filter_sensitive_data("ACCESS_TOKEN") { ENV["ANTHROPIC_ACCESS_TOKEN"] } config.filter_sensitive_data("GITHUB_MCP_TOKEN") { ENV["GITHUB_MCP_TOKEN"] } + + # Azure OpenAI credentials + config.filter_sensitive_data("AZURE_API_KEY") { ENV["AZURE_OPENAI_API_KEY"] } + config.filter_sensitive_data("AZURE_RESOURCE") { ENV["AZURE_OPENAI_RESOURCE"] } + config.filter_sensitive_data("AZURE_DEPLOYMENT") { ENV["AZURE_OPENAI_DEPLOYMENT_ID"] } + + # Filter Azure resource name from URLs + if ENV["AZURE_OPENAI_RESOURCE"] + config.filter_sensitive_data("azure-resource") { ENV["AZURE_OPENAI_RESOURCE"] } + end end # Load fixtures from the engine @@ -161,6 +171,8 @@ def has_provider_credentials?(provider) has_openrouter_credentials? when :ollama has_ollama_credentials? + when :azure, :azure_openai + has_azure_openai_credentials? else false end @@ -193,4 +205,10 @@ def has_ollama_credentials? # In real tests, you might want to actually ping the server host.present? end + + def has_azure_openai_credentials? + ENV["AZURE_OPENAI_API_KEY"].present? && + ENV["AZURE_OPENAI_RESOURCE"].present? && + ENV["AZURE_OPENAI_DEPLOYMENT_ID"].present? + end end From 604f3ba2367d8c0df55710dbf56744229a1622fe Mon Sep 17 00:00:00 2001 From: Neil Jobbins <3514122+TheRealNeil@users.noreply.github.com> Date: Sat, 31 Jan 2026 19:05:23 -0800 Subject: [PATCH 2/2] Make Rubocop happy :) --- test/integration/azure/common_format/tools_test.rb | 12 ++++++------ test/integration/azure/response_test.rb | 2 +- test/providers/azure/azure_provider_test.rb | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/test/integration/azure/common_format/tools_test.rb b/test/integration/azure/common_format/tools_test.rb index 00356682..a00c7efe 100644 --- a/test/integration/azure/common_format/tools_test.rb +++ b/test/integration/azure/common_format/tools_test.rb @@ -45,7 +45,7 @@ def tool_with_parameters description: "The city and state, e.g. San Francisco, CA" } }, - required: ["location"] + required: [ "location" ] } } ] @@ -65,7 +65,7 @@ def multiple_tools properties: { location: { type: "string" } }, - required: ["location"] + required: [ "location" ] } }, { @@ -74,11 +74,11 @@ def multiple_tools parameters: { type: "object", properties: { - operation: { type: "string", enum: ["add", "subtract", "multiply", "divide"] }, + operation: { type: "string", enum: [ "add", "subtract", "multiply", "divide" ] }, a: { type: "number" }, b: { type: "number" } }, - required: ["operation", "a", "b"] + required: [ "operation", "a", "b" ] } } ] @@ -98,7 +98,7 @@ def tool_choice_auto properties: { location: { type: "string" } }, - required: ["location"] + required: [ "location" ] } } ], @@ -119,7 +119,7 @@ def tool_choice_required properties: { location: { type: "string" } }, - required: ["location"] + required: [ "location" ] } } ], diff --git a/test/integration/azure/response_test.rb b/test/integration/azure/response_test.rb index c085248e..c99dbaea 100644 --- a/test/integration/azure/response_test.rb +++ b/test/integration/azure/response_test.rb @@ -21,7 +21,7 @@ class PromptAgent < ActiveAgent::Base def simple_prompt prompt( - messages: [{ role: "user", content: "Say 'test' once." }], + messages: [ { role: "user", content: "Say 'test' once." } ], max_completion_tokens: 10 ) end diff --git a/test/providers/azure/azure_provider_test.rb b/test/providers/azure/azure_provider_test.rb index a366bd56..a66eb4d8 100644 --- a/test/providers/azure/azure_provider_test.rb +++ b/test/providers/azure/azure_provider_test.rb @@ -11,7 +11,7 @@ class AzureProviderTest < ActiveSupport::TestCase azure_resource: "mycompany", deployment_id: "gpt-4-deployment", api_version: "2024-10-21", - messages: [{ role: "user", content: "Hello" }] + messages: [ { role: "user", content: "Hello" } ] } end