Compare commits
22 Commits
09c8696a36
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 2181201781 | |||
| 414299cdc7 | |||
| 5e5ce794d6 | |||
| 09e5d3d1f8 | |||
| e2c0c67b17 | |||
| c4dd835594 | |||
| 95fb24d5f1 | |||
| f7dc6ead57 | |||
| 5e0fc06327 | |||
|
|
71e2440e0b | ||
|
|
d9667c29f7 | ||
|
|
7da2d82b49 | ||
|
|
622156591c | ||
| 2ed8d881c6 | |||
| c4acf465ba | |||
| f8a4323117 | |||
| d1bd00ce95 | |||
| a8fbe658bb | |||
| eb38c65ac5 | |||
| 4c156fbb33 | |||
| 92030a0dd9 | |||
| 56cf4f280b |
61
.github/workflows/ci.yml
vendored
Normal file
61
.github/workflows/ci.yml
vendored
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
name: CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
pull_request:
|
||||||
|
branches: [main]
|
||||||
|
|
||||||
|
env:
|
||||||
|
CARGO_TERM_COLOR: always
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
check:
|
||||||
|
name: Check
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: dtolnay/rust-toolchain@stable
|
||||||
|
- uses: Swatinem/rust-cache@v2
|
||||||
|
- run: cargo check
|
||||||
|
|
||||||
|
test:
|
||||||
|
name: Test
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: dtolnay/rust-toolchain@stable
|
||||||
|
- uses: Swatinem/rust-cache@v2
|
||||||
|
- run: cargo test
|
||||||
|
|
||||||
|
clippy:
|
||||||
|
name: Clippy
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: dtolnay/rust-toolchain@stable
|
||||||
|
with:
|
||||||
|
components: clippy
|
||||||
|
- uses: Swatinem/rust-cache@v2
|
||||||
|
- run: cargo clippy -- -D warnings
|
||||||
|
|
||||||
|
fmt:
|
||||||
|
name: Format
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: dtolnay/rust-toolchain@stable
|
||||||
|
with:
|
||||||
|
components: rustfmt
|
||||||
|
- run: cargo fmt --check
|
||||||
|
|
||||||
|
doc:
|
||||||
|
name: Documentation
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: dtolnay/rust-toolchain@stable
|
||||||
|
- uses: Swatinem/rust-cache@v2
|
||||||
|
- run: cargo doc --no-deps
|
||||||
|
env:
|
||||||
|
RUSTDOCFLAGS: "-D warnings"
|
||||||
169
Cargo.lock
generated
169
Cargo.lock
generated
@@ -216,9 +216,9 @@ checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bytes"
|
name = "bytes"
|
||||||
version = "1.11.0"
|
version = "1.11.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3"
|
checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
@@ -306,13 +306,12 @@ checksum = "75984efb6ed102a0d42db99afb6c1948f0380d1d91808d5529916e6c08b49d8d"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "console"
|
name = "console"
|
||||||
version = "0.16.2"
|
version = "0.16.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "03e45a4a8926227e4197636ba97a9fc9b00477e9f4bd711395687c5f0734bec4"
|
checksum = "d64e8af5551369d19cf50138de61f1c42074ab970f74e99be916646777f8fc87"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"encode_unicode",
|
"encode_unicode",
|
||||||
"libc",
|
"libc",
|
||||||
"once_cell",
|
|
||||||
"unicode-width",
|
"unicode-width",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
@@ -394,9 +393,9 @@ checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "darling"
|
name = "darling"
|
||||||
version = "0.21.3"
|
version = "0.23.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0"
|
checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"darling_core",
|
"darling_core",
|
||||||
"darling_macro",
|
"darling_macro",
|
||||||
@@ -404,11 +403,10 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "darling_core"
|
name = "darling_core"
|
||||||
version = "0.21.3"
|
version = "0.23.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4"
|
checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"fnv",
|
|
||||||
"ident_case",
|
"ident_case",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -418,9 +416,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "darling_macro"
|
name = "darling_macro"
|
||||||
version = "0.21.3"
|
version = "0.23.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81"
|
checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"darling_core",
|
"darling_core",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -683,6 +681,26 @@ dependencies = [
|
|||||||
"slab",
|
"slab",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "geologia"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"console",
|
||||||
|
"dialoguer",
|
||||||
|
"dotenvy",
|
||||||
|
"image",
|
||||||
|
"indicatif",
|
||||||
|
"reqwest",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"serde_with",
|
||||||
|
"tokio",
|
||||||
|
"tokio-stream",
|
||||||
|
"tokio-util",
|
||||||
|
"tracing",
|
||||||
|
"tracing-subscriber",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "getrandom"
|
name = "getrandom"
|
||||||
version = "0.2.16"
|
version = "0.2.16"
|
||||||
@@ -720,26 +738,6 @@ dependencies = [
|
|||||||
"weezl",
|
"weezl",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "google-genai"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"console",
|
|
||||||
"dialoguer",
|
|
||||||
"dotenvy",
|
|
||||||
"image",
|
|
||||||
"indicatif",
|
|
||||||
"reqwest",
|
|
||||||
"serde",
|
|
||||||
"serde_json",
|
|
||||||
"serde_with",
|
|
||||||
"tokio",
|
|
||||||
"tokio-stream",
|
|
||||||
"tokio-util",
|
|
||||||
"tracing",
|
|
||||||
"tracing-subscriber",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "h2"
|
name = "h2"
|
||||||
version = "0.4.12"
|
version = "0.4.12"
|
||||||
@@ -1025,9 +1023,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "image"
|
name = "image"
|
||||||
version = "0.25.9"
|
version = "0.25.10"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e6506c6c10786659413faa717ceebcb8f70731c0a60cbae39795fdf114519c1a"
|
checksum = "85ab80394333c02fe689eaf900ab500fbd0c2213da414687ebf995a65d5a6104"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
"byteorder-lite",
|
"byteorder-lite",
|
||||||
@@ -1043,8 +1041,8 @@ dependencies = [
|
|||||||
"rayon",
|
"rayon",
|
||||||
"rgb",
|
"rgb",
|
||||||
"tiff",
|
"tiff",
|
||||||
"zune-core 0.5.0",
|
"zune-core",
|
||||||
"zune-jpeg 0.5.5",
|
"zune-jpeg",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1088,9 +1086,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "indicatif"
|
name = "indicatif"
|
||||||
version = "0.18.3"
|
version = "0.18.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9375e112e4b463ec1b1c6c011953545c65a30164fbab5b581df32b3abf0dcb88"
|
checksum = "25470f23803092da7d239834776d653104d551bc4d7eacaf31e6837854b8e9eb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"console",
|
"console",
|
||||||
"portable-atomic",
|
"portable-atomic",
|
||||||
@@ -1197,9 +1195,9 @@ checksum = "7a79a3332a6609480d7d0c9eab957bca6b455b91bb84e66d19f5ff66294b85b8"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.178"
|
version = "0.2.185"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091"
|
checksum = "52ff2c0fe9bc6cb6b14a0592c2ff4fa9ceb83eea9db979b0487cd054946a2b8f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libfuzzer-sys"
|
name = "libfuzzer-sys"
|
||||||
@@ -1287,9 +1285,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mio"
|
name = "mio"
|
||||||
version = "1.1.0"
|
version = "1.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "69d83b0086dc8ecf3ce9ae2874b2d1290252e2a30720bea58a5c6639b0092873"
|
checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"wasi",
|
"wasi",
|
||||||
@@ -1298,9 +1296,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "moxcms"
|
name = "moxcms"
|
||||||
version = "0.7.10"
|
version = "0.8.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "80986bbbcf925ebd3be54c26613d861255284584501595cf418320c078945608"
|
checksum = "bb85c154ba489f01b25c0d36ae69a87e4a1c73a72631fc6c0eb6dde34a73e44b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"num-traits",
|
"num-traits",
|
||||||
"pxfm",
|
"pxfm",
|
||||||
@@ -1348,9 +1346,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-conv"
|
name = "num-conv"
|
||||||
version = "0.1.0"
|
version = "0.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
|
checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-derive"
|
name = "num-derive"
|
||||||
@@ -1472,9 +1470,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "portable-atomic"
|
name = "portable-atomic"
|
||||||
version = "1.11.1"
|
version = "1.13.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483"
|
checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "potential_utf"
|
name = "potential_utf"
|
||||||
@@ -1574,9 +1572,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quinn-proto"
|
name = "quinn-proto"
|
||||||
version = "0.11.13"
|
version = "0.11.14"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31"
|
checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aws-lc-rs",
|
"aws-lc-rs",
|
||||||
"bytes",
|
"bytes",
|
||||||
@@ -1689,9 +1687,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ravif"
|
name = "ravif"
|
||||||
version = "0.12.0"
|
version = "0.13.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ef69c1990ceef18a116855938e74793a5f7496ee907562bd0857b6ac734ab285"
|
checksum = "e52310197d971b0f5be7fe6b57530dcd27beb35c1b013f29d66c1ad73fbbcc45"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"avif-serialize",
|
"avif-serialize",
|
||||||
"imgref",
|
"imgref",
|
||||||
@@ -1898,9 +1896,9 @@ checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustls-webpki"
|
name = "rustls-webpki"
|
||||||
version = "0.103.8"
|
version = "0.103.10"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52"
|
checksum = "df33b2b81ac578cabaf06b89b0631153a3f416b0a886e8a7a1707fb51abbd1ef"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aws-lc-rs",
|
"aws-lc-rs",
|
||||||
"ring",
|
"ring",
|
||||||
@@ -2036,9 +2034,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_with"
|
name = "serde_with"
|
||||||
version = "3.16.1"
|
version = "3.18.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4fa237f2807440d238e0364a218270b98f767a00d3dada77b1c53ae88940e2e7"
|
checksum = "dd5414fad8e6907dbdd5bc441a50ae8d6e26151a03b1de04d89a5576de61d01f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64",
|
"base64",
|
||||||
"chrono",
|
"chrono",
|
||||||
@@ -2055,9 +2053,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_with_macros"
|
name = "serde_with_macros"
|
||||||
version = "3.16.1"
|
version = "3.18.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "52a8e3ca0ca629121f70ab50f95249e5a6f925cc0f6ffe8256c45b728875706c"
|
checksum = "d3db8978e608f1fe7357e211969fd9abdcae80bac1ba7a3369bb7eb6b404eb65"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"darling",
|
"darling",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
@@ -2124,12 +2122,12 @@ checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "socket2"
|
name = "socket2"
|
||||||
version = "0.6.1"
|
version = "0.6.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881"
|
checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"windows-sys 0.60.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2266,44 +2264,44 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tiff"
|
name = "tiff"
|
||||||
version = "0.10.3"
|
version = "0.11.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "af9605de7fee8d9551863fd692cce7637f548dbd9db9180fcc07ccc6d26c336f"
|
checksum = "b63feaf3343d35b6ca4d50483f94843803b0f51634937cc2ec519fc32232bc52"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"fax",
|
"fax",
|
||||||
"flate2",
|
"flate2",
|
||||||
"half",
|
"half",
|
||||||
"quick-error",
|
"quick-error",
|
||||||
"weezl",
|
"weezl",
|
||||||
"zune-jpeg 0.4.21",
|
"zune-jpeg",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "time"
|
name = "time"
|
||||||
version = "0.3.44"
|
version = "0.3.47"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d"
|
checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"deranged",
|
"deranged",
|
||||||
"itoa",
|
"itoa",
|
||||||
"num-conv",
|
"num-conv",
|
||||||
"powerfmt",
|
"powerfmt",
|
||||||
"serde",
|
"serde_core",
|
||||||
"time-core",
|
"time-core",
|
||||||
"time-macros",
|
"time-macros",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "time-core"
|
name = "time-core"
|
||||||
version = "0.1.6"
|
version = "0.1.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b"
|
checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "time-macros"
|
name = "time-macros"
|
||||||
version = "0.2.24"
|
version = "0.2.27"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3"
|
checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"num-conv",
|
"num-conv",
|
||||||
"time-core",
|
"time-core",
|
||||||
@@ -2336,9 +2334,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio"
|
name = "tokio"
|
||||||
version = "1.49.0"
|
version = "1.52.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86"
|
checksum = "b67dee974fe86fd92cc45b7a95fdd2f99a36a6d7b0d431a231178d3d670bbcc6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"libc",
|
"libc",
|
||||||
@@ -2353,9 +2351,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio-macros"
|
name = "tokio-macros"
|
||||||
version = "2.6.0"
|
version = "2.7.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5"
|
checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -2491,9 +2489,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tracing-subscriber"
|
name = "tracing-subscriber"
|
||||||
version = "0.3.22"
|
version = "0.3.23"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e"
|
checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"nu-ansi-term",
|
"nu-ansi-term",
|
||||||
"sharded-slab",
|
"sharded-slab",
|
||||||
@@ -3130,12 +3128,6 @@ dependencies = [
|
|||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "zune-core"
|
|
||||||
version = "0.4.12"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zune-core"
|
name = "zune-core"
|
||||||
version = "0.5.0"
|
version = "0.5.0"
|
||||||
@@ -3151,20 +3143,11 @@ dependencies = [
|
|||||||
"simd-adler32",
|
"simd-adler32",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "zune-jpeg"
|
|
||||||
version = "0.4.21"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "29ce2c8a9384ad323cf564b67da86e21d3cfdff87908bc1223ed5c99bc792713"
|
|
||||||
dependencies = [
|
|
||||||
"zune-core 0.4.12",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zune-jpeg"
|
name = "zune-jpeg"
|
||||||
version = "0.5.5"
|
version = "0.5.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dc6fb7703e32e9a07fb3f757360338b3a567a5054f21b5f52a666752e333d58e"
|
checksum = "dc6fb7703e32e9a07fb3f757360338b3a567a5054f21b5f52a666752e333d58e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"zune-core 0.5.0",
|
"zune-core",
|
||||||
]
|
]
|
||||||
|
|||||||
15
Cargo.toml
15
Cargo.toml
@@ -1,8 +1,9 @@
|
|||||||
|
|
||||||
[package]
|
[package]
|
||||||
name = "google-genai"
|
name = "geologia"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
license = "Apache-2.0"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
@@ -10,17 +11,17 @@ edition = "2024"
|
|||||||
reqwest = { version = "0.13", features = ["json", "gzip", "stream"] }
|
reqwest = { version = "0.13", features = ["json", "gzip", "stream"] }
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
serde_json = { version = "1" }
|
serde_json = { version = "1" }
|
||||||
serde_with = { version = "3.16", features = ["base64"] }
|
serde_with = { version = "3.18", features = ["base64"] }
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
tokio = { version = "1" }
|
tokio = { version = "1" }
|
||||||
tokio-stream = "0.1"
|
tokio-stream = "0.1"
|
||||||
tokio-util = "0.7.18"
|
tokio-util = "0.7.18"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
console = "0.16.2"
|
console = "0.16.3"
|
||||||
dialoguer = "0.12.0"
|
dialoguer = "0.12.0"
|
||||||
dotenvy = "0.15.7"
|
dotenvy = "0.15.7"
|
||||||
image = "0.25.9"
|
image = "0.25.10"
|
||||||
indicatif = "0.18.3"
|
indicatif = "0.18.4"
|
||||||
tokio = { version = "1.49.0", features = ["full"] }
|
tokio = { version = "1.51.0", features = ["full"] }
|
||||||
tracing-subscriber = "0.3.22"
|
tracing-subscriber = "0.3.23"
|
||||||
|
|||||||
191
LICENSE
Normal file
191
LICENSE
Normal file
@@ -0,0 +1,191 @@
|
|||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship made available under
|
||||||
|
the License, as indicated by a copyright notice that is included in
|
||||||
|
or attached to the work (an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other transformations
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean, as submitted to the Licensor for inclusion
|
||||||
|
in the Work by the copyright owner or by an individual or Legal Entity
|
||||||
|
authorized to submit on behalf of the copyright owner. For the purposes
|
||||||
|
of this definition, "submitted" means any form of electronic, verbal,
|
||||||
|
or written communication sent to the Licensor or its representatives,
|
||||||
|
including but not limited to communication on electronic mailing lists,
|
||||||
|
source code control systems, and issue tracking systems that are managed
|
||||||
|
by, or on behalf of, the Licensor for the purpose of discussing and
|
||||||
|
improving the Work, but excluding communication that is conspicuously
|
||||||
|
marked or designated in writing by the copyright owner as "Not a
|
||||||
|
Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any Legal Entity on behalf of
|
||||||
|
whom a Contribution has been received by the Licensor and included
|
||||||
|
within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by the combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a cross-claim
|
||||||
|
or counterclaim in a lawsuit) alleging that the Work or any
|
||||||
|
Contribution embodied within the Work constitutes direct or contributory
|
||||||
|
patent infringement, then any patent licenses granted to You under
|
||||||
|
this License for that Work shall terminate as of the date such
|
||||||
|
litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or Derivative
|
||||||
|
Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, You must include a readable copy of the
|
||||||
|
attribution notices contained within such NOTICE file, in
|
||||||
|
at least one of the following places: within a NOTICE text
|
||||||
|
file distributed as part of the Derivative Works; within
|
||||||
|
the Source form or documentation, if provided along with the
|
||||||
|
Derivative Works; or, within a display generated by the
|
||||||
|
Derivative Works, if and wherever such third-party notices
|
||||||
|
normally appear. The contents of the NOTICE file are for
|
||||||
|
informational purposes only and do not modify the License.
|
||||||
|
You may add Your own attribution notices within Derivative
|
||||||
|
Works that You distribute, alongside or in addition to the
|
||||||
|
NOTICE text from the Work, provided that such additional
|
||||||
|
attribution notices cannot be construed as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own license statement for Your modifications and
|
||||||
|
may provide additional grant of rights to use, copy, modify, merge,
|
||||||
|
publish, distribute, sublicense, and/or sell copies of the
|
||||||
|
Contribution, either on an entirely commercial basis or as a
|
||||||
|
separate component.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any conditions of TITLE,
|
||||||
|
MERCHANTIBILITY, FITNESS FOR A PARTICULAR PURPOSE, or NONINFRINGEMENT.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or exemplary damages of any nature, including but not
|
||||||
|
limited to loss of goodwill, work stoppage, computer failure or
|
||||||
|
malfunction, or any and all other commercial damages or losses, even
|
||||||
|
if such Contributor has been advised of the possibility of such
|
||||||
|
damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may offer such
|
||||||
|
obligations only on Your own behalf and on Your sole responsibility,
|
||||||
|
not on behalf of any other Contributor, and only if You agree to
|
||||||
|
indemnify, defend, and hold each Contributor harmless for any
|
||||||
|
liability incurred by, or claims asserted against, such Contributor
|
||||||
|
by reason of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the syntax of the file format. Please also
|
||||||
|
include a link to the license for the legal completeness.
|
||||||
|
|
||||||
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
10
README.md
10
README.md
@@ -1,4 +1,4 @@
|
|||||||
# google-genai
|
# geologia
|
||||||
|
|
||||||
A Rust client library for the Google Generative AI API.
|
A Rust client library for the Google Generative AI API.
|
||||||
|
|
||||||
@@ -30,14 +30,14 @@ To use this library in your own Rust application, add it as a dependency in your
|
|||||||
|
|
||||||
```toml
|
```toml
|
||||||
[dependencies]
|
[dependencies]
|
||||||
google-genai = "0.1.0" # Or the latest version
|
geologia = "0.1.0" # Or the latest version
|
||||||
```
|
```
|
||||||
|
|
||||||
Here is a basic example of how to use the `GeminiClient` to generate content:
|
Here is a basic example of how to use the `GeminiClient` to generate content:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
use std::env;
|
use std::env;
|
||||||
use google_genai::prelude::{Content, GeminiClient, GenerateContentRequest, Role};
|
use geologia::prelude::{Content, GeminiClient, GenerateContentRequest, Role};
|
||||||
|
|
||||||
async fn run() -> Result<(), Box<dyn std::error::Error>> {
|
async fn run() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
// Get the API key from the environment.
|
// Get the API key from the environment.
|
||||||
@@ -68,3 +68,7 @@ async fn run() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
This project is licensed under the [Apache License 2.0](LICENSE).
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
|
// Copyright 2026 Andre Cipriani Bandarra
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
use std::{env, error::Error};
|
use std::{env, error::Error};
|
||||||
|
|
||||||
use google_genai::prelude::{
|
use geologia::prelude::{
|
||||||
Content, FunctionDeclaration, FunctionResponse, GeminiClient, GenerateContentRequest, Part,
|
Content, FunctionDeclaration, FunctionResponse, GeminiClient, GenerateContentRequest, Part,
|
||||||
PartData, Role, Tools,
|
PartData, Role, Tools,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
|
// Copyright 2026 Andre Cipriani Bandarra
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
use std::{env, error::Error};
|
use std::{env, error::Error};
|
||||||
|
|
||||||
use google_genai::prelude::{Content, GeminiClient, GenerateContentRequest, Role};
|
use geologia::prelude::{Content, GeminiClient, GenerateContentRequest, Role};
|
||||||
use tokio_stream::StreamExt;
|
use tokio_stream::StreamExt;
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
|
// Copyright 2026 Andre Cipriani Bandarra
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
use std::{env, error::Error};
|
use std::{env, error::Error};
|
||||||
|
|
||||||
use google_genai::prelude::{Content, GeminiClient, GenerateContentRequest, Role};
|
use geologia::prelude::{Content, GeminiClient, GenerateContentRequest, Role};
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> Result<(), Box<dyn Error>> {
|
async fn main() -> Result<(), Box<dyn Error>> {
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
|
// Copyright 2026 Andre Cipriani Bandarra
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
use std::{env, error::Error, io::Cursor};
|
use std::{env, error::Error, io::Cursor};
|
||||||
|
|
||||||
use google_genai::prelude::{
|
use geologia::prelude::{
|
||||||
GeminiClient, PersonGeneration, PredictImageRequest, PredictImageRequestParameters,
|
GeminiClient, PersonGeneration, PredictImageRequest, PredictImageRequestParameters,
|
||||||
PredictImageRequestParametersOutputOptions, PredictImageRequestPrompt,
|
PredictImageRequestParametersOutputOptions, PredictImageRequestPrompt,
|
||||||
PredictImageSafetySetting,
|
PredictImageSafetySetting,
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
|
// Copyright 2026 Andre Cipriani Bandarra
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
use std::env;
|
use std::env;
|
||||||
|
|
||||||
use google_genai::{
|
use geologia::{
|
||||||
network::event_source::EventSource,
|
network::event_source::EventSource,
|
||||||
prelude::{Content, GenerateContentRequest, Role},
|
prelude::{Content, GenerateContentRequest, Role},
|
||||||
};
|
};
|
||||||
|
|||||||
127
src/client.rs
127
src/client.rs
@@ -1,3 +1,6 @@
|
|||||||
|
// Copyright 2026 Andre Cipriani Bandarra
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
use crate::error::{Error as GeminiError, Result as GeminiResult};
|
use crate::error::{Error as GeminiError, Result as GeminiResult};
|
||||||
use crate::network::event_source::{EventSource, ServerSentEvent};
|
use crate::network::event_source::{EventSource, ServerSentEvent};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
@@ -6,18 +9,34 @@ use tokio_stream::{Stream, StreamExt};
|
|||||||
use tokio_util::codec::LinesCodecError;
|
use tokio_util::codec::LinesCodecError;
|
||||||
use tracing::error;
|
use tracing::error;
|
||||||
|
|
||||||
pub static AUTH_SCOPE: &[&str] = &["https://www.googleapis.com/auth/cloud-platform"];
|
/// Async client for the Google Gemini API.
|
||||||
|
///
|
||||||
|
/// Provides methods for content generation, streaming, token counting, text embeddings,
|
||||||
|
/// and image prediction. All requests are authenticated with an API key passed at
|
||||||
|
/// construction time.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```no_run
|
||||||
|
/// use geologia::prelude::*;
|
||||||
|
///
|
||||||
|
/// # async fn run() -> geologia::error::Result<()> {
|
||||||
|
/// let client = GeminiClient::new("YOUR_API_KEY".into());
|
||||||
|
/// let request = GenerateContentRequest::builder()
|
||||||
|
/// .contents(vec![Content::builder().add_text_part("Hi!").build()])
|
||||||
|
/// .build();
|
||||||
|
/// let response = client.generate_content(&request, "gemini-2.0-flash").await?;
|
||||||
|
/// # Ok(())
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct GeminiClient {
|
pub struct GeminiClient {
|
||||||
client: reqwest::Client,
|
client: reqwest::Client,
|
||||||
api_key: String,
|
api_key: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe impl Send for GeminiClient {}
|
|
||||||
unsafe impl Sync for GeminiClient {}
|
|
||||||
|
|
||||||
impl GeminiClient {
|
impl GeminiClient {
|
||||||
|
/// Creates a new [`GeminiClient`] with the given API key.
|
||||||
pub fn new(api_key: String) -> Self {
|
pub fn new(api_key: String) -> Self {
|
||||||
GeminiClient {
|
GeminiClient {
|
||||||
client: reqwest::Client::new(),
|
client: reqwest::Client::new(),
|
||||||
@@ -25,6 +44,10 @@ impl GeminiClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sends a content generation request and returns a stream of response chunks via SSE.
|
||||||
|
///
|
||||||
|
/// Each item in the stream is a [`GenerateContentResponseResult`] containing one or more
|
||||||
|
/// candidates. Useful for displaying incremental output as it is generated.
|
||||||
pub async fn stream_generate_content(
|
pub async fn stream_generate_content(
|
||||||
&self,
|
&self,
|
||||||
request: &GenerateContentRequest,
|
request: &GenerateContentRequest,
|
||||||
@@ -57,6 +80,9 @@ impl GeminiClient {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sends a content generation request and returns the complete response.
|
||||||
|
///
|
||||||
|
/// For streaming responses, use [`stream_generate_content`](Self::stream_generate_content).
|
||||||
pub async fn generate_content(
|
pub async fn generate_content(
|
||||||
&self,
|
&self,
|
||||||
request: &GenerateContentRequest,
|
request: &GenerateContentRequest,
|
||||||
@@ -99,50 +125,12 @@ impl GeminiClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Prompts a conversation to the model.
|
/// Generates text embeddings for the given input.
|
||||||
pub async fn prompt_conversation(
|
|
||||||
&self,
|
|
||||||
messages: &[Message],
|
|
||||||
model: &str,
|
|
||||||
) -> GeminiResult<Message> {
|
|
||||||
let request = GenerateContentRequest {
|
|
||||||
contents: messages
|
|
||||||
.iter()
|
|
||||||
.map(|m| Content {
|
|
||||||
role: Some(m.role),
|
|
||||||
parts: Some(vec![Part::from_text(m.text.clone())]),
|
|
||||||
})
|
|
||||||
.collect(),
|
|
||||||
generation_config: None,
|
|
||||||
tools: None,
|
|
||||||
system_instruction: None,
|
|
||||||
safety_settings: None,
|
|
||||||
};
|
|
||||||
|
|
||||||
let response = self.generate_content(&request, model).await?;
|
|
||||||
|
|
||||||
// Check for errors in the response.
|
|
||||||
let mut candidates = GeminiClient::collect_text_from_response(&response);
|
|
||||||
|
|
||||||
match candidates.pop() {
|
|
||||||
Some(text) => Ok(Message::new(Role::Model, &text)),
|
|
||||||
None => Err(GeminiError::NoCandidatesError),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn collect_text_from_response(response: &GenerateContentResponseResult) -> Vec<String> {
|
|
||||||
response
|
|
||||||
.candidates
|
|
||||||
.iter()
|
|
||||||
.filter_map(Candidate::get_text)
|
|
||||||
.collect::<Vec<String>>()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn text_embeddings(
|
pub async fn text_embeddings(
|
||||||
&self,
|
&self,
|
||||||
request: &TextEmbeddingRequest,
|
request: &TextEmbeddingRequest,
|
||||||
model: &str,
|
model: &str,
|
||||||
) -> GeminiResult<TextEmbeddingResponse> {
|
) -> GeminiResult<TextEmbeddingResponseOk> {
|
||||||
let endpoint_url =
|
let endpoint_url =
|
||||||
format!("https://generativelanguage.googleapis.com/v1beta/models/{model}:predict");
|
format!("https://generativelanguage.googleapis.com/v1beta/models/{model}:predict");
|
||||||
let resp = self
|
let resp = self
|
||||||
@@ -152,16 +140,38 @@ impl GeminiClient {
|
|||||||
.json(&request)
|
.json(&request)
|
||||||
.send()
|
.send()
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
let status = resp.status();
|
||||||
let txt_json = resp.text().await?;
|
let txt_json = resp.text().await?;
|
||||||
tracing::debug!("text_embeddings response: {:?}", txt_json);
|
tracing::debug!("text_embeddings response: {:?}", txt_json);
|
||||||
Ok(serde_json::from_str::<TextEmbeddingResponse>(&txt_json)?)
|
|
||||||
|
if !status.is_success() {
|
||||||
|
if let Ok(gemini_error) =
|
||||||
|
serde_json::from_str::<crate::types::GeminiApiError>(&txt_json)
|
||||||
|
{
|
||||||
|
return Err(GeminiError::GeminiError(gemini_error));
|
||||||
|
}
|
||||||
|
return Err(GeminiError::GenericApiError {
|
||||||
|
status: status.as_u16(),
|
||||||
|
body: txt_json,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
match serde_json::from_str::<TextEmbeddingResponse>(&txt_json) {
|
||||||
|
Ok(response) => Ok(response.into_result()?),
|
||||||
|
Err(e) => {
|
||||||
|
error!(response = txt_json, error = ?e, "Failed to parse response");
|
||||||
|
Err(e.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Counts the number of tokens in the given content.
|
||||||
pub async fn count_tokens(
|
pub async fn count_tokens(
|
||||||
&self,
|
&self,
|
||||||
request: &CountTokensRequest,
|
request: &CountTokensRequest,
|
||||||
model: &str,
|
model: &str,
|
||||||
) -> GeminiResult<CountTokensResponse> {
|
) -> GeminiResult<CountTokensResponseResult> {
|
||||||
let endpoint_url =
|
let endpoint_url =
|
||||||
format!("https://generativelanguage.googleapis.com/v1beta/models/{model}:countTokens");
|
format!("https://generativelanguage.googleapis.com/v1beta/models/{model}:countTokens");
|
||||||
let resp = self
|
let resp = self
|
||||||
@@ -172,11 +182,32 @@ impl GeminiClient {
|
|||||||
.send()
|
.send()
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
let status = resp.status();
|
||||||
let txt_json = resp.text().await?;
|
let txt_json = resp.text().await?;
|
||||||
tracing::debug!("count_tokens response: {:?}", txt_json);
|
tracing::debug!("count_tokens response: {:?}", txt_json);
|
||||||
Ok(serde_json::from_str(&txt_json)?)
|
|
||||||
|
if !status.is_success() {
|
||||||
|
if let Ok(gemini_error) =
|
||||||
|
serde_json::from_str::<crate::types::GeminiApiError>(&txt_json)
|
||||||
|
{
|
||||||
|
return Err(GeminiError::GeminiError(gemini_error));
|
||||||
|
}
|
||||||
|
return Err(GeminiError::GenericApiError {
|
||||||
|
status: status.as_u16(),
|
||||||
|
body: txt_json,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
match serde_json::from_str::<CountTokensResponse>(&txt_json) {
|
||||||
|
Ok(response) => Ok(response.into_result()?),
|
||||||
|
Err(e) => {
|
||||||
|
error!(response = txt_json, error = ?e, "Failed to parse response");
|
||||||
|
Err(e.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Generates images from a text prompt using an Imagen model.
|
||||||
pub async fn predict_image(
|
pub async fn predict_image(
|
||||||
&self,
|
&self,
|
||||||
request: &PredictImageRequest,
|
request: &PredictImageRequest,
|
||||||
|
|||||||
@@ -1,42 +0,0 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
use crate::{error::Result, prelude::*};
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
|
||||||
pub struct Message {
|
|
||||||
pub role: Role,
|
|
||||||
pub text: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Message {
|
|
||||||
pub fn new(role: Role, text: &str) -> Self {
|
|
||||||
Message {
|
|
||||||
role,
|
|
||||||
text: text.to_string(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
|
||||||
pub struct Dialogue {
|
|
||||||
model: String,
|
|
||||||
messages: Vec<Message>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Dialogue {
|
|
||||||
pub fn new(model: &str) -> Self {
|
|
||||||
Dialogue {
|
|
||||||
model: model.to_string(),
|
|
||||||
messages: vec![],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn do_turn(&mut self, gemini: &GeminiClient, message: &str) -> Result<Message> {
|
|
||||||
self.messages.push(Message::new(Role::User, message));
|
|
||||||
let response = gemini
|
|
||||||
.prompt_conversation(&self.messages, &self.model)
|
|
||||||
.await?;
|
|
||||||
self.messages.push(response.clone());
|
|
||||||
Ok(response)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
25
src/error.rs
25
src/error.rs
@@ -1,21 +1,42 @@
|
|||||||
|
// Copyright 2026 Andre Cipriani Bandarra
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
//! Error types for the Google Gemini client.
|
||||||
|
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
use tokio_util::codec::LinesCodecError;
|
use tokio_util::codec::LinesCodecError;
|
||||||
|
|
||||||
use crate::types;
|
use crate::types;
|
||||||
|
|
||||||
|
/// A type alias for `Result<T, error::Error>`.
|
||||||
pub type Result<T> = std::result::Result<T, Error>;
|
pub type Result<T> = std::result::Result<T, Error>;
|
||||||
|
|
||||||
|
/// Errors that can occur when using the Gemini client.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
|
/// An environment variable required for configuration was missing or invalid.
|
||||||
Env(std::env::VarError),
|
Env(std::env::VarError),
|
||||||
|
/// An HTTP transport error from the underlying `reqwest` client.
|
||||||
HttpClient(reqwest::Error),
|
HttpClient(reqwest::Error),
|
||||||
|
/// A JSON serialization or deserialization error.
|
||||||
Serde(serde_json::Error),
|
Serde(serde_json::Error),
|
||||||
|
/// A structured error returned by the Vertex AI API.
|
||||||
VertexError(types::VertexApiError),
|
VertexError(types::VertexApiError),
|
||||||
|
/// A structured error returned by the Gemini API.
|
||||||
GeminiError(types::GeminiApiError),
|
GeminiError(types::GeminiApiError),
|
||||||
|
/// The API response contained no candidate completions.
|
||||||
NoCandidatesError,
|
NoCandidatesError,
|
||||||
|
/// An error occurred while decoding the SSE event stream.
|
||||||
EventSourceError(LinesCodecError),
|
EventSourceError(LinesCodecError),
|
||||||
|
/// The SSE event stream closed unexpectedly.
|
||||||
EventSourceClosedError,
|
EventSourceClosedError,
|
||||||
GenericApiError { status: u16, body: String },
|
/// An API error that could not be parsed into a structured error type.
|
||||||
|
GenericApiError {
|
||||||
|
/// The HTTP status code.
|
||||||
|
status: u16,
|
||||||
|
/// The raw response body.
|
||||||
|
body: String,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for Error {
|
impl Display for Error {
|
||||||
@@ -34,7 +55,7 @@ impl Display for Error {
|
|||||||
write!(f, "No candidates returned for the prompt")
|
write!(f, "No candidates returned for the prompt")
|
||||||
}
|
}
|
||||||
Error::EventSourceError(e) => {
|
Error::EventSourceError(e) => {
|
||||||
write!(f, "EventSourrce Error: {e}")
|
write!(f, "EventSource Error: {e}")
|
||||||
}
|
}
|
||||||
Error::EventSourceClosedError => {
|
Error::EventSourceClosedError => {
|
||||||
write!(f, "EventSource closed error")
|
write!(f, "EventSource closed error")
|
||||||
|
|||||||
34
src/lib.rs
34
src/lib.rs
@@ -1,11 +1,41 @@
|
|||||||
|
// Copyright 2026 Andre Cipriani Bandarra
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
//! Async Rust client for the Google Gemini API.
|
||||||
|
//!
|
||||||
|
//! This crate provides a high-level async client for interacting with Google's Gemini
|
||||||
|
//! generative AI models. It supports content generation (including streaming via SSE),
|
||||||
|
//! token counting, text embeddings, and image generation.
|
||||||
|
//!
|
||||||
|
//! # Usage
|
||||||
|
//!
|
||||||
|
//! ```no_run
|
||||||
|
//! use geologia::prelude::*;
|
||||||
|
//!
|
||||||
|
//! # async fn run() -> geologia::error::Result<()> {
|
||||||
|
//! let client = GeminiClient::new("YOUR_API_KEY".into());
|
||||||
|
//!
|
||||||
|
//! let request = GenerateContentRequest::builder()
|
||||||
|
//! .contents(vec![
|
||||||
|
//! Content::builder().add_text_part("Hello, Gemini!").build()
|
||||||
|
//! ])
|
||||||
|
//! .build();
|
||||||
|
//!
|
||||||
|
//! let response = client.generate_content(&request, "gemini-2.0-flash").await?;
|
||||||
|
//! # Ok(())
|
||||||
|
//! # }
|
||||||
|
//! ```
|
||||||
|
|
||||||
mod client;
|
mod client;
|
||||||
mod dialogue;
|
|
||||||
pub mod error;
|
pub mod error;
|
||||||
pub mod network;
|
pub mod network;
|
||||||
mod types;
|
mod types;
|
||||||
|
|
||||||
|
/// Convenience re-exports of the most commonly used types.
|
||||||
|
///
|
||||||
|
/// Importing `use geologia::prelude::*` brings [`GeminiClient`](crate::prelude::GeminiClient)
|
||||||
|
/// and all request/response types into scope.
|
||||||
pub mod prelude {
|
pub mod prelude {
|
||||||
pub use crate::client::*;
|
pub use crate::client::*;
|
||||||
pub use crate::dialogue::*;
|
|
||||||
pub use crate::types::*;
|
pub use crate::types::*;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,14 @@
|
|||||||
|
// Copyright 2026 Andre Cipriani Bandarra
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
//! Server-Sent Events (SSE) decoder for streaming HTTP responses.
|
||||||
|
//!
|
||||||
|
//! Implements a [`tokio_util::codec::Decoder`] that parses an SSE byte stream into
|
||||||
|
//! [`ServerSentEvent`] values. Used internally by [`GeminiClient::stream_generate_content`]
|
||||||
|
//! to process chunked model responses.
|
||||||
|
//!
|
||||||
|
//! [`GeminiClient::stream_generate_content`]: crate::prelude::GeminiClient::stream_generate_content
|
||||||
|
|
||||||
use reqwest::Response;
|
use reqwest::Response;
|
||||||
use std::mem;
|
use std::mem;
|
||||||
use tokio_stream::{Stream, StreamExt};
|
use tokio_stream::{Stream, StreamExt};
|
||||||
@@ -12,7 +23,9 @@ static DATA: &str = "data: ";
|
|||||||
static ID: &str = "id: ";
|
static ID: &str = "id: ";
|
||||||
static RETRY: &str = "retry: ";
|
static RETRY: &str = "retry: ";
|
||||||
|
|
||||||
|
/// Extension trait for converting an HTTP response into a stream of [`ServerSentEvent`]s.
|
||||||
pub trait EventSource {
|
pub trait EventSource {
|
||||||
|
/// Consumes the response and returns a stream of parsed SSE events.
|
||||||
fn event_stream(self) -> impl Stream<Item = Result<ServerSentEvent, LinesCodecError>>;
|
fn event_stream(self) -> impl Stream<Item = Result<ServerSentEvent, LinesCodecError>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -22,14 +35,25 @@ impl EventSource for Response {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A parsed Server-Sent Event.
|
||||||
|
///
|
||||||
|
/// Fields correspond to the standard SSE fields: `event`, `data`, `id`, and `retry`.
|
||||||
|
/// Multiple `data:` lines within a single event are concatenated with newline separators.
|
||||||
#[derive(Debug, Default, Clone)]
|
#[derive(Debug, Default, Clone)]
|
||||||
pub struct ServerSentEvent {
|
pub struct ServerSentEvent {
|
||||||
|
/// The event type (from the `event:` field).
|
||||||
pub event: Option<String>,
|
pub event: Option<String>,
|
||||||
|
/// The event payload (from one or more `data:` fields, joined by `\n`).
|
||||||
pub data: Option<String>,
|
pub data: Option<String>,
|
||||||
|
/// The event ID (from the `id:` field).
|
||||||
pub id: Option<String>,
|
pub id: Option<String>,
|
||||||
|
/// The reconnection time in milliseconds (from the `retry:` field).
|
||||||
pub retry: Option<usize>,
|
pub retry: Option<usize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A [`Decoder`] that parses a byte stream of SSE-formatted data into [`ServerSentEvent`]s.
|
||||||
|
///
|
||||||
|
/// Wraps a [`LinesCodec`] and accumulates fields until an empty line signals the end of an event.
|
||||||
pub struct ServerSentEventsCodec {
|
pub struct ServerSentEventsCodec {
|
||||||
lines_code: LinesCodec,
|
lines_code: LinesCodec,
|
||||||
next: ServerSentEvent,
|
next: ServerSentEvent,
|
||||||
@@ -42,6 +66,7 @@ impl Default for ServerSentEventsCodec {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ServerSentEventsCodec {
|
impl ServerSentEventsCodec {
|
||||||
|
/// Creates a new SSE codec.
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
lines_code: LinesCodec::new(),
|
lines_code: LinesCodec::new(),
|
||||||
@@ -73,7 +98,12 @@ impl Decoder for ServerSentEventsCodec {
|
|||||||
self.next.event = Some(line);
|
self.next.event = Some(line);
|
||||||
} else if line.starts_with(DATA) {
|
} else if line.starts_with(DATA) {
|
||||||
line.drain(..DATA.len());
|
line.drain(..DATA.len());
|
||||||
self.next.data = Some(line)
|
if let Some(ref mut existing) = self.next.data {
|
||||||
|
existing.push('\n');
|
||||||
|
existing.push_str(&line);
|
||||||
|
} else {
|
||||||
|
self.next.data = Some(line);
|
||||||
|
}
|
||||||
} else if line.starts_with(ID) {
|
} else if line.starts_with(ID) {
|
||||||
line.drain(..ID.len());
|
line.drain(..ID.len());
|
||||||
self.next.id = Some(line);
|
self.next.id = Some(line);
|
||||||
@@ -90,6 +120,9 @@ impl Decoder for ServerSentEventsCodec {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Converts a [`Response`] into a stream of [`ServerSentEvent`]s.
|
||||||
|
///
|
||||||
|
/// The response body is read as a byte stream and decoded using [`ServerSentEventsCodec`].
|
||||||
pub fn stream_response(
|
pub fn stream_response(
|
||||||
response: Response,
|
response: Response,
|
||||||
) -> impl Stream<Item = Result<ServerSentEvent, LinesCodecError>> {
|
) -> impl Stream<Item = Result<ServerSentEvent, LinesCodecError>> {
|
||||||
|
|||||||
@@ -1 +1,6 @@
|
|||||||
|
// Copyright 2026 Andre Cipriani Bandarra
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
//! Networking utilities for streaming HTTP responses.
|
||||||
|
|
||||||
pub mod event_source;
|
pub mod event_source;
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
// Copyright 2026 Andre Cipriani Bandarra
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
use std::{fmt::Display, str::FromStr, vec};
|
use std::{fmt::Display, str::FromStr, vec};
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
@@ -5,13 +8,21 @@ use serde_json::Value;
|
|||||||
|
|
||||||
use crate::types::FunctionResponse;
|
use crate::types::FunctionResponse;
|
||||||
|
|
||||||
|
/// A conversation message containing one or more [`Part`]s.
|
||||||
|
///
|
||||||
|
/// See <https://ai.google.dev/api/caching#Content>.
|
||||||
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
|
||||||
pub struct Content {
|
pub struct Content {
|
||||||
|
/// The role of the message author (`user` or `model`).
|
||||||
pub role: Option<Role>,
|
pub role: Option<Role>,
|
||||||
|
/// The ordered parts that make up this message.
|
||||||
pub parts: Option<Vec<Part>>,
|
pub parts: Option<Vec<Part>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Content {
|
impl Content {
|
||||||
|
/// Concatenates all [`PartData::Text`] parts into a single string.
|
||||||
|
///
|
||||||
|
/// Returns `None` if there are no parts.
|
||||||
pub fn get_text(&self) -> Option<String> {
|
pub fn get_text(&self) -> Option<String> {
|
||||||
self.parts.as_ref().map(|parts| {
|
self.parts.as_ref().map(|parts| {
|
||||||
parts
|
parts
|
||||||
@@ -24,25 +35,30 @@ impl Content {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates a [`Content`] containing a single text part, suitable for use as a system instruction.
|
||||||
pub fn system_prompt<S: Into<String>>(system_prompt: S) -> Self {
|
pub fn system_prompt<S: Into<String>>(system_prompt: S) -> Self {
|
||||||
Self::builder().add_text_part(system_prompt).build()
|
Self::builder().add_text_part(system_prompt).build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a new [`ContentBuilder`].
|
||||||
pub fn builder() -> ContentBuilder {
|
pub fn builder() -> ContentBuilder {
|
||||||
ContentBuilder::default()
|
ContentBuilder::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
/// Builder for constructing [`Content`] values incrementally.
|
||||||
|
#[derive(Clone, Debug, Default)]
|
||||||
pub struct ContentBuilder {
|
pub struct ContentBuilder {
|
||||||
content: Content,
|
content: Content,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ContentBuilder {
|
impl ContentBuilder {
|
||||||
|
/// Appends a text part to this content.
|
||||||
pub fn add_text_part<T: Into<String>>(self, text: T) -> Self {
|
pub fn add_text_part<T: Into<String>>(self, text: T) -> Self {
|
||||||
self.add_part(Part::from_text(text.into()))
|
self.add_part(Part::from_text(text.into()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Appends an arbitrary [`Part`] to this content.
|
||||||
pub fn add_part(mut self, part: Part) -> Self {
|
pub fn add_part(mut self, part: Part) -> Self {
|
||||||
match &mut self.content.parts {
|
match &mut self.content.parts {
|
||||||
Some(parts) => parts.push(part),
|
Some(parts) => parts.push(part),
|
||||||
@@ -51,16 +67,19 @@ impl ContentBuilder {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets the [`Role`] for this content.
|
||||||
pub fn role(mut self, role: Role) -> Self {
|
pub fn role(mut self, role: Role) -> Self {
|
||||||
self.content.role = Some(role);
|
self.content.role = Some(role);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Consumes the builder and returns the constructed [`Content`].
|
||||||
pub fn build(self) -> Content {
|
pub fn build(self) -> Content {
|
||||||
self.content
|
self.content
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The role of a message author in a conversation.
|
||||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "lowercase")]
|
#[serde(rename_all = "lowercase")]
|
||||||
pub enum Role {
|
pub enum Role {
|
||||||
@@ -90,7 +109,9 @@ impl FromStr for Role {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// See https://ai.google.dev/api/caching#Part
|
/// A single unit of content within a [`Content`] message.
|
||||||
|
///
|
||||||
|
/// See <https://ai.google.dev/api/caching#Part>.
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct Part {
|
pub struct Part {
|
||||||
@@ -108,29 +129,42 @@ pub struct Part {
|
|||||||
pub data: PartData, // Create enum for data.
|
pub data: PartData, // Create enum for data.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The payload of a [`Part`], representing different content types.
|
||||||
|
///
|
||||||
|
/// See <https://ai.google.dev/api/caching#Part>.
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub enum PartData {
|
pub enum PartData {
|
||||||
|
/// Plain text content.
|
||||||
Text(String),
|
Text(String),
|
||||||
// https://ai.google.dev/api/caching#Blob
|
/// Binary data encoded inline. See <https://ai.google.dev/api/caching#Blob>.
|
||||||
InlineData {
|
InlineData {
|
||||||
|
/// The IANA MIME type of the data (e.g. `"image/png"`).
|
||||||
mime_type: String,
|
mime_type: String,
|
||||||
|
/// Base64-encoded binary data.
|
||||||
data: String,
|
data: String,
|
||||||
},
|
},
|
||||||
// https://ai.google.dev/api/caching#FunctionCall
|
/// A function call requested by the model. See <https://ai.google.dev/api/caching#FunctionCall>.
|
||||||
FunctionCall {
|
FunctionCall {
|
||||||
|
/// Optional unique identifier for the function call.
|
||||||
id: Option<String>,
|
id: Option<String>,
|
||||||
|
/// The name of the function to call.
|
||||||
name: String,
|
name: String,
|
||||||
|
/// The arguments to pass, as a JSON object.
|
||||||
args: Option<Value>,
|
args: Option<Value>,
|
||||||
},
|
},
|
||||||
// https://ai.google.dev/api/caching#FunctionResponse
|
/// A response to a function call. See <https://ai.google.dev/api/caching#FunctionResponse>.
|
||||||
FunctionResponse(FunctionResponse),
|
FunctionResponse(FunctionResponse),
|
||||||
|
/// A reference to a file stored in the API.
|
||||||
FileData(Value),
|
FileData(Value),
|
||||||
|
/// Code to be executed by the model.
|
||||||
ExecutableCode(Value),
|
ExecutableCode(Value),
|
||||||
|
/// The result of executing code.
|
||||||
CodeExecutionResult(Value),
|
CodeExecutionResult(Value),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Part {
|
impl Part {
|
||||||
|
/// Creates a [`Part`] containing only text.
|
||||||
pub fn from_text<S: Into<String>>(text: S) -> Self {
|
pub fn from_text<S: Into<String>>(text: S) -> Self {
|
||||||
Self {
|
Self {
|
||||||
thought: None,
|
thought: None,
|
||||||
|
|||||||
@@ -1,24 +1,38 @@
|
|||||||
|
// Copyright 2026 Andre Cipriani Bandarra
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::error::{Error, Result};
|
||||||
|
|
||||||
use super::Content;
|
use super::Content;
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
/// Request body for the `countTokens` endpoint.
|
||||||
|
///
|
||||||
|
/// Use [`CountTokensRequest::builder`] for ergonomic construction.
|
||||||
|
///
|
||||||
|
/// See <https://ai.google.dev/api/tokens#method:-models.counttokens>.
|
||||||
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
pub struct CountTokensRequest {
|
pub struct CountTokensRequest {
|
||||||
|
/// The content to count tokens for.
|
||||||
pub contents: Content,
|
pub contents: Content,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CountTokensRequest {
|
impl CountTokensRequest {
|
||||||
|
/// Returns a new [`CountTokensRequestBuilder`].
|
||||||
pub fn builder() -> CountTokensRequestBuilder {
|
pub fn builder() -> CountTokensRequestBuilder {
|
||||||
CountTokensRequestBuilder::default()
|
CountTokensRequestBuilder::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
/// Builder for [`CountTokensRequest`].
|
||||||
|
#[derive(Debug, Default)]
|
||||||
pub struct CountTokensRequestBuilder {
|
pub struct CountTokensRequestBuilder {
|
||||||
contents: Content,
|
contents: Content,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CountTokensRequestBuilder {
|
impl CountTokensRequestBuilder {
|
||||||
|
/// Creates a builder pre-populated with a single text prompt.
|
||||||
pub fn from_prompt(prompt: &str) -> Self {
|
pub fn from_prompt(prompt: &str) -> Self {
|
||||||
CountTokensRequestBuilder {
|
CountTokensRequestBuilder {
|
||||||
contents: Content {
|
contents: Content {
|
||||||
@@ -28,6 +42,7 @@ impl CountTokensRequestBuilder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Consumes the builder and returns the constructed [`CountTokensRequest`].
|
||||||
pub fn build(self) -> CountTokensRequest {
|
pub fn build(self) -> CountTokensRequest {
|
||||||
CountTokensRequest {
|
CountTokensRequest {
|
||||||
contents: self.contents,
|
contents: self.contents,
|
||||||
@@ -35,15 +50,32 @@ impl CountTokensRequestBuilder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
/// The raw response from the `countTokens` endpoint, which may be a success or an error.
|
||||||
|
///
|
||||||
|
/// Use [`into_result`](CountTokensResponse::into_result) to convert into a standard `Result`.
|
||||||
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
#[serde(untagged)]
|
#[serde(untagged)]
|
||||||
pub enum CountTokensResponse {
|
pub enum CountTokensResponse {
|
||||||
#[serde(rename_all = "camelCase")]
|
Ok(CountTokensResponseResult),
|
||||||
Ok {
|
Error { error: super::VertexApiError },
|
||||||
total_tokens: i32,
|
}
|
||||||
total_billable_characters: u32,
|
|
||||||
},
|
impl CountTokensResponse {
|
||||||
Error {
|
/// Converts this response into a `Result`, mapping the error variant to [`crate::error::Error`].
|
||||||
error: super::VertexApiError,
|
pub fn into_result(self) -> Result<CountTokensResponseResult> {
|
||||||
},
|
match self {
|
||||||
|
CountTokensResponse::Ok(result) => Ok(result),
|
||||||
|
CountTokensResponse::Error { error } => Err(Error::VertexError(error)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A successful response from the `countTokens` endpoint.
|
||||||
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct CountTokensResponseResult {
|
||||||
|
/// The total number of tokens in the input.
|
||||||
|
pub total_tokens: i32,
|
||||||
|
/// The total number of billable characters in the input.
|
||||||
|
pub total_billable_characters: u32,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,20 @@
|
|||||||
|
// Copyright 2026 Andre Cipriani Bandarra
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
use std::fmt::Formatter;
|
use std::fmt::Formatter;
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
/// A structured error returned by the Vertex AI / Gemini API.
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
pub struct VertexApiError {
|
pub struct VertexApiError {
|
||||||
|
/// The HTTP status code.
|
||||||
pub code: i32,
|
pub code: i32,
|
||||||
|
/// A human-readable error message.
|
||||||
pub message: String,
|
pub message: String,
|
||||||
|
/// The gRPC status string (e.g. `"INVALID_ARGUMENT"`).
|
||||||
pub status: String,
|
pub status: String,
|
||||||
|
/// Optional additional error details.
|
||||||
pub details: Option<Vec<serde_json::Value>>,
|
pub details: Option<Vec<serde_json::Value>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -19,49 +27,23 @@ impl core::fmt::Display for VertexApiError {
|
|||||||
|
|
||||||
impl std::error::Error for VertexApiError {}
|
impl std::error::Error for VertexApiError {}
|
||||||
|
|
||||||
|
/// A wrapper around [`VertexApiError`] matching the Gemini API error response format.
|
||||||
|
///
|
||||||
|
/// The Gemini API nests the error details inside an `error` field.
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
pub struct GeminiApiError {
|
pub struct GeminiApiError {
|
||||||
|
/// The inner error details.
|
||||||
pub error: VertexApiError,
|
pub error: VertexApiError,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl core::fmt::Display for GeminiApiError {
|
impl core::fmt::Display for GeminiApiError {
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
|
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
|
||||||
write!(f, "Gemini API Error {} - {}", self.error.code, self.error.message)
|
write!(
|
||||||
|
f,
|
||||||
|
"Gemini API Error {} - {}",
|
||||||
|
self.error.code, self.error.message
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::error::Error for GeminiApiError {}
|
impl std::error::Error for GeminiApiError {}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
|
||||||
pub struct Link {
|
|
||||||
pub description: String,
|
|
||||||
pub url: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
|
||||||
#[serde(tag = "@type")]
|
|
||||||
pub enum ErrorType {
|
|
||||||
#[serde(rename = "type.googleapis.com/google.rpc.ErrorInfo")]
|
|
||||||
ErrorInfo { metadata: ErrorInfoMetadata },
|
|
||||||
|
|
||||||
#[serde(rename = "type.googleapis.com/google.rpc.Help")]
|
|
||||||
Help { links: Vec<Link> },
|
|
||||||
|
|
||||||
#[serde(rename = "type.googleapis.com/google.rpc.BadRequest")]
|
|
||||||
BadRequest {
|
|
||||||
#[serde(rename = "fieldViolations")]
|
|
||||||
field_violations: Vec<FieldViolation>,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
|
||||||
pub struct ErrorInfoMetadata {
|
|
||||||
pub service: String,
|
|
||||||
pub consumer: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
|
||||||
pub struct FieldViolation {
|
|
||||||
pub field: String,
|
|
||||||
pub description: String,
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,10 +1,18 @@
|
|||||||
|
// Copyright 2026 Andre Cipriani Bandarra
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
|
||||||
use super::{Content, VertexApiError};
|
use super::{Content, VertexApiError};
|
||||||
use crate::error::Result;
|
use crate::error::Result;
|
||||||
|
|
||||||
#[derive(Clone, Default, Serialize, Deserialize)]
|
/// Request body for the `generateContent` and `streamGenerateContent` endpoints.
|
||||||
|
///
|
||||||
|
/// Use [`GenerateContentRequest::builder`] for ergonomic construction.
|
||||||
|
///
|
||||||
|
/// See <https://ai.google.dev/api/generate-content#request-body>.
|
||||||
|
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct GenerateContentRequest {
|
pub struct GenerateContentRequest {
|
||||||
pub contents: Vec<Content>,
|
pub contents: Vec<Content>,
|
||||||
@@ -19,11 +27,14 @@ pub struct GenerateContentRequest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl GenerateContentRequest {
|
impl GenerateContentRequest {
|
||||||
|
/// Returns a new [`GenerateContentRequestBuilder`].
|
||||||
pub fn builder() -> GenerateContentRequestBuilder {
|
pub fn builder() -> GenerateContentRequestBuilder {
|
||||||
GenerateContentRequestBuilder::new()
|
GenerateContentRequestBuilder::new()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Builder for [`GenerateContentRequest`].
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct GenerateContentRequestBuilder {
|
pub struct GenerateContentRequestBuilder {
|
||||||
request: GenerateContentRequest,
|
request: GenerateContentRequest,
|
||||||
}
|
}
|
||||||
@@ -35,37 +46,46 @@ impl GenerateContentRequestBuilder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets the conversation contents.
|
||||||
pub fn contents(mut self, contents: Vec<Content>) -> Self {
|
pub fn contents(mut self, contents: Vec<Content>) -> Self {
|
||||||
self.request.contents = contents;
|
self.request.contents = contents;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets the generation configuration.
|
||||||
pub fn generation_config(mut self, generation_config: GenerationConfig) -> Self {
|
pub fn generation_config(mut self, generation_config: GenerationConfig) -> Self {
|
||||||
self.request.generation_config = Some(generation_config);
|
self.request.generation_config = Some(generation_config);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets the tools available to the model (e.g. function calling, Google Search).
|
||||||
pub fn tools(mut self, tools: Vec<Tools>) -> Self {
|
pub fn tools(mut self, tools: Vec<Tools>) -> Self {
|
||||||
self.request.tools = Some(tools);
|
self.request.tools = Some(tools);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets the safety filter settings.
|
||||||
pub fn safety_settings(mut self, safety_settings: Vec<SafetySetting>) -> Self {
|
pub fn safety_settings(mut self, safety_settings: Vec<SafetySetting>) -> Self {
|
||||||
self.request.safety_settings = Some(safety_settings);
|
self.request.safety_settings = Some(safety_settings);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets a system instruction to guide the model's behavior.
|
||||||
pub fn system_instruction(mut self, system_instruction: Content) -> Self {
|
pub fn system_instruction(mut self, system_instruction: Content) -> Self {
|
||||||
self.request.system_instruction = Some(system_instruction);
|
self.request.system_instruction = Some(system_instruction);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Consumes the builder and returns the constructed [`GenerateContentRequest`].
|
||||||
pub fn build(self) -> GenerateContentRequest {
|
pub fn build(self) -> GenerateContentRequest {
|
||||||
self.request
|
self.request
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Default, Serialize, Deserialize)]
|
/// A set of tool declarations the model may use during generation.
|
||||||
|
///
|
||||||
|
/// See <https://ai.google.dev/api/caching#Tool>.
|
||||||
|
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
|
||||||
pub struct Tools {
|
pub struct Tools {
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub function_declarations: Option<Vec<FunctionDeclaration>>,
|
pub function_declarations: Option<Vec<FunctionDeclaration>>,
|
||||||
@@ -78,13 +98,17 @@ pub struct Tools {
|
|||||||
pub google_search: Option<GoogleSearch>,
|
pub google_search: Option<GoogleSearch>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Default, Serialize, Deserialize)]
|
/// Enables the Google Search grounding tool (no configuration required).
|
||||||
|
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
|
||||||
pub struct GoogleSearch {}
|
pub struct GoogleSearch {}
|
||||||
|
|
||||||
#[derive(Clone, Serialize, Deserialize)]
|
/// Configuration for dynamic retrieval in Google Search grounding.
|
||||||
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct DynamicRetrievalConfig {
|
pub struct DynamicRetrievalConfig {
|
||||||
|
/// The retrieval mode (e.g. `"MODE_DYNAMIC"`).
|
||||||
pub mode: String,
|
pub mode: String,
|
||||||
|
/// The threshold for triggering retrieval. Defaults to `0.7`.
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub dynamic_threshold: Option<f32>,
|
pub dynamic_threshold: Option<f32>,
|
||||||
}
|
}
|
||||||
@@ -98,12 +122,19 @@ impl Default for DynamicRetrievalConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Default, Serialize, Deserialize)]
|
/// Google Search retrieval tool with dynamic retrieval configuration.
|
||||||
|
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct GoogleSearchRetrieval {
|
pub struct GoogleSearchRetrieval {
|
||||||
|
/// Configuration controlling when retrieval is triggered.
|
||||||
pub dynamic_retrieval_config: DynamicRetrievalConfig,
|
pub dynamic_retrieval_config: DynamicRetrievalConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parameters that control how the model generates content.
|
||||||
|
///
|
||||||
|
/// Use [`GenerationConfig::builder`] for ergonomic construction.
|
||||||
|
///
|
||||||
|
/// See <https://ai.google.dev/api/generate-content#generationconfig>.
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize, Default)]
|
#[derive(Clone, Debug, Serialize, Deserialize, Default)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct GenerationConfig {
|
pub struct GenerationConfig {
|
||||||
@@ -128,11 +159,14 @@ pub struct GenerationConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl GenerationConfig {
|
impl GenerationConfig {
|
||||||
|
/// Returns a new [`GenerationConfigBuilder`].
|
||||||
pub fn builder() -> GenerationConfigBuilder {
|
pub fn builder() -> GenerationConfigBuilder {
|
||||||
GenerationConfigBuilder::new()
|
GenerationConfigBuilder::new()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Builder for [`GenerationConfig`].
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct GenerationConfigBuilder {
|
pub struct GenerationConfigBuilder {
|
||||||
generation_config: GenerationConfig,
|
generation_config: GenerationConfig,
|
||||||
}
|
}
|
||||||
@@ -189,11 +223,13 @@ impl GenerationConfigBuilder {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Consumes the builder and returns the constructed [`GenerationConfig`].
|
||||||
pub fn build(self) -> GenerationConfig {
|
pub fn build(self) -> GenerationConfig {
|
||||||
self.generation_config
|
self.generation_config
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Configuration for the model's "thinking" (chain-of-thought) behavior.
|
||||||
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct ThinkingConfig {
|
pub struct ThinkingConfig {
|
||||||
@@ -204,6 +240,7 @@ pub struct ThinkingConfig {
|
|||||||
pub thinking_level: Option<ThinkingLevel>,
|
pub thinking_level: Option<ThinkingLevel>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The level of thinking effort the model should use.
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||||
pub enum ThinkingLevel {
|
pub enum ThinkingLevel {
|
||||||
@@ -212,6 +249,9 @@ pub enum ThinkingLevel {
|
|||||||
High,
|
High,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A safety filter configuration that controls blocking thresholds for harmful content.
|
||||||
|
///
|
||||||
|
/// See <https://ai.google.dev/api/generate-content#safetysetting>.
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct SafetySetting {
|
pub struct SafetySetting {
|
||||||
@@ -221,6 +261,9 @@ pub struct SafetySetting {
|
|||||||
pub method: Option<HarmBlockMethod>,
|
pub method: Option<HarmBlockMethod>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Categories of potentially harmful content.
|
||||||
|
///
|
||||||
|
/// See <https://ai.google.dev/api/generate-content#harmcategory>.
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
pub enum HarmCategory {
|
pub enum HarmCategory {
|
||||||
#[serde(rename = "HARM_CATEGORY_UNSPECIFIED")]
|
#[serde(rename = "HARM_CATEGORY_UNSPECIFIED")]
|
||||||
@@ -235,6 +278,7 @@ pub enum HarmCategory {
|
|||||||
SexuallyExplicit,
|
SexuallyExplicit,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The threshold at which harmful content is blocked.
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
pub enum HarmBlockThreshold {
|
pub enum HarmBlockThreshold {
|
||||||
#[serde(rename = "HARM_BLOCK_THRESHOLD_UNSPECIFIED")]
|
#[serde(rename = "HARM_BLOCK_THRESHOLD_UNSPECIFIED")]
|
||||||
@@ -249,6 +293,7 @@ pub enum HarmBlockThreshold {
|
|||||||
BlockNone,
|
BlockNone,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The method used to evaluate harm (severity-based or probability-based).
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
pub enum HarmBlockMethod {
|
pub enum HarmBlockMethod {
|
||||||
#[serde(rename = "HARM_BLOCK_METHOD_UNSPECIFIED")]
|
#[serde(rename = "HARM_BLOCK_METHOD_UNSPECIFIED")]
|
||||||
@@ -259,7 +304,10 @@ pub enum HarmBlockMethod {
|
|||||||
Probability, // PROBABILITY
|
Probability, // PROBABILITY
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
/// A single candidate response generated by the model.
|
||||||
|
///
|
||||||
|
/// See <https://ai.google.dev/api/generate-content#candidate>.
|
||||||
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct Candidate {
|
pub struct Candidate {
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
@@ -274,6 +322,7 @@ pub struct Candidate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Candidate {
|
impl Candidate {
|
||||||
|
/// Returns the concatenated text from this candidate's content, if any.
|
||||||
pub fn get_text(&self) -> Option<String> {
|
pub fn get_text(&self) -> Option<String> {
|
||||||
match &self.content {
|
match &self.content {
|
||||||
Some(content) => content.get_text(),
|
Some(content) => content.get_text(),
|
||||||
@@ -282,7 +331,8 @@ impl Candidate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
/// A citation to a source used by the model in its response.
|
||||||
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct Citation {
|
pub struct Citation {
|
||||||
pub start_index: Option<i32>,
|
pub start_index: Option<i32>,
|
||||||
@@ -290,13 +340,15 @@ pub struct Citation {
|
|||||||
pub uri: Option<String>,
|
pub uri: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
/// Metadata containing citations for a candidate's content.
|
||||||
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
pub struct CitationMetadata {
|
pub struct CitationMetadata {
|
||||||
#[serde(alias = "citationSources")]
|
#[serde(alias = "citationSources")]
|
||||||
pub citations: Vec<Citation>,
|
pub citations: Vec<Citation>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
/// A safety rating for a piece of content across a specific harm category.
|
||||||
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct SafetyRating {
|
pub struct SafetyRating {
|
||||||
pub category: String,
|
pub category: String,
|
||||||
@@ -306,7 +358,8 @@ pub struct SafetyRating {
|
|||||||
pub severity_score: Option<f32>,
|
pub severity_score: Option<f32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
/// Token usage statistics for a generate content request/response.
|
||||||
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct UsageMetadata {
|
pub struct UsageMetadata {
|
||||||
pub candidates_token_count: Option<u32>,
|
pub candidates_token_count: Option<u32>,
|
||||||
@@ -314,6 +367,9 @@ pub struct UsageMetadata {
|
|||||||
pub total_token_count: Option<u32>,
|
pub total_token_count: Option<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A declaration of a function the model may call.
|
||||||
|
///
|
||||||
|
/// See <https://ai.google.dev/api/caching#FunctionDeclaration>.
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct FunctionDeclaration {
|
pub struct FunctionDeclaration {
|
||||||
@@ -330,7 +386,7 @@ pub struct FunctionDeclaration {
|
|||||||
pub response_json_schema: Option<Value>,
|
pub response_json_schema: Option<Value>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// See https://ai.google.dev/api/caching#FunctionResponse
|
/// See <https://ai.google.dev/api/caching#FunctionResponse>.
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct FunctionResponse {
|
pub struct FunctionResponse {
|
||||||
@@ -343,14 +399,14 @@ pub struct FunctionResponse {
|
|||||||
pub scheduling: Option<Scheduling>,
|
pub scheduling: Option<Scheduling>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// See https://ai.google.dev/api/caching#FunctionResponsePart
|
/// See <https://ai.google.dev/api/caching#FunctionResponsePart>.
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub enum FunctionResponsePart {
|
pub enum FunctionResponsePart {
|
||||||
InlineData(FunctionResponseBlob),
|
InlineData(FunctionResponseBlob),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// See https://ai.google.dev/api/caching#FunctionResponseBlob
|
/// See <https://ai.google.dev/api/caching#FunctionResponseBlob>.
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct FunctionResponseBlob {
|
pub struct FunctionResponseBlob {
|
||||||
@@ -358,7 +414,7 @@ pub struct FunctionResponseBlob {
|
|||||||
pub data: String,
|
pub data: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// See https://ai.google.dev/api/caching#Scheduling
|
/// See <https://ai.google.dev/api/caching#Scheduling>.
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||||
pub enum Scheduling {
|
pub enum Scheduling {
|
||||||
@@ -368,6 +424,7 @@ pub enum Scheduling {
|
|||||||
Interrupt,
|
Interrupt,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A single property within a function's parameter schema.
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct FunctionParametersProperty {
|
pub struct FunctionParametersProperty {
|
||||||
@@ -375,7 +432,11 @@ pub struct FunctionParametersProperty {
|
|||||||
pub description: String,
|
pub description: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
/// The raw response from the `generateContent` endpoint, which may be a success or an error.
|
||||||
|
///
|
||||||
|
/// Use [`into_result`](GenerateContentResponse::into_result) to convert into a standard
|
||||||
|
/// `Result<GenerateContentResponseResult>`.
|
||||||
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
#[serde(untagged)]
|
#[serde(untagged)]
|
||||||
pub enum GenerateContentResponse {
|
pub enum GenerateContentResponse {
|
||||||
Ok(GenerateContentResponseResult),
|
Ok(GenerateContentResponseResult),
|
||||||
@@ -391,19 +452,22 @@ impl From<GenerateContentResponse> for Result<GenerateContentResponseResult> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
/// A successful response from the `generateContent` endpoint.
|
||||||
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct GenerateContentResponseResult {
|
pub struct GenerateContentResponseResult {
|
||||||
pub candidates: Vec<Candidate>,
|
pub candidates: Vec<Candidate>,
|
||||||
pub usage_metadata: Option<UsageMetadata>,
|
pub usage_metadata: Option<UsageMetadata>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
/// An error response from the `generateContent` endpoint.
|
||||||
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
pub struct GenerateContentResponseError {
|
pub struct GenerateContentResponseError {
|
||||||
pub error: VertexApiError,
|
pub error: VertexApiError,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GenerateContentResponse {
|
impl GenerateContentResponse {
|
||||||
|
/// Converts this response into a `Result`, mapping the error variant to [`crate::error::Error`].
|
||||||
pub fn into_result(self) -> Result<GenerateContentResponseResult> {
|
pub fn into_result(self) -> Result<GenerateContentResponseResult> {
|
||||||
match self {
|
match self {
|
||||||
GenerateContentResponse::Ok(result) => Ok(result),
|
GenerateContentResponse::Ok(result) => Ok(result),
|
||||||
@@ -416,13 +480,7 @@ impl GenerateContentResponse {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use crate::types::{Candidate, UsageMetadata};
|
use crate::types::{Candidate, UsageMetadata};
|
||||||
|
|
||||||
use super::{GenerateContentResponse, GenerateContentResponseResult};
|
use super::GenerateContentResponseResult;
|
||||||
|
|
||||||
#[test]
|
|
||||||
pub fn parses_empty_metadata_response() {
|
|
||||||
let input = r#"{"candidates": [{"content": {"role": "model","parts": [{"text": "-"}]}}],"usageMetadata": {}}"#;
|
|
||||||
serde_json::from_str::<GenerateContentResponseResult>(input).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
pub fn parses_usage_metadata() {
|
pub fn parses_usage_metadata() {
|
||||||
@@ -498,177 +556,4 @@ mod tests {
|
|||||||
"#;
|
"#;
|
||||||
let _ = serde_json::from_str::<GenerateContentResponseResult>(input).unwrap();
|
let _ = serde_json::from_str::<GenerateContentResponseResult>(input).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
pub fn parses_max_tokens_response() {
|
|
||||||
let input = r#"{
|
|
||||||
"candidates": [
|
|
||||||
{
|
|
||||||
"content": {
|
|
||||||
"role": "model",
|
|
||||||
"parts": [
|
|
||||||
{
|
|
||||||
"text": "Service workers are powerful and absolutely worth learning. They let you deliver an entirely new level of experience to your users. Your site can load instantly . It can work offline . It can be installed as a platform-specific app and feel every bit as polished—but with the reach and freedom of the web."
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"finishReason": "MAX_TOKENS",
|
|
||||||
"safetyRatings": [
|
|
||||||
{
|
|
||||||
"category": "HARM_CATEGORY_HATE_SPEECH",
|
|
||||||
"probability": "NEGLIGIBLE",
|
|
||||||
"probabilityScore": 0.03882902,
|
|
||||||
"severity": "HARM_SEVERITY_NEGLIGIBLE",
|
|
||||||
"severityScore": 0.05781161
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"category": "HARM_CATEGORY_DANGEROUS_CONTENT",
|
|
||||||
"probability": "NEGLIGIBLE",
|
|
||||||
"probabilityScore": 0.07626997,
|
|
||||||
"severity": "HARM_SEVERITY_NEGLIGIBLE",
|
|
||||||
"severityScore": 0.06705628
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"category": "HARM_CATEGORY_HARASSMENT",
|
|
||||||
"probability": "NEGLIGIBLE",
|
|
||||||
"probabilityScore": 0.05749328,
|
|
||||||
"severity": "HARM_SEVERITY_NEGLIGIBLE",
|
|
||||||
"severityScore": 0.027532939
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT",
|
|
||||||
"probability": "NEGLIGIBLE",
|
|
||||||
"probabilityScore": 0.12929276,
|
|
||||||
"severity": "HARM_SEVERITY_NEGLIGIBLE",
|
|
||||||
"severityScore": 0.17838266
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"citationMetadata": {
|
|
||||||
"citations": [
|
|
||||||
{
|
|
||||||
"endIndex": 151,
|
|
||||||
"uri": "https://web.dev/service-worker-mindset/"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startIndex": 93,
|
|
||||||
"endIndex": 297,
|
|
||||||
"uri": "https://web.dev/service-worker-mindset/"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"endIndex": 297
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"usageMetadata": {
|
|
||||||
"promptTokenCount": 12069,
|
|
||||||
"candidatesTokenCount": 61,
|
|
||||||
"totalTokenCount": 12130
|
|
||||||
}
|
|
||||||
}"#;
|
|
||||||
serde_json::from_str::<GenerateContentResponseResult>(input).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn parses_candidates_without_content() {
|
|
||||||
let input = r#"{
|
|
||||||
"candidates": [
|
|
||||||
{
|
|
||||||
"finishReason": "RECITATION",
|
|
||||||
"safetyRatings": [
|
|
||||||
{
|
|
||||||
"category": "HARM_CATEGORY_HATE_SPEECH",
|
|
||||||
"probability": "NEGLIGIBLE",
|
|
||||||
"probabilityScore": 0.08021325,
|
|
||||||
"severity": "HARM_SEVERITY_NEGLIGIBLE",
|
|
||||||
"severityScore": 0.0721122
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"category": "HARM_CATEGORY_DANGEROUS_CONTENT",
|
|
||||||
"probability": "NEGLIGIBLE",
|
|
||||||
"probabilityScore": 0.19360436,
|
|
||||||
"severity": "HARM_SEVERITY_NEGLIGIBLE",
|
|
||||||
"severityScore": 0.1066906
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"category": "HARM_CATEGORY_HARASSMENT",
|
|
||||||
"probability": "NEGLIGIBLE",
|
|
||||||
"probabilityScore": 0.07751766,
|
|
||||||
"severity": "HARM_SEVERITY_NEGLIGIBLE",
|
|
||||||
"severityScore": 0.040769264
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT",
|
|
||||||
"probability": "NEGLIGIBLE",
|
|
||||||
"probabilityScore": 0.030792166,
|
|
||||||
"severity": "HARM_SEVERITY_NEGLIGIBLE",
|
|
||||||
"severityScore": 0.04138472
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"citationMetadata": {
|
|
||||||
"citations": [
|
|
||||||
{
|
|
||||||
"startIndex": 1108,
|
|
||||||
"endIndex": 1250,
|
|
||||||
"uri": "https://chrome.google.com/webstore/detail/autocontrol-shortcut-mana/lkaihdpfpifdlgoapbfocpmekbokmcfd?hl=zh-TW"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"usageMetadata": {
|
|
||||||
"promptTokenCount": 577,
|
|
||||||
"totalTokenCount": 577
|
|
||||||
}
|
|
||||||
}"#;
|
|
||||||
serde_json::from_str::<GenerateContentResponse>(input).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn parses_safety_rating_without_scores() {
|
|
||||||
let input = r#"{
|
|
||||||
"candidates": [
|
|
||||||
{
|
|
||||||
"content": {
|
|
||||||
"role": "model",
|
|
||||||
"parts": [
|
|
||||||
{
|
|
||||||
"text": "Return text"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"finishReason": "STOP",
|
|
||||||
"safetyRatings": [
|
|
||||||
{
|
|
||||||
"category": "HARM_CATEGORY_HATE_SPEECH",
|
|
||||||
"probability": "NEGLIGIBLE",
|
|
||||||
"severity": "HARM_SEVERITY_NEGLIGIBLE"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"category": "HARM_CATEGORY_DANGEROUS_CONTENT",
|
|
||||||
"probability": "NEGLIGIBLE",
|
|
||||||
"severity": "HARM_SEVERITY_NEGLIGIBLE"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"category": "HARM_CATEGORY_HARASSMENT",
|
|
||||||
"probability": "NEGLIGIBLE",
|
|
||||||
"severity": "HARM_SEVERITY_NEGLIGIBLE"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT",
|
|
||||||
"probability": "NEGLIGIBLE",
|
|
||||||
"severity": "HARM_SEVERITY_NEGLIGIBLE"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"usageMetadata": {
|
|
||||||
"promptTokenCount": 5492,
|
|
||||||
"candidatesTokenCount": 1256,
|
|
||||||
"totalTokenCount": 6748
|
|
||||||
}
|
|
||||||
}"#;
|
|
||||||
serde_json::from_str::<GenerateContentResponse>(input).unwrap();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
|
// Copyright 2026 Andre Cipriani Bandarra
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
//! Request and response types for the Gemini API.
|
||||||
|
|
||||||
mod common;
|
mod common;
|
||||||
mod count_tokens;
|
mod count_tokens;
|
||||||
mod error;
|
mod error;
|
||||||
|
|||||||
@@ -1,14 +1,19 @@
|
|||||||
|
// Copyright 2026 Andre Cipriani Bandarra
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_with::base64::Base64;
|
use serde_with::base64::Base64;
|
||||||
use serde_with::serde_as;
|
use serde_with::serde_as;
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
/// Request body for the Imagen image generation `predict` endpoint.
|
||||||
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
pub struct PredictImageRequest {
|
pub struct PredictImageRequest {
|
||||||
pub instances: Vec<PredictImageRequestPrompt>,
|
pub instances: Vec<PredictImageRequestPrompt>,
|
||||||
pub parameters: PredictImageRequestParameters,
|
pub parameters: PredictImageRequestParameters,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
/// A text prompt instance for image generation.
|
||||||
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
pub struct PredictImageRequestPrompt {
|
pub struct PredictImageRequestPrompt {
|
||||||
/// The text prompt for the image.
|
/// The text prompt for the image.
|
||||||
/// The following models support different values for this parameter:
|
/// The following models support different values for this parameter:
|
||||||
@@ -20,7 +25,8 @@ pub struct PredictImageRequestPrompt {
|
|||||||
pub prompt: String,
|
pub prompt: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default, Serialize, Deserialize)]
|
/// Parameters controlling image generation behavior.
|
||||||
|
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct PredictImageRequestParameters {
|
pub struct PredictImageRequestParameters {
|
||||||
/// The number of images to generate. The default value is 4.
|
/// The number of images to generate. The default value is 4.
|
||||||
@@ -139,7 +145,8 @@ pub struct PredictImageRequestParameters {
|
|||||||
pub storage_uri: Option<String>,
|
pub storage_uri: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
/// Output format options for generated images.
|
||||||
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct PredictImageRequestParametersOutputOptions {
|
pub struct PredictImageRequestParametersOutputOptions {
|
||||||
/// The image format that the output should be saved as. The following values are supported:
|
/// The image format that the output should be saved as. The following values are supported:
|
||||||
@@ -155,13 +162,15 @@ pub struct PredictImageRequestParametersOutputOptions {
|
|||||||
pub compression_quality: Option<i32>,
|
pub compression_quality: Option<i32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
/// A successful response from the Imagen `predict` endpoint.
|
||||||
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
pub struct PredictImageResponse {
|
pub struct PredictImageResponse {
|
||||||
pub predictions: Vec<PredictImageResponsePrediction>,
|
pub predictions: Vec<PredictImageResponsePrediction>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A single generated image from the prediction response.
|
||||||
#[serde_as]
|
#[serde_as]
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct PredictImageResponsePrediction {
|
pub struct PredictImageResponsePrediction {
|
||||||
#[serde_as(as = "Base64")]
|
#[serde_as(as = "Base64")]
|
||||||
@@ -169,7 +178,8 @@ pub struct PredictImageResponsePrediction {
|
|||||||
pub mime_type: String,
|
pub mime_type: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
/// Controls whether generated images may include people.
|
||||||
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
pub enum PersonGeneration {
|
pub enum PersonGeneration {
|
||||||
DontAllow,
|
DontAllow,
|
||||||
@@ -177,7 +187,8 @@ pub enum PersonGeneration {
|
|||||||
AllowAll,
|
AllowAll,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
/// Safety filter level for image generation.
|
||||||
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
pub enum PredictImageSafetySetting {
|
pub enum PredictImageSafetySetting {
|
||||||
BlockLowAndAbove,
|
BlockLowAndAbove,
|
||||||
|
|||||||
@@ -1,20 +1,32 @@
|
|||||||
|
// Copyright 2026 Andre Cipriani Bandarra
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::error::{Error, Result};
|
use crate::error::{Error, Result};
|
||||||
use crate::prelude::VertexApiError;
|
use crate::prelude::VertexApiError;
|
||||||
|
|
||||||
|
/// Request body for the text embeddings `predict` endpoint.
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
pub struct TextEmbeddingRequest {
|
pub struct TextEmbeddingRequest {
|
||||||
|
/// The list of text instances to embed.
|
||||||
pub instances: Vec<TextEmbeddingRequestInstance>,
|
pub instances: Vec<TextEmbeddingRequestInstance>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A single text instance to embed.
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
pub struct TextEmbeddingRequestInstance {
|
pub struct TextEmbeddingRequestInstance {
|
||||||
|
/// The text content to generate an embedding for.
|
||||||
pub content: String,
|
pub content: String,
|
||||||
|
/// The task type for the embedding (e.g. `"RETRIEVAL_DOCUMENT"`, `"RETRIEVAL_QUERY"`).
|
||||||
pub task_type: String,
|
pub task_type: String,
|
||||||
|
/// An optional title for the content (used with retrieval task types).
|
||||||
pub title: Option<String>,
|
pub title: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The raw response from the text embeddings endpoint, which may be a success or an error.
|
||||||
|
///
|
||||||
|
/// Use [`into_result`](TextEmbeddingResponse::into_result) to convert into a standard `Result`.
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
#[serde(untagged)]
|
#[serde(untagged)]
|
||||||
pub enum TextEmbeddingResponse {
|
pub enum TextEmbeddingResponse {
|
||||||
@@ -23,13 +35,16 @@ pub enum TextEmbeddingResponse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl TextEmbeddingResponse {
|
impl TextEmbeddingResponse {
|
||||||
|
/// Converts this response into a `Result`, mapping the error variant to [`crate::error::Error`].
|
||||||
pub fn into_result(self) -> Result<TextEmbeddingResponseOk> {
|
pub fn into_result(self) -> Result<TextEmbeddingResponseOk> {
|
||||||
self.into()
|
self.into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A successful response from the text embeddings endpoint.
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
pub struct TextEmbeddingResponseOk {
|
pub struct TextEmbeddingResponseOk {
|
||||||
|
/// The embedding predictions, one per input instance.
|
||||||
pub predictions: Vec<TextEmbeddingPrediction>,
|
pub predictions: Vec<TextEmbeddingPrediction>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,19 +57,27 @@ impl From<TextEmbeddingResponse> for Result<TextEmbeddingResponseOk> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A single embedding prediction.
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
pub struct TextEmbeddingPrediction {
|
pub struct TextEmbeddingPrediction {
|
||||||
|
/// The embedding result containing the vector and statistics.
|
||||||
pub embeddings: TextEmbeddingResult,
|
pub embeddings: TextEmbeddingResult,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The embedding vector and associated statistics.
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
pub struct TextEmbeddingResult {
|
pub struct TextEmbeddingResult {
|
||||||
|
/// Statistics about the embedding computation.
|
||||||
pub statistics: TextEmbeddingStatistics,
|
pub statistics: TextEmbeddingStatistics,
|
||||||
|
/// The embedding vector.
|
||||||
pub values: Vec<f64>,
|
pub values: Vec<f64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Statistics about a text embedding computation.
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
pub struct TextEmbeddingStatistics {
|
pub struct TextEmbeddingStatistics {
|
||||||
|
/// Whether the input was truncated to fit the model's context window.
|
||||||
pub truncated: bool,
|
pub truncated: bool,
|
||||||
|
/// The number of tokens in the input.
|
||||||
pub token_count: u32,
|
pub token_count: u32,
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user