liter_llm 1.6.4 → 1.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/ext/liter_llm_rb/native/Cargo.lock +3 -3
- data/ext/liter_llm_rb/native/Cargo.toml +2 -2
- data/ext/liter_llm_rb/src/lib.rs +449 -23
- data/lib/liter_llm/native.rb +137 -2
- data/lib/liter_llm/version.rb +2 -2
- data/lib/liter_llm.rb +1 -1
- data/sig/types.rbs +32 -6
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 336f60aa43a86dbbddd88a10084f24180cc2e02dda02294754e66203b7d2698d
|
|
4
|
+
data.tar.gz: '0816afd2387ade4af23e63e05f24d970da571a7f799f41399548e49ea42fa885'
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 777e16f5ebecd0e90576f0714fc1b7b8f6bf41e8b7ccb6f23734d581bd2909a0c99c2279a9edcd45c56f742116915c7b226cbfdda0054980cab2fbb3842c1f6a
|
|
7
|
+
data.tar.gz: 2008f0e64537acc3fa8578667dff3cc10c5fd45a008d40785dc74aa526113a285523e7eddc8b0a1c0839b7deceb904f7379d6a6accea4cdc1cb9ebdbe629b676
|
|
@@ -1875,9 +1875,9 @@ checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0"
|
|
|
1875
1875
|
|
|
1876
1876
|
[[package]]
|
|
1877
1877
|
name = "liter-llm"
|
|
1878
|
-
version = "1.
|
|
1878
|
+
version = "1.7.1"
|
|
1879
1879
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1880
|
-
checksum = "
|
|
1880
|
+
checksum = "0552ad2a855bd57c422933227a1d664ad66b1e773dd2244a14194500880ef544"
|
|
1881
1881
|
dependencies = [
|
|
1882
1882
|
"ahash 0.8.12",
|
|
1883
1883
|
"async-trait",
|
|
@@ -1917,7 +1917,7 @@ dependencies = [
|
|
|
1917
1917
|
|
|
1918
1918
|
[[package]]
|
|
1919
1919
|
name = "liter-llm-rb"
|
|
1920
|
-
version = "1.
|
|
1920
|
+
version = "1.7.1"
|
|
1921
1921
|
dependencies = [
|
|
1922
1922
|
"futures",
|
|
1923
1923
|
"liter-llm",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[package]
|
|
2
2
|
name = "liter-llm-rb"
|
|
3
|
-
version = "1.
|
|
3
|
+
version = "1.7.1"
|
|
4
4
|
edition = "2024"
|
|
5
5
|
license = "MIT"
|
|
6
6
|
description = "Universal LLM API client with Rust-powered polyglot bindings."
|
|
@@ -24,7 +24,7 @@ wasm-http = ["liter-llm/wasm-http"]
|
|
|
24
24
|
|
|
25
25
|
[dependencies]
|
|
26
26
|
futures = "0.3"
|
|
27
|
-
liter-llm = { version = "1.
|
|
27
|
+
liter-llm = { version = "1.7.1", features = ["native-http", "full"] }
|
|
28
28
|
magnus = "0.8"
|
|
29
29
|
rb-sys = ">=0.9, <0.9.128"
|
|
30
30
|
serde = { version = "1", features = ["derive"] }
|
data/ext/liter_llm_rb/src/lib.rs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// This file is auto-generated by alef. DO NOT EDIT.
|
|
2
|
-
// alef:hash:
|
|
2
|
+
// alef:hash:fec9b44a56d9714b7b12931bb48e1e8f6d57ef2d0ccaad1f9ac6046ce01a89af
|
|
3
3
|
// Re-generate with: alef generate
|
|
4
4
|
#![allow(dead_code, unused_imports, unused_variables)]
|
|
5
5
|
#![allow(
|
|
@@ -67,7 +67,7 @@ fn json_to_ruby(handle: &Ruby, val: serde_json::Value) -> magnus::Value {
|
|
|
67
67
|
#[serde(default)]
|
|
68
68
|
#[magnus::wrap(class = "LiterLlm::SystemMessage")]
|
|
69
69
|
pub struct SystemMessage {
|
|
70
|
-
content:
|
|
70
|
+
content: UserContent,
|
|
71
71
|
name: Option<String>,
|
|
72
72
|
}
|
|
73
73
|
|
|
@@ -101,7 +101,10 @@ unsafe impl TryConvertOwned for SystemMessage {}
|
|
|
101
101
|
|
|
102
102
|
impl Default for SystemMessage {
|
|
103
103
|
fn default() -> Self {
|
|
104
|
-
|
|
104
|
+
Self {
|
|
105
|
+
content: Default::default(),
|
|
106
|
+
name: None,
|
|
107
|
+
}
|
|
105
108
|
}
|
|
106
109
|
}
|
|
107
110
|
|
|
@@ -114,7 +117,7 @@ impl SystemMessage {
|
|
|
114
117
|
Ok(Self {
|
|
115
118
|
content: kwargs
|
|
116
119
|
.get(ruby.to_symbol("content"))
|
|
117
|
-
.and_then(|v|
|
|
120
|
+
.and_then(|v| UserContent::try_convert(v).ok())
|
|
118
121
|
.unwrap_or_default(),
|
|
119
122
|
name: kwargs
|
|
120
123
|
.get(ruby.to_symbol("name"))
|
|
@@ -122,18 +125,13 @@ impl SystemMessage {
|
|
|
122
125
|
})
|
|
123
126
|
}
|
|
124
127
|
|
|
125
|
-
fn content(&self) ->
|
|
128
|
+
fn content(&self) -> UserContent {
|
|
126
129
|
self.content.clone()
|
|
127
130
|
}
|
|
128
131
|
|
|
129
132
|
fn name(&self) -> Option<String> {
|
|
130
133
|
self.name.clone()
|
|
131
134
|
}
|
|
132
|
-
|
|
133
|
-
#[allow(clippy::should_implement_trait)]
|
|
134
|
-
fn to_s(&self) -> String {
|
|
135
|
-
self.content.clone()
|
|
136
|
-
}
|
|
137
135
|
}
|
|
138
136
|
|
|
139
137
|
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
|
|
@@ -420,7 +418,7 @@ impl AudioContent {
|
|
|
420
418
|
#[serde(default)]
|
|
421
419
|
#[magnus::wrap(class = "LiterLlm::AssistantMessage")]
|
|
422
420
|
pub struct AssistantMessage {
|
|
423
|
-
content: Option<
|
|
421
|
+
content: Option<AssistantContent>,
|
|
424
422
|
name: Option<String>,
|
|
425
423
|
tool_calls: Option<Vec<ToolCall>>,
|
|
426
424
|
refusal: Option<String>,
|
|
@@ -476,7 +474,7 @@ impl AssistantMessage {
|
|
|
476
474
|
Ok(Self {
|
|
477
475
|
content: kwargs
|
|
478
476
|
.get(ruby.to_symbol("content"))
|
|
479
|
-
.and_then(|v|
|
|
477
|
+
.and_then(|v| AssistantContent::try_convert(v).ok()),
|
|
480
478
|
name: kwargs
|
|
481
479
|
.get(ruby.to_symbol("name"))
|
|
482
480
|
.and_then(|v| String::try_convert(v).ok()),
|
|
@@ -492,7 +490,7 @@ impl AssistantMessage {
|
|
|
492
490
|
})
|
|
493
491
|
}
|
|
494
492
|
|
|
495
|
-
fn content(&self) -> Option<
|
|
493
|
+
fn content(&self) -> Option<AssistantContent> {
|
|
496
494
|
self.content.clone()
|
|
497
495
|
}
|
|
498
496
|
|
|
@@ -512,9 +510,64 @@ impl AssistantMessage {
|
|
|
512
510
|
self.function_call.clone()
|
|
513
511
|
}
|
|
514
512
|
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
513
|
+
fn text(&self) -> Option<String> {
|
|
514
|
+
let core_self = liter_llm::types::AssistantMessage {
|
|
515
|
+
content: self.content.clone().map(Into::into),
|
|
516
|
+
|
|
517
|
+
name: self.name.clone(),
|
|
518
|
+
|
|
519
|
+
tool_calls: self.tool_calls.clone().map(|v| v.into_iter().map(Into::into).collect()),
|
|
520
|
+
|
|
521
|
+
refusal: self.refusal.clone(),
|
|
522
|
+
|
|
523
|
+
function_call: self.function_call.clone().map(Into::into),
|
|
524
|
+
};
|
|
525
|
+
core_self.text()
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
fn refusal_text(&self) -> Option<String> {
|
|
529
|
+
let core_self = liter_llm::types::AssistantMessage {
|
|
530
|
+
content: self.content.clone().map(Into::into),
|
|
531
|
+
|
|
532
|
+
name: self.name.clone(),
|
|
533
|
+
|
|
534
|
+
tool_calls: self.tool_calls.clone().map(|v| v.into_iter().map(Into::into).collect()),
|
|
535
|
+
|
|
536
|
+
refusal: self.refusal.clone(),
|
|
537
|
+
|
|
538
|
+
function_call: self.function_call.clone().map(Into::into),
|
|
539
|
+
};
|
|
540
|
+
core_self.refusal_text().map(|v| v.to_owned())
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
fn output_images(&self) -> Vec<ImageUrl> {
|
|
544
|
+
let core_self = liter_llm::types::AssistantMessage {
|
|
545
|
+
content: self.content.clone().map(Into::into),
|
|
546
|
+
|
|
547
|
+
name: self.name.clone(),
|
|
548
|
+
|
|
549
|
+
tool_calls: self.tool_calls.clone().map(|v| v.into_iter().map(Into::into).collect()),
|
|
550
|
+
|
|
551
|
+
refusal: self.refusal.clone(),
|
|
552
|
+
|
|
553
|
+
function_call: self.function_call.clone().map(Into::into),
|
|
554
|
+
};
|
|
555
|
+
core_self.output_images().into_iter().map(Into::into).collect()
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
fn output_audio(&self) -> Vec<AudioContent> {
|
|
559
|
+
let core_self = liter_llm::types::AssistantMessage {
|
|
560
|
+
content: self.content.clone().map(Into::into),
|
|
561
|
+
|
|
562
|
+
name: self.name.clone(),
|
|
563
|
+
|
|
564
|
+
tool_calls: self.tool_calls.clone().map(|v| v.into_iter().map(Into::into).collect()),
|
|
565
|
+
|
|
566
|
+
refusal: self.refusal.clone(),
|
|
567
|
+
|
|
568
|
+
function_call: self.function_call.clone().map(Into::into),
|
|
569
|
+
};
|
|
570
|
+
core_self.output_audio().into_iter().map(Into::into).collect()
|
|
518
571
|
}
|
|
519
572
|
}
|
|
520
573
|
|
|
@@ -1445,6 +1498,7 @@ pub struct ChatCompletionRequest {
|
|
|
1445
1498
|
stream_options: Option<StreamOptions>,
|
|
1446
1499
|
seed: Option<i64>,
|
|
1447
1500
|
reasoning_effort: Option<ReasoningEffort>,
|
|
1501
|
+
modalities: Option<Vec<Modality>>,
|
|
1448
1502
|
extra_body: Option<String>,
|
|
1449
1503
|
}
|
|
1450
1504
|
|
|
@@ -1498,6 +1552,7 @@ impl Default for ChatCompletionRequest {
|
|
|
1498
1552
|
stream_options: None,
|
|
1499
1553
|
seed: None,
|
|
1500
1554
|
reasoning_effort: None,
|
|
1555
|
+
modalities: None,
|
|
1501
1556
|
extra_body: None,
|
|
1502
1557
|
}
|
|
1503
1558
|
}
|
|
@@ -1567,6 +1622,9 @@ impl ChatCompletionRequest {
|
|
|
1567
1622
|
reasoning_effort: kwargs
|
|
1568
1623
|
.get(ruby.to_symbol("reasoning_effort"))
|
|
1569
1624
|
.and_then(|v| ReasoningEffort::try_convert(v).ok()),
|
|
1625
|
+
modalities: kwargs
|
|
1626
|
+
.get(ruby.to_symbol("modalities"))
|
|
1627
|
+
.and_then(|v| <Vec<Modality>>::try_convert(v).ok()),
|
|
1570
1628
|
extra_body: kwargs
|
|
1571
1629
|
.get(ruby.to_symbol("extra_body"))
|
|
1572
1630
|
.and_then(|v| String::try_convert(v).ok()),
|
|
@@ -1649,6 +1707,10 @@ impl ChatCompletionRequest {
|
|
|
1649
1707
|
self.reasoning_effort.clone()
|
|
1650
1708
|
}
|
|
1651
1709
|
|
|
1710
|
+
fn modalities(&self) -> Option<Vec<Modality>> {
|
|
1711
|
+
self.modalities.clone()
|
|
1712
|
+
}
|
|
1713
|
+
|
|
1652
1714
|
fn extra_body(&self) -> Option<String> {
|
|
1653
1715
|
self.extra_body.clone()
|
|
1654
1716
|
}
|
|
@@ -2885,6 +2947,75 @@ impl Image {
|
|
|
2885
2947
|
}
|
|
2886
2948
|
}
|
|
2887
2949
|
|
|
2950
|
+
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
|
|
2951
|
+
#[serde(default)]
|
|
2952
|
+
#[magnus::wrap(class = "LiterLlm::DecodedDataUrl")]
|
|
2953
|
+
pub struct DecodedDataUrl {
|
|
2954
|
+
mime: String,
|
|
2955
|
+
data: Vec<u8>,
|
|
2956
|
+
}
|
|
2957
|
+
|
|
2958
|
+
unsafe impl IntoValueFromNative for DecodedDataUrl {}
|
|
2959
|
+
|
|
2960
|
+
impl magnus::TryConvert for DecodedDataUrl {
|
|
2961
|
+
fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
|
|
2962
|
+
if let Ok(r) = <&DecodedDataUrl as magnus::TryConvert>::try_convert(val) {
|
|
2963
|
+
return Ok(r.clone());
|
|
2964
|
+
}
|
|
2965
|
+
let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
|
|
2966
|
+
s
|
|
2967
|
+
} else {
|
|
2968
|
+
val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
|
|
2969
|
+
magnus::Error::new(
|
|
2970
|
+
unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
|
|
2971
|
+
format!("no implicit conversion into DecodedDataUrl: {}", e),
|
|
2972
|
+
)
|
|
2973
|
+
})?
|
|
2974
|
+
};
|
|
2975
|
+
serde_json::from_str::<DecodedDataUrl>(&json_str).map_err(|e| {
|
|
2976
|
+
magnus::Error::new(
|
|
2977
|
+
unsafe { magnus::Ruby::get_unchecked() }.exception_type_error(),
|
|
2978
|
+
format!("failed to deserialize DecodedDataUrl: {}", e),
|
|
2979
|
+
)
|
|
2980
|
+
})
|
|
2981
|
+
}
|
|
2982
|
+
}
|
|
2983
|
+
|
|
2984
|
+
unsafe impl TryConvertOwned for DecodedDataUrl {}
|
|
2985
|
+
|
|
2986
|
+
impl Default for DecodedDataUrl {
|
|
2987
|
+
fn default() -> Self {
|
|
2988
|
+
liter_llm::image::DecodedDataUrl::default().into()
|
|
2989
|
+
}
|
|
2990
|
+
}
|
|
2991
|
+
|
|
2992
|
+
impl DecodedDataUrl {
|
|
2993
|
+
fn new(args: &[magnus::Value]) -> Result<Self, magnus::Error> {
|
|
2994
|
+
let ruby = unsafe { magnus::Ruby::get_unchecked() };
|
|
2995
|
+
let args = magnus::scan_args::scan_args::<(), (Option<magnus::RHash>,), (), (), (), ()>(args)?;
|
|
2996
|
+
let (kwargs_opt,) = args.optional;
|
|
2997
|
+
let kwargs = kwargs_opt.unwrap_or_else(|| ruby.hash_new());
|
|
2998
|
+
Ok(Self {
|
|
2999
|
+
mime: kwargs
|
|
3000
|
+
.get(ruby.to_symbol("mime"))
|
|
3001
|
+
.and_then(|v| String::try_convert(v).ok())
|
|
3002
|
+
.unwrap_or_default(),
|
|
3003
|
+
data: kwargs
|
|
3004
|
+
.get(ruby.to_symbol("data"))
|
|
3005
|
+
.and_then(|v| <Vec<u8>>::try_convert(v).ok())
|
|
3006
|
+
.unwrap_or_default(),
|
|
3007
|
+
})
|
|
3008
|
+
}
|
|
3009
|
+
|
|
3010
|
+
fn mime(&self) -> String {
|
|
3011
|
+
self.mime.clone()
|
|
3012
|
+
}
|
|
3013
|
+
|
|
3014
|
+
fn data(&self) -> Vec<u8> {
|
|
3015
|
+
self.data.clone()
|
|
3016
|
+
}
|
|
3017
|
+
}
|
|
3018
|
+
|
|
2888
3019
|
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
|
|
2889
3020
|
#[serde(default)]
|
|
2890
3021
|
#[magnus::wrap(class = "LiterLlm::CreateSpeechRequest")]
|
|
@@ -7710,6 +7841,122 @@ impl magnus::TryConvert for ImageDetail {
|
|
|
7710
7841
|
unsafe impl IntoValueFromNative for ImageDetail {}
|
|
7711
7842
|
unsafe impl TryConvertOwned for ImageDetail {}
|
|
7712
7843
|
|
|
7844
|
+
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
|
|
7845
|
+
#[serde(untagged)]
|
|
7846
|
+
pub enum AssistantContent {
|
|
7847
|
+
Text(String),
|
|
7848
|
+
Parts(Vec<AssistantPart>),
|
|
7849
|
+
}
|
|
7850
|
+
|
|
7851
|
+
impl Default for AssistantContent {
|
|
7852
|
+
fn default() -> Self {
|
|
7853
|
+
Self::Text(Default::default())
|
|
7854
|
+
}
|
|
7855
|
+
}
|
|
7856
|
+
|
|
7857
|
+
impl magnus::IntoValue for AssistantContent {
|
|
7858
|
+
fn into_value_with(self, handle: &Ruby) -> magnus::Value {
|
|
7859
|
+
match serde_json::to_value(&self) {
|
|
7860
|
+
Ok(v) => json_to_ruby(handle, v),
|
|
7861
|
+
Err(_) => handle.qnil().into_value_with(handle),
|
|
7862
|
+
}
|
|
7863
|
+
}
|
|
7864
|
+
}
|
|
7865
|
+
|
|
7866
|
+
impl magnus::TryConvert for AssistantContent {
|
|
7867
|
+
fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
|
|
7868
|
+
// For data enums with fields (e.g., PageAction), try to deserialize from JSON first.
|
|
7869
|
+
// For unit enums or when passed as a string, fall back to string-based conversion.
|
|
7870
|
+
let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
|
|
7871
|
+
s
|
|
7872
|
+
} else {
|
|
7873
|
+
val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
|
|
7874
|
+
magnus::Error::new(
|
|
7875
|
+
unsafe { Ruby::get_unchecked() }.exception_type_error(),
|
|
7876
|
+
format!("no implicit conversion into AssistantContent: {}", e),
|
|
7877
|
+
)
|
|
7878
|
+
})?
|
|
7879
|
+
};
|
|
7880
|
+
// Try deserializing as JSON first (handles JSON strings like "\"markdown\"" or "{\"click\":{\"selector\":\"...\"}}\"")
|
|
7881
|
+
// If that fails, try treating it as a plain string value and wrap in quotes
|
|
7882
|
+
// If both fail, try as Custom variant (for untagged enum support)
|
|
7883
|
+
serde_json::from_str(&json_str)
|
|
7884
|
+
.or_else(|_| serde_json::from_str(&format!("\"{json_str}\"")))
|
|
7885
|
+
.or_else(|_| {
|
|
7886
|
+
// Try as a JSON string for Custom variant (untagged enums accept any remaining value)
|
|
7887
|
+
match serde_json::to_value(&json_str) {
|
|
7888
|
+
Ok(val) => serde_json::from_value(val),
|
|
7889
|
+
Err(e) => Err(e),
|
|
7890
|
+
}
|
|
7891
|
+
})
|
|
7892
|
+
.map_err(|e| magnus::Error::new(unsafe { Ruby::get_unchecked() }.exception_type_error(), e.to_string()))
|
|
7893
|
+
}
|
|
7894
|
+
}
|
|
7895
|
+
|
|
7896
|
+
unsafe impl IntoValueFromNative for AssistantContent {}
|
|
7897
|
+
unsafe impl TryConvertOwned for AssistantContent {}
|
|
7898
|
+
|
|
7899
|
+
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
|
|
7900
|
+
#[serde(tag = "type")]
|
|
7901
|
+
#[serde(rename_all = "snake_case")]
|
|
7902
|
+
pub enum AssistantPart {
|
|
7903
|
+
Text { text: String },
|
|
7904
|
+
Refusal { refusal: String },
|
|
7905
|
+
OutputImage { image_url: ImageUrl },
|
|
7906
|
+
OutputAudio { audio: AudioContent },
|
|
7907
|
+
}
|
|
7908
|
+
|
|
7909
|
+
impl Default for AssistantPart {
|
|
7910
|
+
fn default() -> Self {
|
|
7911
|
+
Self::Text {
|
|
7912
|
+
text: Default::default(),
|
|
7913
|
+
}
|
|
7914
|
+
}
|
|
7915
|
+
}
|
|
7916
|
+
|
|
7917
|
+
impl magnus::IntoValue for AssistantPart {
|
|
7918
|
+
fn into_value_with(self, handle: &Ruby) -> magnus::Value {
|
|
7919
|
+
match serde_json::to_value(&self) {
|
|
7920
|
+
Ok(v) => json_to_ruby(handle, v),
|
|
7921
|
+
Err(_) => handle.qnil().into_value_with(handle),
|
|
7922
|
+
}
|
|
7923
|
+
}
|
|
7924
|
+
}
|
|
7925
|
+
|
|
7926
|
+
impl magnus::TryConvert for AssistantPart {
|
|
7927
|
+
fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
|
|
7928
|
+
// For data enums with fields (e.g., PageAction), try to deserialize from JSON first.
|
|
7929
|
+
// For unit enums or when passed as a string, fall back to string-based conversion.
|
|
7930
|
+
let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
|
|
7931
|
+
s
|
|
7932
|
+
} else {
|
|
7933
|
+
val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
|
|
7934
|
+
magnus::Error::new(
|
|
7935
|
+
unsafe { Ruby::get_unchecked() }.exception_type_error(),
|
|
7936
|
+
format!("no implicit conversion into AssistantPart: {}", e),
|
|
7937
|
+
)
|
|
7938
|
+
})?
|
|
7939
|
+
};
|
|
7940
|
+
// Try deserializing as JSON first (handles JSON strings like "\"markdown\"" or "{\"click\":{\"selector\":\"...\"}}\"")
|
|
7941
|
+
// If that fails, try treating it as a plain string value and wrap in quotes
|
|
7942
|
+
// If both fail, try as Custom variant (for untagged enum support)
|
|
7943
|
+
serde_json::from_str(&json_str)
|
|
7944
|
+
.or_else(|_| serde_json::from_str(&format!("\"{json_str}\"")))
|
|
7945
|
+
.or_else(|_| {
|
|
7946
|
+
// Try as a JSON string for Custom variant (untagged enums accept any remaining value)
|
|
7947
|
+
match serde_json::to_value(&json_str) {
|
|
7948
|
+
Ok(val) => serde_json::from_value(val),
|
|
7949
|
+
Err(e) => Err(e),
|
|
7950
|
+
}
|
|
7951
|
+
})
|
|
7952
|
+
.map_err(|e| magnus::Error::new(unsafe { Ruby::get_unchecked() }.exception_type_error(), e.to_string()))
|
|
7953
|
+
}
|
|
7954
|
+
}
|
|
7955
|
+
|
|
7956
|
+
unsafe impl IntoValueFromNative for AssistantPart {}
|
|
7957
|
+
unsafe impl TryConvertOwned for AssistantPart {}
|
|
7958
|
+
impl AssistantPart {}
|
|
7959
|
+
|
|
7713
7960
|
#[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
|
|
7714
7961
|
pub enum ToolType {
|
|
7715
7962
|
#[serde(rename = "function")]
|
|
@@ -7964,6 +8211,51 @@ impl magnus::TryConvert for StopSequence {
|
|
|
7964
8211
|
unsafe impl IntoValueFromNative for StopSequence {}
|
|
7965
8212
|
unsafe impl TryConvertOwned for StopSequence {}
|
|
7966
8213
|
|
|
8214
|
+
#[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
|
|
8215
|
+
#[serde(rename_all = "snake_case")]
|
|
8216
|
+
pub enum Modality {
|
|
8217
|
+
Text,
|
|
8218
|
+
Audio,
|
|
8219
|
+
Image,
|
|
8220
|
+
}
|
|
8221
|
+
|
|
8222
|
+
impl Default for Modality {
|
|
8223
|
+
fn default() -> Self {
|
|
8224
|
+
Self::Text
|
|
8225
|
+
}
|
|
8226
|
+
}
|
|
8227
|
+
|
|
8228
|
+
impl magnus::IntoValue for Modality {
|
|
8229
|
+
fn into_value_with(self, handle: &Ruby) -> magnus::Value {
|
|
8230
|
+
let sym = match self {
|
|
8231
|
+
Modality::Text => "text",
|
|
8232
|
+
Modality::Audio => "audio",
|
|
8233
|
+
Modality::Image => "image",
|
|
8234
|
+
};
|
|
8235
|
+
handle.to_symbol(sym).into_value_with(handle)
|
|
8236
|
+
}
|
|
8237
|
+
}
|
|
8238
|
+
|
|
8239
|
+
impl magnus::TryConvert for Modality {
|
|
8240
|
+
fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
|
|
8241
|
+
let s: String = magnus::TryConvert::try_convert(val)?;
|
|
8242
|
+
// Accept the serde wire name (snake_case), the PascalCase Rust variant name,
|
|
8243
|
+
// and a lowercase fallback so fixtures written in any of those styles work.
|
|
8244
|
+
match s.as_str() {
|
|
8245
|
+
"text" | "Text" => Ok(Modality::Text),
|
|
8246
|
+
"audio" | "Audio" => Ok(Modality::Audio),
|
|
8247
|
+
"image" | "Image" => Ok(Modality::Image),
|
|
8248
|
+
other => Err(magnus::Error::new(
|
|
8249
|
+
unsafe { Ruby::get_unchecked() }.exception_arg_error(),
|
|
8250
|
+
format!("invalid Modality value: {other}"),
|
|
8251
|
+
)),
|
|
8252
|
+
}
|
|
8253
|
+
}
|
|
8254
|
+
}
|
|
8255
|
+
|
|
8256
|
+
unsafe impl IntoValueFromNative for Modality {}
|
|
8257
|
+
unsafe impl TryConvertOwned for Modality {}
|
|
8258
|
+
|
|
7967
8259
|
#[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
|
|
7968
8260
|
#[serde(rename_all = "snake_case")]
|
|
7969
8261
|
pub enum FinishReason {
|
|
@@ -8762,6 +9054,27 @@ fn create_client_from_json(json: String) -> Result<DefaultClient, Error> {
|
|
|
8762
9054
|
})
|
|
8763
9055
|
}
|
|
8764
9056
|
|
|
9057
|
+
fn encode_data_url(args: &[magnus::Value]) -> Result<String, Error> {
|
|
9058
|
+
let args = magnus::scan_args::scan_args::<(Vec<u8>,), (Option<magnus::Value>,), (), (), (), ()>(args)?;
|
|
9059
|
+
let (bytes,) = args.required;
|
|
9060
|
+
|
|
9061
|
+
let (mime,) = args.optional;
|
|
9062
|
+
|
|
9063
|
+
let mime: Option<String> = mime.and_then(|v| {
|
|
9064
|
+
if v.is_nil() {
|
|
9065
|
+
None
|
|
9066
|
+
} else {
|
|
9067
|
+
Some(String::try_convert(v).unwrap_or_default())
|
|
9068
|
+
}
|
|
9069
|
+
});
|
|
9070
|
+
|
|
9071
|
+
Ok(liter_llm::image::encode_data_url(&bytes, mime.as_deref()))
|
|
9072
|
+
}
|
|
9073
|
+
|
|
9074
|
+
fn decode_data_url(url: String) -> Option<DecodedDataUrl> {
|
|
9075
|
+
liter_llm::image::decode_data_url(&url).map(Into::into)
|
|
9076
|
+
}
|
|
9077
|
+
|
|
8765
9078
|
fn register_custom_provider(config: magnus::Value) -> Result<(), Error> {
|
|
8766
9079
|
let config: liter_llm::CustomProviderConfig = {
|
|
8767
9080
|
let binding_val: CustomProviderConfig = CustomProviderConfig::try_convert(config)
|
|
@@ -8880,6 +9193,11 @@ fn ensure_crypto_provider() -> () {
|
|
|
8880
9193
|
liter_llm::ensure_crypto_provider()
|
|
8881
9194
|
}
|
|
8882
9195
|
|
|
9196
|
+
#[cfg(all(feature = "native-http", target_os = "windows"))]
|
|
9197
|
+
fn ensure_crypto_provider() -> () {
|
|
9198
|
+
liter_llm::ensure_crypto_provider()
|
|
9199
|
+
}
|
|
9200
|
+
|
|
8883
9201
|
fn chat_stream(engine: DefaultClient, req: ChatCompletionRequest) -> Result<magnus::Value, Error> {
|
|
8884
9202
|
engine.chat_stream(req)
|
|
8885
9203
|
}
|
|
@@ -8888,7 +9206,7 @@ fn chat_stream(engine: DefaultClient, req: ChatCompletionRequest) -> Result<magn
|
|
|
8888
9206
|
impl From<SystemMessage> for liter_llm::types::SystemMessage {
|
|
8889
9207
|
fn from(val: SystemMessage) -> Self {
|
|
8890
9208
|
Self {
|
|
8891
|
-
content: val.content,
|
|
9209
|
+
content: val.content.into(),
|
|
8892
9210
|
name: val.name,
|
|
8893
9211
|
}
|
|
8894
9212
|
}
|
|
@@ -8898,7 +9216,7 @@ impl From<SystemMessage> for liter_llm::types::SystemMessage {
|
|
|
8898
9216
|
impl From<liter_llm::types::SystemMessage> for SystemMessage {
|
|
8899
9217
|
fn from(val: liter_llm::types::SystemMessage) -> Self {
|
|
8900
9218
|
Self {
|
|
8901
|
-
content: val.content.
|
|
9219
|
+
content: val.content.into(),
|
|
8902
9220
|
name: val.name.map(|v| v.to_string()),
|
|
8903
9221
|
}
|
|
8904
9222
|
}
|
|
@@ -8988,7 +9306,7 @@ impl From<liter_llm::types::AudioContent> for AudioContent {
|
|
|
8988
9306
|
impl From<AssistantMessage> for liter_llm::types::AssistantMessage {
|
|
8989
9307
|
fn from(val: AssistantMessage) -> Self {
|
|
8990
9308
|
Self {
|
|
8991
|
-
content: val.content,
|
|
9309
|
+
content: val.content.map(Into::into),
|
|
8992
9310
|
name: val.name,
|
|
8993
9311
|
tool_calls: val.tool_calls.map(|v| v.into_iter().map(Into::into).collect()),
|
|
8994
9312
|
refusal: val.refusal,
|
|
@@ -9001,7 +9319,7 @@ impl From<AssistantMessage> for liter_llm::types::AssistantMessage {
|
|
|
9001
9319
|
impl From<liter_llm::types::AssistantMessage> for AssistantMessage {
|
|
9002
9320
|
fn from(val: liter_llm::types::AssistantMessage) -> Self {
|
|
9003
9321
|
Self {
|
|
9004
|
-
content: val.content.map(
|
|
9322
|
+
content: val.content.map(Into::into),
|
|
9005
9323
|
name: val.name.map(|v| v.to_string()),
|
|
9006
9324
|
tool_calls: val.tool_calls.map(|v| v.into_iter().map(Into::into).collect()),
|
|
9007
9325
|
refusal: val.refusal.map(|v| v.to_string()),
|
|
@@ -9285,6 +9603,7 @@ impl From<ChatCompletionRequest> for liter_llm::types::ChatCompletionRequest {
|
|
|
9285
9603
|
stream_options: val.stream_options.map(Into::into),
|
|
9286
9604
|
seed: val.seed,
|
|
9287
9605
|
reasoning_effort: val.reasoning_effort.map(Into::into),
|
|
9606
|
+
modalities: val.modalities.map(|v| v.into_iter().map(Into::into).collect()),
|
|
9288
9607
|
extra_body: val.extra_body.as_ref().and_then(|s| serde_json::from_str(s).ok()),
|
|
9289
9608
|
}
|
|
9290
9609
|
}
|
|
@@ -9313,6 +9632,7 @@ impl From<liter_llm::types::ChatCompletionRequest> for ChatCompletionRequest {
|
|
|
9313
9632
|
stream_options: val.stream_options.map(Into::into),
|
|
9314
9633
|
seed: val.seed,
|
|
9315
9634
|
reasoning_effort: val.reasoning_effort.map(Into::into),
|
|
9635
|
+
modalities: val.modalities.map(|v| v.into_iter().map(Into::into).collect()),
|
|
9316
9636
|
extra_body: val.extra_body.as_ref().map(ToString::to_string),
|
|
9317
9637
|
}
|
|
9318
9638
|
}
|
|
@@ -9598,6 +9918,26 @@ impl From<liter_llm::types::Image> for Image {
|
|
|
9598
9918
|
}
|
|
9599
9919
|
}
|
|
9600
9920
|
|
|
9921
|
+
#[allow(clippy::redundant_closure, clippy::useless_conversion)]
|
|
9922
|
+
impl From<DecodedDataUrl> for liter_llm::image::DecodedDataUrl {
|
|
9923
|
+
fn from(val: DecodedDataUrl) -> Self {
|
|
9924
|
+
Self {
|
|
9925
|
+
mime: val.mime,
|
|
9926
|
+
data: val.data.to_vec().into(),
|
|
9927
|
+
}
|
|
9928
|
+
}
|
|
9929
|
+
}
|
|
9930
|
+
|
|
9931
|
+
#[allow(clippy::redundant_closure, clippy::useless_conversion)]
|
|
9932
|
+
impl From<liter_llm::image::DecodedDataUrl> for DecodedDataUrl {
|
|
9933
|
+
fn from(val: liter_llm::image::DecodedDataUrl) -> Self {
|
|
9934
|
+
Self {
|
|
9935
|
+
mime: val.mime.to_string(),
|
|
9936
|
+
data: val.data.to_vec().into(),
|
|
9937
|
+
}
|
|
9938
|
+
}
|
|
9939
|
+
}
|
|
9940
|
+
|
|
9601
9941
|
#[allow(clippy::redundant_closure, clippy::useless_conversion)]
|
|
9602
9942
|
impl From<CreateSpeechRequest> for liter_llm::types::CreateSpeechRequest {
|
|
9603
9943
|
fn from(val: CreateSpeechRequest) -> Self {
|
|
@@ -10813,6 +11153,52 @@ impl From<liter_llm::types::ImageDetail> for ImageDetail {
|
|
|
10813
11153
|
}
|
|
10814
11154
|
}
|
|
10815
11155
|
|
|
11156
|
+
impl From<AssistantContent> for liter_llm::types::AssistantContent {
|
|
11157
|
+
fn from(val: AssistantContent) -> Self {
|
|
11158
|
+
match val {
|
|
11159
|
+
AssistantContent::Text(_0) => Self::Text(_0),
|
|
11160
|
+
AssistantContent::Parts(_0) => Self::Parts(_0.into_iter().map(Into::into).collect()),
|
|
11161
|
+
}
|
|
11162
|
+
}
|
|
11163
|
+
}
|
|
11164
|
+
|
|
11165
|
+
impl From<liter_llm::types::AssistantContent> for AssistantContent {
|
|
11166
|
+
fn from(val: liter_llm::types::AssistantContent) -> Self {
|
|
11167
|
+
match val {
|
|
11168
|
+
liter_llm::types::AssistantContent::Text(_0) => Self::Text(_0),
|
|
11169
|
+
liter_llm::types::AssistantContent::Parts(_0) => Self::Parts(_0.into_iter().map(Into::into).collect()),
|
|
11170
|
+
}
|
|
11171
|
+
}
|
|
11172
|
+
}
|
|
11173
|
+
|
|
11174
|
+
impl From<AssistantPart> for liter_llm::types::AssistantPart {
|
|
11175
|
+
fn from(val: AssistantPart) -> Self {
|
|
11176
|
+
match val {
|
|
11177
|
+
AssistantPart::Text { text } => Self::Text { text },
|
|
11178
|
+
AssistantPart::Refusal { refusal } => Self::Refusal { refusal },
|
|
11179
|
+
AssistantPart::OutputImage { image_url } => Self::OutputImage {
|
|
11180
|
+
image_url: image_url.into(),
|
|
11181
|
+
},
|
|
11182
|
+
AssistantPart::OutputAudio { audio } => Self::OutputAudio { audio: audio.into() },
|
|
11183
|
+
}
|
|
11184
|
+
}
|
|
11185
|
+
}
|
|
11186
|
+
|
|
11187
|
+
impl From<liter_llm::types::AssistantPart> for AssistantPart {
|
|
11188
|
+
fn from(val: liter_llm::types::AssistantPart) -> Self {
|
|
11189
|
+
match val {
|
|
11190
|
+
liter_llm::types::AssistantPart::Text { text } => Self::Text { text: text.to_string() },
|
|
11191
|
+
liter_llm::types::AssistantPart::Refusal { refusal } => Self::Refusal {
|
|
11192
|
+
refusal: refusal.to_string(),
|
|
11193
|
+
},
|
|
11194
|
+
liter_llm::types::AssistantPart::OutputImage { image_url } => Self::OutputImage {
|
|
11195
|
+
image_url: image_url.into(),
|
|
11196
|
+
},
|
|
11197
|
+
liter_llm::types::AssistantPart::OutputAudio { audio } => Self::OutputAudio { audio: audio.into() },
|
|
11198
|
+
}
|
|
11199
|
+
}
|
|
11200
|
+
}
|
|
11201
|
+
|
|
10816
11202
|
impl From<ToolType> for liter_llm::types::ToolType {
|
|
10817
11203
|
fn from(val: ToolType) -> Self {
|
|
10818
11204
|
match val {
|
|
@@ -10909,6 +11295,26 @@ impl From<liter_llm::types::StopSequence> for StopSequence {
|
|
|
10909
11295
|
}
|
|
10910
11296
|
}
|
|
10911
11297
|
|
|
11298
|
+
impl From<Modality> for liter_llm::types::Modality {
|
|
11299
|
+
fn from(val: Modality) -> Self {
|
|
11300
|
+
match val {
|
|
11301
|
+
Modality::Text => Self::Text,
|
|
11302
|
+
Modality::Audio => Self::Audio,
|
|
11303
|
+
Modality::Image => Self::Image,
|
|
11304
|
+
}
|
|
11305
|
+
}
|
|
11306
|
+
}
|
|
11307
|
+
|
|
11308
|
+
impl From<liter_llm::types::Modality> for Modality {
|
|
11309
|
+
fn from(val: liter_llm::types::Modality) -> Self {
|
|
11310
|
+
match val {
|
|
11311
|
+
liter_llm::types::Modality::Text => Self::Text,
|
|
11312
|
+
liter_llm::types::Modality::Audio => Self::Audio,
|
|
11313
|
+
liter_llm::types::Modality::Image => Self::Image,
|
|
11314
|
+
}
|
|
11315
|
+
}
|
|
11316
|
+
}
|
|
11317
|
+
|
|
10912
11318
|
impl From<FinishReason> for liter_llm::types::FinishReason {
|
|
10913
11319
|
fn from(val: FinishReason) -> Self {
|
|
10914
11320
|
match val {
|
|
@@ -11328,8 +11734,6 @@ fn ruby_init(ruby: &Ruby) -> Result<(), Error> {
|
|
|
11328
11734
|
|
|
11329
11735
|
class.define_method("name", method!(SystemMessage::name, 0))?;
|
|
11330
11736
|
|
|
11331
|
-
class.define_method("to_s", method!(SystemMessage::to_s, 0))?;
|
|
11332
|
-
|
|
11333
11737
|
let class = module.define_class("UserMessage", ruby.class_object())?;
|
|
11334
11738
|
|
|
11335
11739
|
class.define_singleton_method("new", function!(UserMessage::new, -1))?;
|
|
@@ -11376,7 +11780,13 @@ fn ruby_init(ruby: &Ruby) -> Result<(), Error> {
|
|
|
11376
11780
|
|
|
11377
11781
|
class.define_method("function_call", method!(AssistantMessage::function_call, 0))?;
|
|
11378
11782
|
|
|
11379
|
-
class.define_method("
|
|
11783
|
+
class.define_method("text", method!(AssistantMessage::text, 0))?;
|
|
11784
|
+
|
|
11785
|
+
class.define_method("refusal_text", method!(AssistantMessage::refusal_text, 0))?;
|
|
11786
|
+
|
|
11787
|
+
class.define_method("output_images", method!(AssistantMessage::output_images, 0))?;
|
|
11788
|
+
|
|
11789
|
+
class.define_method("output_audio", method!(AssistantMessage::output_audio, 0))?;
|
|
11380
11790
|
|
|
11381
11791
|
let class = module.define_class("ToolMessage", ruby.class_object())?;
|
|
11382
11792
|
|
|
@@ -11542,6 +11952,8 @@ fn ruby_init(ruby: &Ruby) -> Result<(), Error> {
|
|
|
11542
11952
|
|
|
11543
11953
|
class.define_method("reasoning_effort", method!(ChatCompletionRequest::reasoning_effort, 0))?;
|
|
11544
11954
|
|
|
11955
|
+
class.define_method("modalities", method!(ChatCompletionRequest::modalities, 0))?;
|
|
11956
|
+
|
|
11545
11957
|
class.define_method("extra_body", method!(ChatCompletionRequest::extra_body, 0))?;
|
|
11546
11958
|
|
|
11547
11959
|
let class = module.define_class("StreamOptions", ruby.class_object())?;
|
|
@@ -11726,6 +12138,14 @@ fn ruby_init(ruby: &Ruby) -> Result<(), Error> {
|
|
|
11726
12138
|
|
|
11727
12139
|
class.define_method("revised_prompt", method!(Image::revised_prompt, 0))?;
|
|
11728
12140
|
|
|
12141
|
+
let class = module.define_class("DecodedDataUrl", ruby.class_object())?;
|
|
12142
|
+
|
|
12143
|
+
class.define_singleton_method("new", function!(DecodedDataUrl::new, -1))?;
|
|
12144
|
+
|
|
12145
|
+
class.define_method("mime", method!(DecodedDataUrl::mime, 0))?;
|
|
12146
|
+
|
|
12147
|
+
class.define_method("data", method!(DecodedDataUrl::data, 0))?;
|
|
12148
|
+
|
|
11729
12149
|
let class = module.define_class("CreateSpeechRequest", ruby.class_object())?;
|
|
11730
12150
|
|
|
11731
12151
|
class.define_singleton_method("new", function!(CreateSpeechRequest::new, -1))?;
|
|
@@ -12405,6 +12825,10 @@ fn ruby_init(ruby: &Ruby) -> Result<(), Error> {
|
|
|
12405
12825
|
|
|
12406
12826
|
module.define_module_function("create_client_from_json", function!(create_client_from_json, 1))?;
|
|
12407
12827
|
|
|
12828
|
+
module.define_module_function("encode_data_url", function!(encode_data_url, -1))?;
|
|
12829
|
+
|
|
12830
|
+
module.define_module_function("decode_data_url", function!(decode_data_url, 1))?;
|
|
12831
|
+
|
|
12408
12832
|
module.define_module_function("register_custom_provider", function!(register_custom_provider, 1))?;
|
|
12409
12833
|
|
|
12410
12834
|
module.define_module_function("unregister_custom_provider", function!(unregister_custom_provider, 1))?;
|
|
@@ -12429,6 +12853,8 @@ fn ruby_init(ruby: &Ruby) -> Result<(), Error> {
|
|
|
12429
12853
|
|
|
12430
12854
|
module.define_module_function("ensure_crypto_provider", function!(ensure_crypto_provider, 0))?;
|
|
12431
12855
|
|
|
12856
|
+
module.define_module_function("ensure_crypto_provider", function!(ensure_crypto_provider, 0))?;
|
|
12857
|
+
|
|
12432
12858
|
module.define_module_function("chat_stream", function!(chat_stream, 2))?;
|
|
12433
12859
|
|
|
12434
12860
|
let liter_llm_error_info_class = module.define_class("LiterLlmErrorInfo", ruby.class_object())?;
|
data/lib/liter_llm/native.rb
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# This file is auto-generated by alef — DO NOT EDIT.
|
|
2
|
-
# alef:hash:
|
|
2
|
+
# alef:hash:fec9b44a56d9714b7b12931bb48e1e8f6d57ef2d0ccaad1f9ac6046ce01a89af
|
|
3
3
|
# To regenerate: alef generate
|
|
4
4
|
# To verify freshness: alef verify --exit-code
|
|
5
5
|
# frozen_string_literal: true
|
|
@@ -314,7 +314,142 @@ module LiterLlm
|
|
|
314
314
|
end
|
|
315
315
|
|
|
316
316
|
module LiterLlm
|
|
317
|
-
#
|
|
317
|
+
# One part of a structured assistant response.
|
|
318
|
+
#
|
|
319
|
+
# `#[serde(tag = "type", rename_all = "snake_case")]` matches OpenAI's
|
|
320
|
+
# parts-spec discriminator (`"type": "text"`, `"type": "output_image"`, …).
|
|
321
|
+
module AssistantPart
|
|
322
|
+
extend T::Helpers
|
|
323
|
+
extend T::Sig
|
|
324
|
+
|
|
325
|
+
interface!
|
|
326
|
+
|
|
327
|
+
# Dispatch from a Hash to the appropriate variant constructor.
|
|
328
|
+
# @param hash [Hash] with discriminator field and variant-specific fields
|
|
329
|
+
# @return [variant_class] an instance of the appropriate variant
|
|
330
|
+
sig { params(hash: T::Hash[T.untyped, T.untyped]).returns(T.untyped) }
|
|
331
|
+
def self.from_hash(hash)
|
|
332
|
+
discriminator = hash[:type] || hash["type"]
|
|
333
|
+
case discriminator
|
|
334
|
+
when "text" then AssistantPartText.from_hash(hash)
|
|
335
|
+
when "refusal" then AssistantPartRefusal.from_hash(hash)
|
|
336
|
+
when "output_image" then AssistantPartOutputImage.from_hash(hash)
|
|
337
|
+
when "output_audio" then AssistantPartOutputAudio.from_hash(hash)
|
|
338
|
+
else raise "Unknown discriminator: #{discriminator}"
|
|
339
|
+
end
|
|
340
|
+
end
|
|
341
|
+
end
|
|
342
|
+
## A text segment of the response.
|
|
343
|
+
AssistantPartText = Data.define(:text) do
|
|
344
|
+
include AssistantPart
|
|
345
|
+
extend T::Sig
|
|
346
|
+
|
|
347
|
+
# The text content of this part.
|
|
348
|
+
sig { returns(String) }
|
|
349
|
+
def text = super # rubocop:disable Lint/UselessMethodDefinition
|
|
350
|
+
sig { returns(T::Boolean) }
|
|
351
|
+
def text? = true
|
|
352
|
+
sig { returns(T::Boolean) }
|
|
353
|
+
def refusal? = false
|
|
354
|
+
sig { returns(T::Boolean) }
|
|
355
|
+
def output_image? = false
|
|
356
|
+
sig { returns(T::Boolean) }
|
|
357
|
+
def output_audio? = false
|
|
358
|
+
# @param hash [Hash] deserialized from the native extension
|
|
359
|
+
# @return [self]
|
|
360
|
+
sig { params(hash: T::Hash[T.untyped, T.untyped]).returns(T.attached_class) }
|
|
361
|
+
def self.from_hash(hash)
|
|
362
|
+
new(text: hash[:text] || hash["text"])
|
|
363
|
+
end
|
|
364
|
+
end
|
|
365
|
+
## A refusal — the model declined to respond.
|
|
366
|
+
AssistantPartRefusal = Data.define(:refusal) do
|
|
367
|
+
include AssistantPart
|
|
368
|
+
extend T::Sig
|
|
369
|
+
|
|
370
|
+
# The refusal reason.
|
|
371
|
+
sig { returns(String) }
|
|
372
|
+
def refusal = super # rubocop:disable Lint/UselessMethodDefinition
|
|
373
|
+
sig { returns(T::Boolean) }
|
|
374
|
+
def text? = false
|
|
375
|
+
sig { returns(T::Boolean) }
|
|
376
|
+
def refusal? = true
|
|
377
|
+
sig { returns(T::Boolean) }
|
|
378
|
+
def output_image? = false
|
|
379
|
+
sig { returns(T::Boolean) }
|
|
380
|
+
def output_audio? = false
|
|
381
|
+
# @param hash [Hash] deserialized from the native extension
|
|
382
|
+
# @return [self]
|
|
383
|
+
sig { params(hash: T::Hash[T.untyped, T.untyped]).returns(T.attached_class) }
|
|
384
|
+
def self.from_hash(hash)
|
|
385
|
+
new(refusal: hash[:refusal] || hash["refusal"])
|
|
386
|
+
end
|
|
387
|
+
end
|
|
388
|
+
## An image produced by the model (e.g. `gpt-image-1`, Gemini Imagen).
|
|
389
|
+
AssistantPartOutputImage = Data.define(:image_url) do
|
|
390
|
+
include AssistantPart
|
|
391
|
+
extend T::Sig
|
|
392
|
+
|
|
393
|
+
# Image URL or data URI referencing the generated image.
|
|
394
|
+
sig { returns(ImageUrl) }
|
|
395
|
+
def image_url = super # rubocop:disable Lint/UselessMethodDefinition
|
|
396
|
+
sig { returns(T::Boolean) }
|
|
397
|
+
def text? = false
|
|
398
|
+
sig { returns(T::Boolean) }
|
|
399
|
+
def refusal? = false
|
|
400
|
+
sig { returns(T::Boolean) }
|
|
401
|
+
def output_image? = true
|
|
402
|
+
sig { returns(T::Boolean) }
|
|
403
|
+
def output_audio? = false
|
|
404
|
+
# @param hash [Hash] deserialized from the native extension
|
|
405
|
+
# @return [self]
|
|
406
|
+
sig { params(hash: T::Hash[T.untyped, T.untyped]).returns(T.attached_class) }
|
|
407
|
+
def self.from_hash(hash)
|
|
408
|
+
new(image_url: hash[:image_url] || hash["image_url"])
|
|
409
|
+
end
|
|
410
|
+
end
|
|
411
|
+
## Audio produced by the model (e.g. `gpt-4o-audio-preview`).
|
|
412
|
+
AssistantPartOutputAudio = Data.define(:audio) do
|
|
413
|
+
include AssistantPart
|
|
414
|
+
extend T::Sig
|
|
415
|
+
|
|
416
|
+
# Base64-encoded audio data and its format.
|
|
417
|
+
sig { returns(AudioContent) }
|
|
418
|
+
def audio = super # rubocop:disable Lint/UselessMethodDefinition
|
|
419
|
+
sig { returns(T::Boolean) }
|
|
420
|
+
def text? = false
|
|
421
|
+
sig { returns(T::Boolean) }
|
|
422
|
+
def refusal? = false
|
|
423
|
+
sig { returns(T::Boolean) }
|
|
424
|
+
def output_image? = false
|
|
425
|
+
sig { returns(T::Boolean) }
|
|
426
|
+
def output_audio? = true
|
|
427
|
+
# @param hash [Hash] deserialized from the native extension
|
|
428
|
+
# @return [self]
|
|
429
|
+
sig { params(hash: T::Hash[T.untyped, T.untyped]).returns(T.attached_class) }
|
|
430
|
+
def self.from_hash(hash)
|
|
431
|
+
new(audio: hash[:audio] || hash["audio"])
|
|
432
|
+
end
|
|
433
|
+
end
|
|
434
|
+
end
|
|
435
|
+
|
|
436
|
+
module LiterLlm
|
|
437
|
+
# Wire format for the chat completions `response_format` field.
|
|
438
|
+
#
|
|
439
|
+
# # Provider mapping
|
|
440
|
+
#
|
|
441
|
+
# - **OpenAI** (and OpenAI-compatible providers): emitted verbatim as
|
|
442
|
+
# `{"type": "json_schema", "json_schema": {...}}` per the
|
|
443
|
+
# chat-completions spec.
|
|
444
|
+
# - **Gemini / Vertex AI**: translated to
|
|
445
|
+
# `generationConfig.responseMimeType = "application/json"` and
|
|
446
|
+
# `generationConfig.responseSchema = <schema>`. The `name`,
|
|
447
|
+
# `description`, and `strict` fields are dropped — Gemini's
|
|
448
|
+
# structured-output API does not consume them.
|
|
449
|
+
# - **Anthropic**: no native JSON mode. A system instruction is
|
|
450
|
+
# prepended asking the model to respond with valid JSON.
|
|
451
|
+
# `strict` is advisory only; callers should still validate the
|
|
452
|
+
# returned JSON if the schema is load-bearing.
|
|
318
453
|
module ResponseFormat
|
|
319
454
|
extend T::Helpers
|
|
320
455
|
extend T::Sig
|
data/lib/liter_llm/version.rb
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# This file is auto-generated by alef — DO NOT EDIT.
|
|
2
|
-
# alef:hash:
|
|
2
|
+
# alef:hash:fec9b44a56d9714b7b12931bb48e1e8f6d57ef2d0ccaad1f9ac6046ce01a89af
|
|
3
3
|
# To regenerate: alef generate
|
|
4
4
|
# To verify freshness: alef verify --exit-code
|
|
5
5
|
# frozen_string_literal: true
|
|
6
6
|
|
|
7
7
|
module LiterLlm
|
|
8
8
|
## The version string for this package.
|
|
9
|
-
VERSION = "1.
|
|
9
|
+
VERSION = "1.7.1"
|
|
10
10
|
end
|
data/lib/liter_llm.rb
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# This file is auto-generated by alef — DO NOT EDIT.
|
|
2
|
-
# alef:hash:
|
|
2
|
+
# alef:hash:fec9b44a56d9714b7b12931bb48e1e8f6d57ef2d0ccaad1f9ac6046ce01a89af
|
|
3
3
|
# To regenerate: alef generate
|
|
4
4
|
# To verify freshness: alef verify --exit-code
|
|
5
5
|
# frozen_string_literal: true
|
data/sig/types.rbs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# This file is auto-generated by alef — DO NOT EDIT.
|
|
2
|
-
# alef:hash:
|
|
2
|
+
# alef:hash:fec9b44a56d9714b7b12931bb48e1e8f6d57ef2d0ccaad1f9ac6046ce01a89af
|
|
3
3
|
# To regenerate: alef generate
|
|
4
4
|
# To verify freshness: alef verify --exit-code
|
|
5
5
|
|
|
@@ -10,10 +10,10 @@ module LiterLlm
|
|
|
10
10
|
type json_value = Hash[String, untyped] | Array[untyped] | String | Integer | Float | bool | nil
|
|
11
11
|
|
|
12
12
|
class SystemMessage
|
|
13
|
-
attr_accessor content:
|
|
13
|
+
attr_accessor content: UserContent?
|
|
14
14
|
attr_accessor name: String?
|
|
15
15
|
|
|
16
|
-
def initialize: (?content:
|
|
16
|
+
def initialize: (?content: UserContent, ?name: String) -> void
|
|
17
17
|
end
|
|
18
18
|
|
|
19
19
|
class UserMessage
|
|
@@ -45,13 +45,17 @@ module LiterLlm
|
|
|
45
45
|
end
|
|
46
46
|
|
|
47
47
|
class AssistantMessage
|
|
48
|
-
attr_accessor content:
|
|
48
|
+
attr_accessor content: AssistantContent?
|
|
49
49
|
attr_accessor name: String?
|
|
50
50
|
attr_accessor tool_calls: Array[ToolCall]?
|
|
51
51
|
attr_accessor refusal: String?
|
|
52
52
|
attr_accessor function_call: FunctionCall?
|
|
53
53
|
|
|
54
|
-
def initialize: (?content:
|
|
54
|
+
def initialize: (?content: AssistantContent, ?name: String, ?tool_calls: Array[ToolCall], ?refusal: String, ?function_call: FunctionCall) -> void
|
|
55
|
+
def text: () -> String?
|
|
56
|
+
def refusal_text: () -> String?
|
|
57
|
+
def output_images: () -> Array[ImageUrl]
|
|
58
|
+
def output_audio: () -> Array[AudioContent]
|
|
55
59
|
end
|
|
56
60
|
|
|
57
61
|
class ToolMessage
|
|
@@ -165,9 +169,10 @@ module LiterLlm
|
|
|
165
169
|
attr_accessor stream_options: StreamOptions?
|
|
166
170
|
attr_accessor seed: Integer?
|
|
167
171
|
attr_accessor reasoning_effort: ReasoningEffort?
|
|
172
|
+
attr_accessor modalities: Array[Modality]?
|
|
168
173
|
attr_accessor extra_body: json_value?
|
|
169
174
|
|
|
170
|
-
def initialize: (?model: String, ?messages: Array[Message], ?temperature: Float, ?top_p: Float, ?n: Integer, ?stream: bool, ?stop: StopSequence, ?max_tokens: Integer, ?presence_penalty: Float, ?frequency_penalty: Float, ?logit_bias: Hash[String, Float], ?user: String, ?tools: Array[ChatCompletionTool], ?tool_choice: ToolChoice, ?parallel_tool_calls: bool, ?response_format: ResponseFormat, ?stream_options: StreamOptions, ?seed: Integer, ?reasoning_effort: ReasoningEffort, ?extra_body: json_value) -> void
|
|
175
|
+
def initialize: (?model: String, ?messages: Array[Message], ?temperature: Float, ?top_p: Float, ?n: Integer, ?stream: bool, ?stop: StopSequence, ?max_tokens: Integer, ?presence_penalty: Float, ?frequency_penalty: Float, ?logit_bias: Hash[String, Float], ?user: String, ?tools: Array[ChatCompletionTool], ?tool_choice: ToolChoice, ?parallel_tool_calls: bool, ?response_format: ResponseFormat, ?stream_options: StreamOptions, ?seed: Integer, ?reasoning_effort: ReasoningEffort, ?modalities: Array[Modality], ?extra_body: json_value) -> void
|
|
171
176
|
end
|
|
172
177
|
|
|
173
178
|
class StreamOptions
|
|
@@ -299,6 +304,13 @@ module LiterLlm
|
|
|
299
304
|
def initialize: (?url: String, ?b64_json: String, ?revised_prompt: String) -> void
|
|
300
305
|
end
|
|
301
306
|
|
|
307
|
+
class DecodedDataUrl
|
|
308
|
+
attr_accessor mime: String?
|
|
309
|
+
attr_accessor data: String?
|
|
310
|
+
|
|
311
|
+
def initialize: (?mime: String, ?data: String) -> void
|
|
312
|
+
end
|
|
313
|
+
|
|
302
314
|
class CreateSpeechRequest
|
|
303
315
|
attr_accessor model: String?
|
|
304
316
|
attr_accessor input: String?
|
|
@@ -780,6 +792,12 @@ module LiterLlm
|
|
|
780
792
|
type value = :low | :high | :auto
|
|
781
793
|
end
|
|
782
794
|
|
|
795
|
+
class AssistantContent
|
|
796
|
+
end
|
|
797
|
+
|
|
798
|
+
class AssistantPart
|
|
799
|
+
end
|
|
800
|
+
|
|
783
801
|
class ToolType
|
|
784
802
|
type value = :function
|
|
785
803
|
end
|
|
@@ -797,6 +815,10 @@ module LiterLlm
|
|
|
797
815
|
class StopSequence
|
|
798
816
|
end
|
|
799
817
|
|
|
818
|
+
class Modality
|
|
819
|
+
type value = :text | :audio | :image
|
|
820
|
+
end
|
|
821
|
+
|
|
800
822
|
class FinishReason
|
|
801
823
|
type value = :stop | :length | :tool_calls | :content_filter | :function_call | :other
|
|
802
824
|
end
|
|
@@ -859,6 +881,10 @@ module LiterLlm
|
|
|
859
881
|
|
|
860
882
|
def self.create_client_from_json: (String json) -> DefaultClient
|
|
861
883
|
|
|
884
|
+
def self.encode_data_url: (String bytes, ?String mime) -> String
|
|
885
|
+
|
|
886
|
+
def self.decode_data_url: (String url) -> DecodedDataUrl?
|
|
887
|
+
|
|
862
888
|
def self.register_custom_provider: (CustomProviderConfig config) -> void
|
|
863
889
|
|
|
864
890
|
def self.unregister_custom_provider: (String name) -> bool
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: liter_llm
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.7.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Na'aman Hirschfeld
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-06-
|
|
11
|
+
date: 2026-06-18 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rb_sys
|