22 def chat(messages, options)
23 api_key = options.api_key || ENV["OPENAI_API_KEY"]
24 raise Rager::Errors::CredentialsError.new("OpenAI", env_var: ["OPENAI_API_KEY"]) if api_key.nil?
25
26 base_url = options.url || ENV["OPENAI_URL"] || "https://api.openai.com/v1"
27
28 body = {
29 model: options.model || "gpt-4.1",
30 messages: build_openai_messages(messages, options.history, options.system_prompt)
31 }.tap do |b|
32 b[:n] = options.n if options.n
33 b[:max_tokens] = options.max_tokens if options.max_tokens
34 b[:temperature] = options.temperature if options.temperature
35 b[:stream] = options.stream if options.stream
36 b[:seed] = options.seed if options.seed
37
38 if options.schema && options.schema_name
39 b[:response_format] = {
40 type: "json_schema",
41 json_schema: {
42 name: T.must(options.schema_name).downcase,
43 strict: true,
44 schema: Rager::Chat::Schema.dry_schema_to_json_schema(T.must(options.schema))
45 }
46 }
47 end
48 end
49
50 headers = {"Content-Type" => "application/json"}
51 headers["Authorization"] = "Bearer #{api_key}" if api_key
52
53 request = Rager::Http::Request.new(
54 verb: Rager::Http::Verb::Post,
55 url: "#{base_url}/chat/completions",
56 headers: headers,
57 body: body.to_json,
58 streaming: options.stream || false,
59 timeout: options.timeout || Rager.config.timeout
60 )
61
62 response = Rager.config.http_adapter.make_request(request)
63 response_body = T.must(response.body)
64
65 raise Rager::Errors::HttpError.new(Rager.config.http_adapter, request.url, response.status, body: T.cast(response_body, String)) if response.status != 200
66
67 case response_body
68 when String then handle_non_stream_body(response_body)
69 when Enumerator then handle_stream_body(response_body)
70 end
71 end