ticuna 0.2.5 → 0.2.7

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: ad184878c7d2fd8d8ee366a2de9996588b74ed1729bc21cc0b22fab7ffe7abb5
4
- data.tar.gz: 4f4fae314169358a091dbb9dfafdc712807ec3f35b27634434d06b587d6080b6
3
+ metadata.gz: 55e07fd5840706e2936718a07ad6e6e6be5ae8681b6c8b763bac6c9c073d632f
4
+ data.tar.gz: '0118e2253d49b82acbbe08ed17145997330a9c42f30f276e87b7ff01136710d9'
5
5
  SHA512:
6
- metadata.gz: c5a0f5ced928c16bcefd52df2a904fa07c895f724aa49eb4621b99b97247034da6a8b64167297bc480e322faf667b5e8c3864f0dc8a4d31099a535c721159464
7
- data.tar.gz: e7d4fe0589f6d82554968f2a095048ac41c33f16c7ffb078e96d95024d3ddc52d13c6ab6dd218650ebbd1eec2d28e2f30d95c1820f63168ea6948ac68013f721
6
+ metadata.gz: 90467485b1691fdf24b14c442945f82f8b58db85b4ff47ee1f2403adf96b3fdd5efcd54fc94cd3c88068151037ac220058536797c9b407f2acd83b2a7edbab3e
7
+ data.tar.gz: d60b05c8b3e1306835c9996e91b88accfe8ca2fe7d1f47c0358d7ae2903a4c7b2bc2e6949007c6294cda3a312c9c5bbea81f9d9cc40095d830eea04c8c657031
data/CHANGELOG.md CHANGED
@@ -28,4 +28,11 @@
28
28
 
29
29
  ## [0.2.5] - 2025-10-13
30
30
  - Better messages
31
- - Add `errors?` method
31
+ - Add `errors?` method
32
+
33
+ ## [0.2.6] - 2025-10-22
34
+ - Add `context` method
35
+
36
+ ## [0.2.7] - 2025-10-22
37
+ - Add modular response, to attend any LLM model
38
+ - Add `.response` method in Ticuna::Response to ease access to the real response
data/lib/ticuna/llm.rb CHANGED
@@ -10,14 +10,14 @@ module Ticuna
10
10
  def self.new(provider = nil)
11
11
  provider_key = detect_provider(provider)
12
12
  provider_client = Ticuna::Providers::CLIENTS[provider_key].call
13
- new_instance = allocate
14
- new_instance.send(:initialize_llm, provider_client)
15
- new_instance
13
+ allocate.tap { |instance| instance.send(:initialize_llm, provider_key, provider_client) }
16
14
  end
17
15
 
18
- def initialize_llm(provider_client)
16
+ def initialize_llm(provider_key, provider_client)
17
+ @provider_key = provider_key
19
18
  @provider = provider_client
20
19
  @tools = []
20
+ @contexts = []
21
21
  end
22
22
 
23
23
  def tool(klass)
@@ -25,13 +25,21 @@ module Ticuna
25
25
  self
26
26
  end
27
27
 
28
+ def context(value = nil, &block)
29
+ value = block.call if block
30
+ @contexts << value if value
31
+ self
32
+ end
33
+
28
34
  def ask(message, stream: false, model: :gpt_4_1, output_format: :text, &block)
29
- tool_contexts = @tools.map(&:context).compact.join("\n\n")
35
+ tool_contexts = @tools.map(&:context).compact
36
+ direct_contexts = @contexts.compact
37
+ combined_contexts = (tool_contexts + direct_contexts).reject { |ctx| ctx.to_s.strip.empty? }
30
38
 
31
- system_message = if tool_contexts.empty?
39
+ system_message = if combined_contexts.empty?
32
40
  nil
33
41
  else
34
- { role: "developer", content: tool_contexts }
42
+ { role: "developer", content: combined_contexts.join("\n\n") }
35
43
  end
36
44
 
37
45
  messages = if system_message
@@ -43,7 +51,8 @@ module Ticuna
43
51
  model_string = detect_llm_model(model)
44
52
 
45
53
  Ticuna::Response.new(
46
- @provider.ask_with_messages(messages, stream: stream, model: model_string, output_format: output_format, &block)
54
+ @provider.ask_with_messages(messages, stream: stream, model: model_string, output_format: output_format, &block),
55
+ provider: @provider_key
47
56
  )
48
57
  end
49
58
 
@@ -7,6 +7,8 @@ module Ticuna
7
7
  class OpenAI < BaseProvider
8
8
  def initialize(api_key:)
9
9
  super(api_key: api_key, base_url: "https://api.openai.com/v1/")
10
+ @response = String.new
11
+ @raw_response = String.new
10
12
  end
11
13
 
12
14
  def ask(message, stream: false, model: "gpt-4.1-nano", output_format: :text, &block)
@@ -67,7 +69,8 @@ module Ticuna
67
69
  end
68
70
  end
69
71
 
70
- {
72
+ @response = full_text
73
+ @raw_response = {
71
74
  "id" => "streamed_completion_#{Time.now.to_i}",
72
75
  "object" => "chat.completion",
73
76
  "model" => body[:model],
@@ -81,9 +84,13 @@ module Ticuna
81
84
  }
82
85
  else
83
86
  resp = @connection.post(path, body.to_json)
84
- JSON.parse(resp.body)
87
+ @raw_response = JSON.parse(resp.body)
88
+ @response = @raw_response.dig("choices", 0, "message", "content")
85
89
  end
90
+
91
+ @raw_response
86
92
  end
93
+ attr_reader :response, :raw_response
87
94
  end
88
95
  end
89
96
  end
@@ -24,5 +24,9 @@ module Ticuna
24
24
  # deepseek: -> { Ticuna::Providers::DeepSeek.new(api_key: ENVS[:deepseek].call) },
25
25
  # mistral: -> { Ticuna::Providers::Mistral.new(api_key: ENVS[:mistral].call) }
26
26
  }.freeze
27
+
28
+ RESPONSE_EXTRACTORS = {
29
+ openai: ->(data) { data.dig(:choices, 0, :message, :content) }
30
+ }.freeze
27
31
  end
28
32
  end
@@ -1,12 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "json"
4
+ require "ticuna/providers"
4
5
 
5
6
  module Ticuna
6
7
  class Response
7
- attr_reader :data, :errors, :parsed
8
+ attr_reader :data, :errors, :parsed, :raw_response, :response, :provider
8
9
 
9
- def initialize(raw)
10
+ def initialize(raw, provider: nil)
11
+ @provider = provider
12
+ @raw_response = raw
10
13
  @parsed =
11
14
  case raw
12
15
  when String
@@ -22,7 +25,14 @@ module Ticuna
22
25
  end
23
26
 
24
27
  @data = deep_symbolize_keys(@parsed)
25
- @errors = deep_symbolize_keys(@parsed["error"] || [])
28
+ error_payload =
29
+ if @parsed.is_a?(Hash)
30
+ @parsed["error"] || @parsed[:error] || []
31
+ else
32
+ []
33
+ end
34
+ @errors = deep_symbolize_keys(error_payload)
35
+ @response = resolve_response
26
36
  end
27
37
 
28
38
  def [](key)
@@ -70,12 +80,36 @@ module Ticuna
70
80
  def wrap(value)
71
81
  case value
72
82
  when Hash
73
- self.class.new(value)
83
+ self.class.new(value, provider: provider)
74
84
  when Array
75
85
  value.map { |v| wrap(v) }
76
86
  else
77
87
  value
78
88
  end
79
89
  end
90
+
91
+ def resolve_response
92
+ from_provider = extract_from_provider
93
+ return from_provider unless blank?(from_provider)
94
+
95
+ fallback =
96
+ if @data.is_a?(Hash)
97
+ @data[:content] || @data[:response]
98
+ end
99
+ return fallback unless blank?(fallback)
100
+
101
+ @parsed if @parsed.is_a?(String)
102
+ end
103
+
104
+ def blank?(value)
105
+ value.respond_to?(:empty?) ? value.empty? : !value
106
+ end
107
+
108
+ def extract_from_provider
109
+ return unless provider
110
+
111
+ extractor = Ticuna::Providers::RESPONSE_EXTRACTORS[provider]
112
+ extractor&.call(@data)
113
+ end
80
114
  end
81
115
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Ticuna
4
- VERSION = "0.2.5"
4
+ VERSION = "0.2.7"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ticuna
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.5
4
+ version: 0.2.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chirana
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-10-13 00:00:00.000000000 Z
11
+ date: 2025-10-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday