perplexity_api 0.2.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e12f67af2fe793c9b3e55bab4bf16bb16ddc01340af2b6ed1ae89e54c0501a63
4
- data.tar.gz: adec45a5e5209bafef27fc2ce2dd8a098654b1c7ef32de6c52bd62629a40a14c
3
+ metadata.gz: 4f134dd6fd6ac47f11b94ed74f4a6c361de0859a73b7845c9c642769e4e340b0
4
+ data.tar.gz: b0e6576ed1173b6702d6382d2711f34b8ddae1570b2a793a19a216744b330ec1
5
5
  SHA512:
6
- metadata.gz: 1823a33101eea983a021a20c225a379193116bc0efbaffc8aff5a49afd45d111fcb096ef6a10da433a5b136c4f8469911c3b74a46744d9873d8a0fc39aa640f3
7
- data.tar.gz: 7d73bbe2a1d1b865bc5c72f440ebf7622df70001b0ac5f453d65432fc3668eaa507b67b6fcd3883baed28a6425d3aa130d3dfcd8f41f011732af71461fc4d7a3
6
+ metadata.gz: f891118c6640903373100e66654c59c15900eb36d4ed3d7c6b428e4c624b81d9930bd6b7569f39d1255753edf58acf664e57adbf80e92efe10bf189449b2586d
7
+ data.tar.gz: '0097f6120b11a3ddde782cd0673bb4442a9c45f7e308f16363821ed9ae11b0a455191712e7c2cf8813498271d6bad826cef22e930cee822d7338c7bb44f11186'
@@ -7,4 +7,6 @@ PERPLEXITY_API_KEY=your_api_key_here
7
7
  # PERPLEXITY_TEMPERATURE=0.7
8
8
  # PERPLEXITY_MAX_TOKENS=1024
9
9
  # PERPLEXITY_TOP_P=0.9
10
- # PERPLEXITY_TOP_K=0
10
+ # PERPLEXITY_TOP_K=0
11
+ # PERPLEXITY_FREQUENCY_PENALTY=0.0
12
+ # PERPLEXITY_PRESENCE_PENALTY=0.0
data/.gitignore CHANGED
@@ -55,3 +55,7 @@ build-iPhoneSimulator/
55
55
 
56
56
  # Used by RuboCop. Remote config files pulled in from inherit_from directive.
57
57
  # .rubocop-https?--*
58
+ /docs/
59
+ .rspec_status
60
+
61
+ test_*
data/CHANGELOG.md ADDED
@@ -0,0 +1,42 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ ## [0.3.0] - 2025-01-07
6
+
7
+ ### Added
8
+ - Streaming support via `StreamClient` class for real-time responses
9
+ - Web search capabilities with `search_mode` parameter (web/academic)
10
+ - Domain filtering with `search_domain_filter` (include/exclude domains)
11
+ - Date filtering with `search_after_date_filter` and `search_before_date_filter`
12
+ - Recency filtering with `search_recency_filter` (month/week/day/hour)
13
+ - Location-based search with `web_search_options`
14
+ - Support for new models: sonar-pro, sonar-deep-research
15
+ - Full conversation history support with messages array
16
+ - New parameters: `frequency_penalty` and `presence_penalty`
17
+ - Beta features: `return_images` and `return_related_questions`
18
+ - Model constants in `PerplexityApi::Models`
19
+ - Helper methods: `stream`, `stream_chat`
20
+ - Comprehensive examples directory
21
+
22
+ ### Changed
23
+ - `chat` method now accepts both string and array of messages
24
+ - `chat` method accepts options parameter for per-request configuration
25
+ - Updated default configuration to include new penalty parameters
26
+
27
+ ### Fixed
28
+ - Improved error handling for streaming responses
29
+
30
+ ## [0.2.1] - Previous version
31
+
32
+ ### Fixed
33
+ - Environment variable loading issues
34
+ - Configuration management improvements
35
+
36
+ ## [0.1.0] - Initial release
37
+
38
+ ### Added
39
+ - Basic chat functionality
40
+ - Configuration management
41
+ - Environment variable support
42
+ - Basic parameter support (temperature, max_tokens, top_p, top_k)
data/Gemfile.lock CHANGED
@@ -1,13 +1,13 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- perplexity_api (0.2.0)
4
+ perplexity_api (0.3.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
8
8
  specs:
9
9
  diff-lcs (1.6.1)
10
- rake (10.5.0)
10
+ rake (13.3.0)
11
11
  rspec (3.13.0)
12
12
  rspec-core (~> 3.13.0)
13
13
  rspec-expectations (~> 3.13.0)
@@ -26,10 +26,10 @@ PLATFORMS
26
26
  ruby
27
27
 
28
28
  DEPENDENCIES
29
- bundler (~> 1.17)
29
+ bundler (~> 2.0)
30
30
  perplexity_api!
31
- rake (~> 10.0)
31
+ rake (~> 13.0)
32
32
  rspec (~> 3.0)
33
33
 
34
34
  BUNDLED WITH
35
- 1.17.2
35
+ 2.6.9
data/README.md CHANGED
@@ -1,5 +1,5 @@
1
1
  # PerplexityApi
2
- [![Gem Version](https://badge.fury.io/rb/perplexity_api.svg)](https://badge.fury.io/rb/perplexity_api)
2
+ [![Gem Version](https://badge.fury.io/rb/perplexity_api.svg?v=0.2.1)](https://badge.fury.io/rb/perplexity_api)
3
3
  [![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE.txt)
4
4
 
5
5
  A Ruby wrapper gem for Perplexity AI's API. This gem allows you to easily integrate Perplexity AI's powerful language models into your Ruby applications.
@@ -7,8 +7,13 @@ A Ruby wrapper gem for Perplexity AI's API. This gem allows you to easily integr
7
7
  ## Features
8
8
 
9
9
  - API key can be configured externally
10
- - Ability to select the latest models
10
+ - Support for all latest Perplexity models including sonar-pro and sonar-deep-research
11
11
  - Simple interface to send messages and get results
12
+ - Streaming support for real-time responses
13
+ - Web search capabilities with domain filtering and date filters
14
+ - Conversation history support with full messages array
15
+ - Advanced search features including location-based search
16
+ - Beta features: image results and related questions
12
17
  - Options can be customized or use defaults
13
18
 
14
19
  ## Installation
@@ -85,6 +90,21 @@ response = PerplexityApi.chat("Hello, Perplexity AI!")
85
90
  puts response["choices"][0]["message"]["content"]
86
91
  ```
87
92
 
93
+ ### Conversation History
94
+
95
+ You can maintain conversation context by passing an array of messages:
96
+
97
+ ```ruby
98
+ messages = [
99
+ { role: "system", content: "You are a helpful assistant." },
100
+ { role: "user", content: "What is the capital of France?" },
101
+ { role: "assistant", content: "The capital of France is Paris." },
102
+ { role: "user", content: "What is its population?" }
103
+ ]
104
+
105
+ response = PerplexityApi.chat(messages)
106
+ ```
107
+
88
108
  ### Using a Client Instance
89
109
 
90
110
  For more detailed control, you can create a client instance:
@@ -97,7 +117,10 @@ client = PerplexityApi.new(
97
117
  temperature: 0.5,
98
118
  max_tokens: 2048,
99
119
  top_p: 0.9,
100
- top_k: 0
120
+ top_k: 0,
121
+ frequency_penalty: 0.1,
122
+ presence_penalty: 0.1,
123
+ search_mode: "web"
101
124
  }
102
125
  )
103
126
 
@@ -105,8 +128,106 @@ response = client.chat("Enter your complex question here...")
105
128
  puts response["choices"][0]["message"]["content"]
106
129
  ```
107
130
 
131
+ ### Streaming Responses
132
+
133
+ For real-time streaming responses:
134
+
135
+ ```ruby
136
+ PerplexityApi.stream_chat("Tell me about Ruby programming") do |chunk|
137
+ print chunk["choices"][0]["delta"]["content"] if chunk["choices"][0]["delta"]["content"]
138
+ end
139
+ ```
140
+
141
+ Or with a client instance:
142
+
143
+ ```ruby
144
+ client = PerplexityApi.stream
145
+ client.chat("Explain quantum computing") do |chunk|
146
+ # Process each chunk as it arrives
147
+ end
148
+ ```
149
+
150
+ ### Web Search Features
151
+
152
+ Enable web search with specific filters:
153
+
154
+ ```ruby
155
+ response = PerplexityApi.chat(
156
+ "What are the latest developments in AI?",
157
+ options: {
158
+ search_mode: "web",
159
+ search_domain_filter: ["arxiv.org", "nature.com", "-reddit.com"],
160
+ search_recency_filter: "week"
161
+ }
162
+ )
163
+ ```
164
+
165
+ ### Advanced Search Options
166
+
167
+ Use date filters and location-based search:
168
+
169
+ ```ruby
170
+ response = PerplexityApi.chat(
171
+ "Find research papers on climate change",
172
+ options: {
173
+ search_mode: "academic",
174
+ search_after_date_filter: "01/01/2024",
175
+ search_before_date_filter: "12/31/2024",
176
+ web_search_options: {
177
+ search_context_size: "high",
178
+ user_location: {
179
+ country: "US",
180
+ latitude: 37.7749,
181
+ longitude: -122.4194
182
+ }
183
+ }
184
+ }
185
+ )
186
+ ```
187
+
188
+ ### Beta Features
189
+
190
+ Enable image results and related questions (closed beta):
191
+
192
+ ```ruby
193
+ response = PerplexityApi.chat(
194
+ "Show me images of the Eiffel Tower",
195
+ options: {
196
+ return_images: true,
197
+ return_related_questions: true
198
+ }
199
+ )
200
+
201
+ # Access images if available
202
+ if response["images"]
203
+ response["images"].each do |image|
204
+ puts "Image URL: #{image["url"]}"
205
+ end
206
+ end
207
+
208
+ # Access related questions
209
+ if response["related_questions"]
210
+ puts "Related questions:"
211
+ response["related_questions"].each { |q| puts "- #{q}" }
212
+ end
213
+ ```
214
+
108
215
  ## Models
109
216
 
217
+ The gem includes constants for all available models:
218
+
219
+ ```ruby
220
+ # Current Sonar Models
221
+ PerplexityApi::Models::SONAR # "sonar"
222
+ PerplexityApi::Models::SONAR_PRO # "sonar-pro"
223
+ PerplexityApi::Models::SONAR_DEEP_RESEARCH # "sonar-deep-research"
224
+
225
+ # Other Models
226
+ PerplexityApi::Models::LLAMA_3_1_70B_INSTRUCT # "llama-3.1-70b-instruct"
227
+ PerplexityApi::Models::MISTRAL_7B # "mistral-7b"
228
+ PerplexityApi::Models::CODELLAMA_34B # "codellama-34b"
229
+ ```
230
+
110
231
  For the most up-to-date list of models, refer to the [Perplexity AI official documentation](https://docs.perplexity.ai/guides/model-cards).
111
232
 
112
233
  ## Development
@@ -0,0 +1,25 @@
1
+ require 'perplexity_api'
2
+
3
+ # Basic chat
4
+ puts "=== Basic Chat ==="
5
+ response = PerplexityApi.chat("What is Ruby programming language?")
6
+ puts response["choices"][0]["message"]["content"]
7
+ puts
8
+
9
+ # Chat with options
10
+ puts "=== Chat with Custom Options ==="
11
+ response = PerplexityApi.chat(
12
+ "Tell me about the benefits of Ruby",
13
+ options: {
14
+ temperature: 0.3,
15
+ max_tokens: 500
16
+ }
17
+ )
18
+ puts response["choices"][0]["message"]["content"]
19
+ puts
20
+
21
+ # Using specific model
22
+ puts "=== Using Sonar Pro Model ==="
23
+ client = PerplexityApi.new(model: PerplexityApi::Models::SONAR_PRO)
24
+ response = client.chat("What are the latest features in Ruby 3.3?")
25
+ puts response["choices"][0]["message"]["content"]
@@ -0,0 +1,34 @@
1
+ require 'perplexity_api'
2
+
3
+ # Multi-turn conversation
4
+ puts "=== Multi-turn Conversation ==="
5
+
6
+ messages = [
7
+ { role: "system", content: "You are a helpful Ruby programming expert." },
8
+ { role: "user", content: "What is a Ruby gem?" }
9
+ ]
10
+
11
+ # First response
12
+ response = PerplexityApi.chat(messages)
13
+ assistant_message = response["choices"][0]["message"]["content"]
14
+ puts "User: What is a Ruby gem?"
15
+ puts "Assistant: #{assistant_message}"
16
+ puts
17
+
18
+ # Add assistant response to conversation
19
+ messages << { role: "assistant", content: assistant_message }
20
+ messages << { role: "user", content: "How do I create my own gem?" }
21
+
22
+ # Second response
23
+ response = PerplexityApi.chat(messages)
24
+ puts "User: How do I create my own gem?"
25
+ puts "Assistant: #{response["choices"][0]["message"]["content"]}"
26
+ puts
27
+
28
+ # Continue the conversation
29
+ messages << { role: "assistant", content: response["choices"][0]["message"]["content"] }
30
+ messages << { role: "user", content: "What about publishing it to RubyGems?" }
31
+
32
+ response = PerplexityApi.chat(messages)
33
+ puts "User: What about publishing it to RubyGems?"
34
+ puts "Assistant: #{response["choices"][0]["message"]["content"]}"
@@ -0,0 +1,26 @@
1
+ require 'perplexity_api'
2
+
3
+ # Basic streaming
4
+ puts "=== Basic Streaming ==="
5
+ PerplexityApi.stream_chat("Write a haiku about programming") do |chunk|
6
+ if chunk["choices"] && chunk["choices"][0]["delta"]["content"]
7
+ print chunk["choices"][0]["delta"]["content"]
8
+ end
9
+ end
10
+ puts "\n"
11
+
12
+ # Streaming with web search
13
+ puts "\n=== Streaming with Web Search ==="
14
+ client = PerplexityApi.stream(
15
+ options: {
16
+ search_mode: "web",
17
+ search_recency_filter: "day"
18
+ }
19
+ )
20
+
21
+ client.chat("What happened in tech news today?") do |chunk|
22
+ if chunk["choices"] && chunk["choices"][0]["delta"]["content"]
23
+ print chunk["choices"][0]["delta"]["content"]
24
+ end
25
+ end
26
+ puts "\n"
@@ -0,0 +1,45 @@
1
+ require 'perplexity_api'
2
+
3
+ # Web search with domain filtering
4
+ puts "=== Web Search with Domain Filtering ==="
5
+ response = PerplexityApi.chat(
6
+ "What are the latest AI research papers?",
7
+ options: {
8
+ search_mode: "web",
9
+ search_domain_filter: ["arxiv.org", "openai.com", "-reddit.com"],
10
+ search_recency_filter: "week"
11
+ }
12
+ )
13
+ puts response["choices"][0]["message"]["content"]
14
+ puts
15
+
16
+ # Academic search
17
+ puts "=== Academic Search ==="
18
+ response = PerplexityApi.chat(
19
+ "Find recent studies on climate change mitigation",
20
+ options: {
21
+ search_mode: "academic",
22
+ search_after_date_filter: "01/01/2024",
23
+ search_before_date_filter: "12/31/2024"
24
+ }
25
+ )
26
+ puts response["choices"][0]["message"]["content"]
27
+ puts
28
+
29
+ # Location-based search
30
+ puts "=== Location-based Search ==="
31
+ response = PerplexityApi.chat(
32
+ "What are the best restaurants near me?",
33
+ options: {
34
+ search_mode: "web",
35
+ web_search_options: {
36
+ search_context_size: "high",
37
+ user_location: {
38
+ country: "US",
39
+ latitude: 37.7749,
40
+ longitude: -122.4194
41
+ }
42
+ }
43
+ }
44
+ )
45
+ puts response["choices"][0]["message"]["content"]
@@ -14,9 +14,12 @@ module PerplexityApi
14
14
  end
15
15
 
16
16
  # Method to send a message and get a response
17
- def chat(message)
17
+ def chat(messages, options = {})
18
18
  @config.validate!
19
19
 
20
+ messages = prepare_messages(messages)
21
+ merged_options = @options.merge(options)
22
+
20
23
  uri = URI.parse("#{@config.api_base}/chat/completions")
21
24
  http = Net::HTTP.new(uri.host, uri.port)
22
25
  http.use_ssl = true
@@ -25,14 +28,8 @@ module PerplexityApi
25
28
  request["Content-Type"] = "application/json"
26
29
  request["Authorization"] = "Bearer #{@config.api_key}"
27
30
 
28
- request.body = {
29
- model: @model,
30
- messages: [{ role: "user", content: message }],
31
- temperature: @options[:temperature],
32
- max_tokens: @options[:max_tokens],
33
- top_p: @options[:top_p],
34
- top_k: @options[:top_k]
35
- }.to_json
31
+ request_body = build_request_body(messages, merged_options)
32
+ request.body = request_body.to_json
36
33
 
37
34
  response = http.request(request)
38
35
 
@@ -42,5 +39,56 @@ module PerplexityApi
42
39
  raise Error, "API call failed: #{response.code} #{response.body}"
43
40
  end
44
41
  end
42
+
43
+ private
44
+
45
+ def prepare_messages(messages)
46
+ case messages
47
+ when String
48
+ [{ role: "user", content: messages }]
49
+ when Array
50
+ messages
51
+ else
52
+ raise ArgumentError, "Messages must be a string or array"
53
+ end
54
+ end
55
+
56
+ def build_request_body(messages, options)
57
+ body = {
58
+ model: @model,
59
+ messages: messages
60
+ }
61
+
62
+ # Basic parameters
63
+ body[:temperature] = options[:temperature] if options[:temperature]
64
+ body[:max_tokens] = options[:max_tokens] if options[:max_tokens]
65
+ body[:top_p] = options[:top_p] if options[:top_p]
66
+ body[:top_k] = options[:top_k] if options[:top_k]
67
+ body[:frequency_penalty] = options[:frequency_penalty] if options[:frequency_penalty]
68
+ body[:presence_penalty] = options[:presence_penalty] if options[:presence_penalty]
69
+ body[:stream] = options[:stream] if options.key?(:stream)
70
+
71
+ # Search parameters
72
+ body[:search_mode] = options[:search_mode] if options[:search_mode]
73
+ body[:search_domain_filter] = options[:search_domain_filter] if options[:search_domain_filter]
74
+ body[:search_recency_filter] = options[:search_recency_filter] if options[:search_recency_filter]
75
+ body[:search_after_date_filter] = options[:search_after_date_filter] if options[:search_after_date_filter]
76
+ body[:search_before_date_filter] = options[:search_before_date_filter] if options[:search_before_date_filter]
77
+ body[:last_updated_after_filter] = options[:last_updated_after_filter] if options[:last_updated_after_filter]
78
+ body[:last_updated_before_filter] = options[:last_updated_before_filter] if options[:last_updated_before_filter]
79
+
80
+ # Beta features
81
+ body[:return_images] = options[:return_images] if options[:return_images]
82
+ body[:return_related_questions] = options[:return_related_questions] if options[:return_related_questions]
83
+
84
+ # Advanced features
85
+ body[:reasoning_effort] = options[:reasoning_effort] if options[:reasoning_effort]
86
+
87
+ if options[:web_search_options]
88
+ body[:web_search_options] = options[:web_search_options]
89
+ end
90
+
91
+ body
92
+ end
45
93
  end
46
94
  end
@@ -17,7 +17,9 @@ module PerplexityApi
17
17
  temperature: ENV["PERPLEXITY_TEMPERATURE"] ? ENV["PERPLEXITY_TEMPERATURE"].to_f : 0.7,
18
18
  max_tokens: ENV["PERPLEXITY_MAX_TOKENS"] ? ENV["PERPLEXITY_MAX_TOKENS"].to_i : 1024,
19
19
  top_p: ENV["PERPLEXITY_TOP_P"] ? ENV["PERPLEXITY_TOP_P"].to_f : 0.9,
20
- top_k: ENV["PERPLEXITY_TOP_K"] ? ENV["PERPLEXITY_TOP_K"].to_i : 0
20
+ top_k: ENV["PERPLEXITY_TOP_K"] ? ENV["PERPLEXITY_TOP_K"].to_i : 0,
21
+ frequency_penalty: ENV["PERPLEXITY_FREQUENCY_PENALTY"] ? ENV["PERPLEXITY_FREQUENCY_PENALTY"].to_f : 0.0,
22
+ presence_penalty: ENV["PERPLEXITY_PRESENCE_PENALTY"] ? ENV["PERPLEXITY_PRESENCE_PENALTY"].to_f : 0.0
21
23
  }
22
24
 
23
25
  debug_log "Configuration loaded from environment variables"
@@ -0,0 +1,44 @@
1
+ module PerplexityApi
2
+ module Models
3
+ # Current Sonar Models
4
+ SONAR = "sonar"
5
+ SONAR_PRO = "sonar-pro"
6
+ SONAR_DEEP_RESEARCH = "sonar-deep-research"
7
+
8
+ # Legacy Models
9
+ LLAMA_3_1_SONAR_SMALL_128K_CHAT = "llama-3.1-sonar-small-128k-chat"
10
+ LLAMA_3_1_SONAR_LARGE_128K_CHAT = "llama-3.1-sonar-large-128k-chat"
11
+ LLAMA_3_1_70B_INSTRUCT = "llama-3.1-70b-instruct"
12
+ LLAMA_3_1_8B_INSTRUCT = "llama-3.1-8b-instruct"
13
+ MISTRAL_7B = "mistral-7b"
14
+ CODELLAMA_34B = "codellama-34b"
15
+ LLAMA_2_70B = "llama-2-70b"
16
+
17
+ # Model Groups
18
+ SONAR_MODELS = [SONAR, SONAR_PRO, SONAR_DEEP_RESEARCH].freeze
19
+ LLAMA_MODELS = [
20
+ LLAMA_3_1_SONAR_SMALL_128K_CHAT,
21
+ LLAMA_3_1_SONAR_LARGE_128K_CHAT,
22
+ LLAMA_3_1_70B_INSTRUCT,
23
+ LLAMA_3_1_8B_INSTRUCT,
24
+ LLAMA_2_70B
25
+ ].freeze
26
+
27
+ ALL_MODELS = (SONAR_MODELS + LLAMA_MODELS + [MISTRAL_7B, CODELLAMA_34B]).freeze
28
+
29
+ # Search modes
30
+ SEARCH_MODE_WEB = "web"
31
+ SEARCH_MODE_ACADEMIC = "academic"
32
+
33
+ # Search recency filters
34
+ RECENCY_MONTH = "month"
35
+ RECENCY_WEEK = "week"
36
+ RECENCY_DAY = "day"
37
+ RECENCY_HOUR = "hour"
38
+
39
+ # Search context sizes
40
+ CONTEXT_SIZE_LOW = "low"
41
+ CONTEXT_SIZE_MEDIUM = "medium"
42
+ CONTEXT_SIZE_HIGH = "high"
43
+ end
44
+ end
@@ -0,0 +1,115 @@
1
+ require 'net/http'
2
+ require 'uri'
3
+ require 'json'
4
+
5
+ module PerplexityApi
6
+ class StreamClient
7
+ attr_reader :config
8
+
9
+ def initialize(api_key: nil, model: nil, options: {})
10
+ @config = PerplexityApi.configuration.dup
11
+ @config.api_key = api_key if api_key != nil
12
+ @model = model || @config.default_model
13
+ @options = @config.default_options.merge(options)
14
+ end
15
+
16
+ def chat(messages, &block)
17
+ @config.validate!
18
+
19
+ messages = prepare_messages(messages)
20
+
21
+ uri = URI.parse("#{@config.api_base}/chat/completions")
22
+
23
+ Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
24
+ request = Net::HTTP::Post.new(uri.path)
25
+ request["Content-Type"] = "application/json"
26
+ request["Authorization"] = "Bearer #{@config.api_key}"
27
+ request["Accept"] = "text/event-stream"
28
+ request["Cache-Control"] = "no-cache"
29
+
30
+ request_body = build_request_body(messages)
31
+ request_body[:stream] = true
32
+ request.body = request_body.to_json
33
+
34
+ http.request(request) do |response|
35
+ if response.code.to_i != 200
36
+ raise Error, "API call failed: #{response.code} #{response.read_body}"
37
+ end
38
+
39
+ buffer = ""
40
+ response.read_body do |chunk|
41
+ buffer += chunk
42
+
43
+ while (line_end = buffer.index("\n"))
44
+ line = buffer[0...line_end]
45
+ buffer = buffer[(line_end + 1)..-1]
46
+
47
+ next if line.strip.empty?
48
+ next unless line.start_with?("data: ")
49
+
50
+ data = line[6..-1].strip
51
+ next if data == "[DONE]"
52
+
53
+ begin
54
+ parsed = JSON.parse(data)
55
+ block.call(parsed) if block_given?
56
+ rescue JSON::ParserError => e
57
+ # Skip invalid JSON
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
64
+
65
+ private
66
+
67
+ def prepare_messages(messages)
68
+ case messages
69
+ when String
70
+ [{ role: "user", content: messages }]
71
+ when Array
72
+ messages
73
+ else
74
+ raise ArgumentError, "Messages must be a string or array"
75
+ end
76
+ end
77
+
78
+ def build_request_body(messages)
79
+ body = {
80
+ model: @model,
81
+ messages: messages
82
+ }
83
+
84
+ # Basic parameters
85
+ body[:temperature] = @options[:temperature] if @options[:temperature]
86
+ body[:max_tokens] = @options[:max_tokens] if @options[:max_tokens]
87
+ body[:top_p] = @options[:top_p] if @options[:top_p]
88
+ body[:top_k] = @options[:top_k] if @options[:top_k]
89
+ body[:frequency_penalty] = @options[:frequency_penalty] if @options[:frequency_penalty]
90
+ body[:presence_penalty] = @options[:presence_penalty] if @options[:presence_penalty]
91
+
92
+ # Search parameters
93
+ body[:search_mode] = @options[:search_mode] if @options[:search_mode]
94
+ body[:search_domain_filter] = @options[:search_domain_filter] if @options[:search_domain_filter]
95
+ body[:search_recency_filter] = @options[:search_recency_filter] if @options[:search_recency_filter]
96
+ body[:search_after_date_filter] = @options[:search_after_date_filter] if @options[:search_after_date_filter]
97
+ body[:search_before_date_filter] = @options[:search_before_date_filter] if @options[:search_before_date_filter]
98
+ body[:last_updated_after_filter] = @options[:last_updated_after_filter] if @options[:last_updated_after_filter]
99
+ body[:last_updated_before_filter] = @options[:last_updated_before_filter] if @options[:last_updated_before_filter]
100
+
101
+ # Beta features
102
+ body[:return_images] = @options[:return_images] if @options[:return_images]
103
+ body[:return_related_questions] = @options[:return_related_questions] if @options[:return_related_questions]
104
+
105
+ # Advanced features
106
+ body[:reasoning_effort] = @options[:reasoning_effort] if @options[:reasoning_effort]
107
+
108
+ if @options[:web_search_options]
109
+ body[:web_search_options] = @options[:web_search_options]
110
+ end
111
+
112
+ body
113
+ end
114
+ end
115
+ end
@@ -1,3 +1,3 @@
1
1
  module PerplexityApi
2
- VERSION = "0.2.1"
2
+ VERSION = "0.4.0"
3
3
  end
@@ -1,6 +1,8 @@
1
1
  require "perplexity_api/version"
2
2
  require "perplexity_api/configuration"
3
+ require "perplexity_api/models"
3
4
  require "perplexity_api/client"
5
+ require "perplexity_api/stream_client"
4
6
 
5
7
  module PerplexityApi
6
8
  class Error < StandardError; end
@@ -11,9 +13,20 @@ module PerplexityApi
11
13
  end
12
14
 
13
15
  # Helper method to directly send a message
14
- def self.chat(message, api_key: nil, model: nil, options: {})
16
+ def self.chat(messages, api_key: nil, model: nil, options: {})
15
17
  client = Client.new(api_key: api_key, model: model, options: options)
16
- client.chat(message)
18
+ client.chat(messages, options)
19
+ end
20
+
21
+ # Helper method to create a stream client instance
22
+ def self.stream(api_key: nil, model: nil, options: {})
23
+ StreamClient.new(api_key: api_key, model: model, options: options)
24
+ end
25
+
26
+ # Helper method to directly stream a message
27
+ def self.stream_chat(messages, api_key: nil, model: nil, options: {}, &block)
28
+ client = StreamClient.new(api_key: api_key, model: model, options: options)
29
+ client.chat(messages, &block)
17
30
  end
18
31
 
19
32
  # Helper method to get available models
@@ -35,9 +35,9 @@ Gem::Specification.new do |spec|
35
35
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
36
36
  spec.require_paths = ["lib"]
37
37
 
38
- spec.add_development_dependency "bundler", "~> 1.17"
39
- spec.add_development_dependency "rake", "~> 10.0"
38
+ spec.add_development_dependency "bundler", "~> 2.0"
39
+ spec.add_development_dependency "rake", "~> 13.0"
40
40
  spec.add_development_dependency "rspec", "~> 3.0"
41
41
 
42
- spec.required_ruby_version = ">= 2.6.0"
42
+ spec.required_ruby_version = ">= 3.1.0"
43
43
  end
metadata CHANGED
@@ -1,13 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: perplexity_api
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Hisafumi Kikkawa
8
+ autorequire:
8
9
  bindir: exe
9
10
  cert_chain: []
10
- date: 2025-03-27 00:00:00.000000000 Z
11
+ date: 2025-07-07 00:00:00.000000000 Z
11
12
  dependencies:
12
13
  - !ruby/object:Gem::Dependency
13
14
  name: bundler
@@ -15,28 +16,28 @@ dependencies:
15
16
  requirements:
16
17
  - - "~>"
17
18
  - !ruby/object:Gem::Version
18
- version: '1.17'
19
+ version: '2.0'
19
20
  type: :development
20
21
  prerelease: false
21
22
  version_requirements: !ruby/object:Gem::Requirement
22
23
  requirements:
23
24
  - - "~>"
24
25
  - !ruby/object:Gem::Version
25
- version: '1.17'
26
+ version: '2.0'
26
27
  - !ruby/object:Gem::Dependency
27
28
  name: rake
28
29
  requirement: !ruby/object:Gem::Requirement
29
30
  requirements:
30
31
  - - "~>"
31
32
  - !ruby/object:Gem::Version
32
- version: '10.0'
33
+ version: '13.0'
33
34
  type: :development
34
35
  prerelease: false
35
36
  version_requirements: !ruby/object:Gem::Requirement
36
37
  requirements:
37
38
  - - "~>"
38
39
  - !ruby/object:Gem::Version
39
- version: '10.0'
40
+ version: '13.0'
40
41
  - !ruby/object:Gem::Dependency
41
42
  name: rspec
42
43
  requirement: !ruby/object:Gem::Requirement
@@ -59,11 +60,11 @@ executables: []
59
60
  extensions: []
60
61
  extra_rdoc_files: []
61
62
  files:
62
- - ".env.sample"
63
+ - ".env.example"
63
64
  - ".gitignore"
64
65
  - ".rspec"
65
- - ".rspec_status"
66
66
  - ".travis.yml"
67
+ - CHANGELOG.md
67
68
  - CODE_OF_CONDUCT.md
68
69
  - Gemfile
69
70
  - Gemfile.lock
@@ -73,9 +74,15 @@ files:
73
74
  - Rakefile
74
75
  - bin/console
75
76
  - bin/setup
77
+ - examples/basic_usage.rb
78
+ - examples/conversation.rb
79
+ - examples/streaming.rb
80
+ - examples/web_search.rb
76
81
  - lib/perplexity_api.rb
77
82
  - lib/perplexity_api/client.rb
78
83
  - lib/perplexity_api/configuration.rb
84
+ - lib/perplexity_api/models.rb
85
+ - lib/perplexity_api/stream_client.rb
79
86
  - lib/perplexity_api/version.rb
80
87
  - perplexity_api.gemspec
81
88
  homepage: https://github.com/hisafumi-kikkawa/perplexity_api
@@ -86,6 +93,7 @@ metadata:
86
93
  homepage_uri: https://github.com/hisafumi-kikkawa/perplexity_api
87
94
  source_code_uri: https://github.com/hisafumi-kikkawa/perplexity_api
88
95
  changelog_uri: https://github.com/hisafumi-kikkawa/perplexity_api/blob/master/CHANGELOG.md
96
+ post_install_message:
89
97
  rdoc_options: []
90
98
  require_paths:
91
99
  - lib
@@ -93,14 +101,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
93
101
  requirements:
94
102
  - - ">="
95
103
  - !ruby/object:Gem::Version
96
- version: 2.6.0
104
+ version: 3.1.0
97
105
  required_rubygems_version: !ruby/object:Gem::Requirement
98
106
  requirements:
99
107
  - - ">="
100
108
  - !ruby/object:Gem::Version
101
109
  version: '0'
102
110
  requirements: []
103
- rubygems_version: 3.6.2
111
+ rubygems_version: 3.3.3
112
+ signing_key:
104
113
  specification_version: 4
105
114
  summary: Ruby wrapper for Perplexity API
106
115
  test_files: []
data/.rspec_status DELETED
@@ -1,24 +0,0 @@
1
- example_id | status | run_time |
2
- ---------------------------------------------------- | ------ | --------------- |
3
- ./spec/perplexity_api/client_spec.rb[1:1:1:1] | passed | 0.0228 seconds |
4
- ./spec/perplexity_api/client_spec.rb[1:1:2:1] | passed | 0.0001 seconds |
5
- ./spec/perplexity_api/client_spec.rb[1:2:1] | passed | 0.00063 seconds |
6
- ./spec/perplexity_api/client_spec.rb[1:2:2] | passed | 0.00161 seconds |
7
- ./spec/perplexity_api/client_spec.rb[1:2:3:1] | passed | 0.0053 seconds |
8
- ./spec/perplexity_api/client_spec.rb[1:2:4:1] | passed | 0.00045 seconds |
9
- ./spec/perplexity_api/configuration_spec.rb[1:1:1:1] | passed | 0.0081 seconds |
10
- ./spec/perplexity_api/configuration_spec.rb[1:1:2:1] | passed | 0.00034 seconds |
11
- ./spec/perplexity_api/configuration_spec.rb[1:1:3:1] | passed | 0.00192 seconds |
12
- ./spec/perplexity_api/configuration_spec.rb[1:2:1] | passed | 0.00362 seconds |
13
- ./spec/perplexity_api/configuration_spec.rb[1:3:1:1] | passed | 0.00084 seconds |
14
- ./spec/perplexity_api/configuration_spec.rb[1:3:2:1] | passed | 0.00007 seconds |
15
- ./spec/perplexity_api/configuration_spec.rb[2:1:1] | passed | 0.00073 seconds |
16
- ./spec/perplexity_api/configuration_spec.rb[2:1:2] | passed | 0.00006 seconds |
17
- ./spec/perplexity_api/configuration_spec.rb[2:2:1] | passed | 0.00739 seconds |
18
- ./spec/perplexity_api/configuration_spec.rb[2:2:2] | passed | 0.0001 seconds |
19
- ./spec/perplexity_api/configuration_spec.rb[2:3:1] | passed | 0.00102 seconds |
20
- ./spec/perplexity_api_spec.rb[1:1] | passed | 0.00011 seconds |
21
- ./spec/perplexity_api_spec.rb[1:2:1] | passed | 0.00011 seconds |
22
- ./spec/perplexity_api_spec.rb[1:3:1] | passed | 0.00017 seconds |
23
- ./spec/perplexity_api_spec.rb[1:3:2] | passed | 0.00013 seconds |
24
- ./spec/perplexity_api_spec.rb[1:4:1:1] | passed | 0.00151 seconds |