feat: add FIM, files, fine-tuning, batch, OCR, audio, moderation, and agent endpoints

New API endpoints with sync + async support:
- FIM (fill-in-the-middle) completions for code completion
- Files API: upload, list, get, delete, get URL
- Fine-tuning: create, list, get, cancel, start jobs
- Batch jobs: create, list, get, cancel
- OCR: document text extraction with page/table options
- Audio transcription: speech-to-text with diarization
- Moderations and classifications: content safety
- Agent completions: run pre-configured agents
This commit is contained in:
2026-03-20 17:56:28 +00:00
parent bbb6aaed1c
commit 4d6eca62ef
10 changed files with 1278 additions and 1 deletions

98
src/v1/agents.rs Normal file
View File

@@ -0,0 +1,98 @@
use serde::{Deserialize, Serialize};
use crate::v1::{chat, common, constants, tool};
// -----------------------------------------------------------------------------
// Request
#[derive(Debug)]
pub struct AgentCompletionParams {
pub max_tokens: Option<u32>,
pub min_tokens: Option<u32>,
pub temperature: Option<f32>,
pub top_p: Option<f32>,
pub random_seed: Option<u32>,
pub stop: Option<Vec<String>>,
pub response_format: Option<chat::ResponseFormat>,
pub tools: Option<Vec<tool::Tool>>,
pub tool_choice: Option<tool::ToolChoice>,
}
impl Default for AgentCompletionParams {
fn default() -> Self {
Self {
max_tokens: None,
min_tokens: None,
temperature: None,
top_p: None,
random_seed: None,
stop: None,
response_format: None,
tools: None,
tool_choice: None,
}
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct AgentCompletionRequest {
pub agent_id: String,
pub messages: Vec<chat::ChatMessage>,
pub stream: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_tokens: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub min_tokens: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub temperature: Option<f32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub top_p: Option<f32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub random_seed: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub stop: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub response_format: Option<chat::ResponseFormat>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tools: Option<Vec<tool::Tool>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tool_choice: Option<tool::ToolChoice>,
}
impl AgentCompletionRequest {
pub fn new(
agent_id: String,
messages: Vec<chat::ChatMessage>,
stream: bool,
options: Option<AgentCompletionParams>,
) -> Self {
let opts = options.unwrap_or_default();
Self {
agent_id,
messages,
stream,
max_tokens: opts.max_tokens,
min_tokens: opts.min_tokens,
temperature: opts.temperature,
top_p: opts.top_p,
random_seed: opts.random_seed,
stop: opts.stop,
response_format: opts.response_format,
tools: opts.tools,
tool_choice: opts.tool_choice,
}
}
}
// -----------------------------------------------------------------------------
// Response (same shape as chat completions)
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct AgentCompletionResponse {
pub id: String,
pub object: String,
pub created: u64,
pub model: constants::Model,
pub choices: Vec<chat::ChatResponseChoice>,
pub usage: common::ResponseUsage,
}

78
src/v1/audio.rs Normal file
View File

@@ -0,0 +1,78 @@
use serde::{Deserialize, Serialize};
use crate::v1::constants;
// -----------------------------------------------------------------------------
// Request (multipart form, but we define the params struct)
#[derive(Debug)]
pub struct AudioTranscriptionParams {
pub model: constants::Model,
pub language: Option<String>,
pub temperature: Option<f32>,
pub diarize: Option<bool>,
pub timestamp_granularities: Option<Vec<TimestampGranularity>>,
}
impl Default for AudioTranscriptionParams {
fn default() -> Self {
Self {
model: constants::Model::voxtral_mini_transcribe(),
language: None,
temperature: None,
diarize: None,
timestamp_granularities: None,
}
}
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum TimestampGranularity {
Segment,
Word,
}
// -----------------------------------------------------------------------------
// Response
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct AudioTranscriptionResponse {
pub text: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub model: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub language: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub segments: Option<Vec<TranscriptionSegment>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub words: Option<Vec<TranscriptionWord>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub usage: Option<AudioUsage>,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct TranscriptionSegment {
pub id: u32,
pub start: f32,
pub end: f32,
pub text: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub speaker: Option<String>,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct TranscriptionWord {
pub word: String,
pub start: f32,
pub end: f32,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct AudioUsage {
#[serde(skip_serializing_if = "Option::is_none")]
pub prompt_audio_seconds: Option<f32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub prompt_tokens: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub total_tokens: Option<u32>,
}

53
src/v1/batch.rs Normal file
View File

@@ -0,0 +1,53 @@
use serde::{Deserialize, Serialize};
use crate::v1::constants;
// -----------------------------------------------------------------------------
// Request
#[derive(Debug, Serialize, Deserialize)]
pub struct BatchJobRequest {
pub input_files: Vec<String>,
pub model: constants::Model,
pub endpoint: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub metadata: Option<serde_json::Value>,
}
// -----------------------------------------------------------------------------
// Response
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct BatchJobResponse {
pub id: String,
pub object: String,
pub model: constants::Model,
pub endpoint: String,
pub input_files: Vec<String>,
pub status: String,
pub created_at: u64,
#[serde(skip_serializing_if = "Option::is_none")]
pub completed_at: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub output_file: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub error_file: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub metadata: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub total_requests: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub completed_requests: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub succeeded_requests: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub failed_requests: Option<u64>,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct BatchJobListResponse {
pub data: Vec<BatchJobResponse>,
pub object: String,
#[serde(default)]
pub total: u32,
}

View File

@@ -5,10 +5,14 @@ use reqwest::Error as ReqwestError;
use std::{
any::Any,
collections::HashMap,
path::Path,
sync::{Arc, Mutex},
};
use crate::v1::{chat, chat_stream, constants, embedding, error, model_list, tool, utils};
use crate::v1::{
agents, audio, batch, chat, chat_stream, constants, embedding, error, files, fim, fine_tuning,
model_list, moderation, ocr, tool, utils,
};
#[derive(Debug)]
pub struct Client {
@@ -200,6 +204,48 @@ impl Client {
}
}
// =========================================================================
// FIM (Fill-in-the-Middle)
// =========================================================================
pub fn fim(
&self,
model: constants::Model,
prompt: String,
options: Option<fim::FimParams>,
) -> Result<fim::FimResponse, error::ApiError> {
let request = fim::FimRequest::new(model, prompt, false, options);
let response = self.post_sync("/fim/completions", &request)?;
let result = response.json::<fim::FimResponse>();
match result {
Ok(data) => {
utils::debug_pretty_json_from_struct("Response Data", &data);
Ok(data)
}
Err(error) => Err(self.to_api_error(error)),
}
}
pub async fn fim_async(
&self,
model: constants::Model,
prompt: String,
options: Option<fim::FimParams>,
) -> Result<fim::FimResponse, error::ApiError> {
let request = fim::FimRequest::new(model, prompt, false, options);
let response = self.post_async("/fim/completions", &request).await?;
let result = response.json::<fim::FimResponse>().await;
match result {
Ok(data) => {
utils::debug_pretty_json_from_struct("Response Data", &data);
Ok(data)
}
Err(error) => Err(self.to_api_error(error)),
}
}
// =========================================================================
// Models
// =========================================================================
@@ -283,6 +329,577 @@ impl Client {
}
}
// =========================================================================
// Files
// =========================================================================
pub fn list_files(&self) -> Result<files::FileListResponse, error::ApiError> {
let response = self.get_sync("/files")?;
let result = response.json::<files::FileListResponse>();
match result {
Ok(data) => Ok(data),
Err(error) => Err(self.to_api_error(error)),
}
}
pub async fn list_files_async(&self) -> Result<files::FileListResponse, error::ApiError> {
let response = self.get_async("/files").await?;
let result = response.json::<files::FileListResponse>().await;
match result {
Ok(data) => Ok(data),
Err(error) => Err(self.to_api_error(error)),
}
}
pub fn upload_file(
&self,
file_path: &Path,
purpose: files::FilePurpose,
) -> Result<files::FileObject, error::ApiError> {
let reqwest_client = reqwest::blocking::Client::new();
let url = format!("{}/files", self.endpoint);
let purpose_str = serde_json::to_value(&purpose)
.unwrap()
.as_str()
.unwrap()
.to_string();
let file_bytes = std::fs::read(file_path).map_err(|e| error::ApiError {
message: format!("Failed to read file: {}", e),
})?;
let file_name = file_path
.file_name()
.unwrap_or_default()
.to_string_lossy()
.to_string();
let part = reqwest::blocking::multipart::Part::bytes(file_bytes).file_name(file_name);
let form = reqwest::blocking::multipart::Form::new()
.text("purpose", purpose_str)
.part("file", part);
let request = self.build_request_sync_no_accept(reqwest_client.post(url).multipart(form));
let result = request.send();
match result {
Ok(response) => {
if response.status().is_success() {
response
.json::<files::FileObject>()
.map_err(|e| self.to_api_error(e))
} else {
let status = response.status();
let body = response.text().unwrap_or_default();
Err(error::ApiError {
message: format!("{}: {}", status, body),
})
}
}
Err(error) => Err(error::ApiError {
message: error.to_string(),
}),
}
}
pub async fn upload_file_async(
&self,
file_path: &Path,
purpose: files::FilePurpose,
) -> Result<files::FileObject, error::ApiError> {
let reqwest_client = reqwest::Client::new();
let url = format!("{}/files", self.endpoint);
let purpose_str = serde_json::to_value(&purpose)
.unwrap()
.as_str()
.unwrap()
.to_string();
let file_bytes = tokio::fs::read(file_path).await.map_err(|e| error::ApiError {
message: format!("Failed to read file: {}", e),
})?;
let file_name = file_path
.file_name()
.unwrap_or_default()
.to_string_lossy()
.to_string();
let part = reqwest::multipart::Part::bytes(file_bytes).file_name(file_name);
let form = reqwest::multipart::Form::new()
.text("purpose", purpose_str)
.part("file", part);
let request = self.build_request_async_no_accept(reqwest_client.post(url).multipart(form));
let result = request.send().await;
match result {
Ok(response) => {
if response.status().is_success() {
response
.json::<files::FileObject>()
.await
.map_err(|e| self.to_api_error(e))
} else {
let status = response.status();
let body = response.text().await.unwrap_or_default();
Err(error::ApiError {
message: format!("{}: {}", status, body),
})
}
}
Err(error) => Err(error::ApiError {
message: error.to_string(),
}),
}
}
pub fn get_file(&self, file_id: &str) -> Result<files::FileObject, error::ApiError> {
let response = self.get_sync(&format!("/files/{}", file_id))?;
response
.json::<files::FileObject>()
.map_err(|e| self.to_api_error(e))
}
pub async fn get_file_async(
&self,
file_id: &str,
) -> Result<files::FileObject, error::ApiError> {
let response = self.get_async(&format!("/files/{}", file_id)).await?;
response
.json::<files::FileObject>()
.await
.map_err(|e| self.to_api_error(e))
}
pub fn delete_file(&self, file_id: &str) -> Result<files::FileDeleteResponse, error::ApiError> {
let response = self.delete_sync(&format!("/files/{}", file_id))?;
response
.json::<files::FileDeleteResponse>()
.map_err(|e| self.to_api_error(e))
}
pub async fn delete_file_async(
&self,
file_id: &str,
) -> Result<files::FileDeleteResponse, error::ApiError> {
let response = self.delete_async(&format!("/files/{}", file_id)).await?;
response
.json::<files::FileDeleteResponse>()
.await
.map_err(|e| self.to_api_error(e))
}
pub fn get_file_url(&self, file_id: &str) -> Result<files::FileUrlResponse, error::ApiError> {
let response = self.get_sync(&format!("/files/{}/url", file_id))?;
response
.json::<files::FileUrlResponse>()
.map_err(|e| self.to_api_error(e))
}
pub async fn get_file_url_async(
&self,
file_id: &str,
) -> Result<files::FileUrlResponse, error::ApiError> {
let response = self.get_async(&format!("/files/{}/url", file_id)).await?;
response
.json::<files::FileUrlResponse>()
.await
.map_err(|e| self.to_api_error(e))
}
// =========================================================================
// Fine-Tuning
// =========================================================================
pub fn create_fine_tuning_job(
&self,
request: &fine_tuning::FineTuningJobRequest,
) -> Result<fine_tuning::FineTuningJobResponse, error::ApiError> {
let response = self.post_sync("/fine_tuning/jobs", request)?;
response
.json::<fine_tuning::FineTuningJobResponse>()
.map_err(|e| self.to_api_error(e))
}
pub async fn create_fine_tuning_job_async(
&self,
request: &fine_tuning::FineTuningJobRequest,
) -> Result<fine_tuning::FineTuningJobResponse, error::ApiError> {
let response = self.post_async("/fine_tuning/jobs", request).await?;
response
.json::<fine_tuning::FineTuningJobResponse>()
.await
.map_err(|e| self.to_api_error(e))
}
pub fn list_fine_tuning_jobs(
&self,
) -> Result<fine_tuning::FineTuningJobListResponse, error::ApiError> {
let response = self.get_sync("/fine_tuning/jobs")?;
response
.json::<fine_tuning::FineTuningJobListResponse>()
.map_err(|e| self.to_api_error(e))
}
pub async fn list_fine_tuning_jobs_async(
&self,
) -> Result<fine_tuning::FineTuningJobListResponse, error::ApiError> {
let response = self.get_async("/fine_tuning/jobs").await?;
response
.json::<fine_tuning::FineTuningJobListResponse>()
.await
.map_err(|e| self.to_api_error(e))
}
pub fn get_fine_tuning_job(
&self,
job_id: &str,
) -> Result<fine_tuning::FineTuningJobResponse, error::ApiError> {
let response = self.get_sync(&format!("/fine_tuning/jobs/{}", job_id))?;
response
.json::<fine_tuning::FineTuningJobResponse>()
.map_err(|e| self.to_api_error(e))
}
pub async fn get_fine_tuning_job_async(
&self,
job_id: &str,
) -> Result<fine_tuning::FineTuningJobResponse, error::ApiError> {
let response = self
.get_async(&format!("/fine_tuning/jobs/{}", job_id))
.await?;
response
.json::<fine_tuning::FineTuningJobResponse>()
.await
.map_err(|e| self.to_api_error(e))
}
pub fn cancel_fine_tuning_job(
&self,
job_id: &str,
) -> Result<fine_tuning::FineTuningJobResponse, error::ApiError> {
let response = self.post_sync_empty(&format!("/fine_tuning/jobs/{}/cancel", job_id))?;
response
.json::<fine_tuning::FineTuningJobResponse>()
.map_err(|e| self.to_api_error(e))
}
pub async fn cancel_fine_tuning_job_async(
&self,
job_id: &str,
) -> Result<fine_tuning::FineTuningJobResponse, error::ApiError> {
let response = self
.post_async_empty(&format!("/fine_tuning/jobs/{}/cancel", job_id))
.await?;
response
.json::<fine_tuning::FineTuningJobResponse>()
.await
.map_err(|e| self.to_api_error(e))
}
pub fn start_fine_tuning_job(
&self,
job_id: &str,
) -> Result<fine_tuning::FineTuningJobResponse, error::ApiError> {
let response = self.post_sync_empty(&format!("/fine_tuning/jobs/{}/start", job_id))?;
response
.json::<fine_tuning::FineTuningJobResponse>()
.map_err(|e| self.to_api_error(e))
}
pub async fn start_fine_tuning_job_async(
&self,
job_id: &str,
) -> Result<fine_tuning::FineTuningJobResponse, error::ApiError> {
let response = self
.post_async_empty(&format!("/fine_tuning/jobs/{}/start", job_id))
.await?;
response
.json::<fine_tuning::FineTuningJobResponse>()
.await
.map_err(|e| self.to_api_error(e))
}
// =========================================================================
// Batch Jobs
// =========================================================================
pub fn create_batch_job(
&self,
request: &batch::BatchJobRequest,
) -> Result<batch::BatchJobResponse, error::ApiError> {
let response = self.post_sync("/batch/jobs", request)?;
response
.json::<batch::BatchJobResponse>()
.map_err(|e| self.to_api_error(e))
}
pub async fn create_batch_job_async(
&self,
request: &batch::BatchJobRequest,
) -> Result<batch::BatchJobResponse, error::ApiError> {
let response = self.post_async("/batch/jobs", request).await?;
response
.json::<batch::BatchJobResponse>()
.await
.map_err(|e| self.to_api_error(e))
}
pub fn list_batch_jobs(&self) -> Result<batch::BatchJobListResponse, error::ApiError> {
let response = self.get_sync("/batch/jobs")?;
response
.json::<batch::BatchJobListResponse>()
.map_err(|e| self.to_api_error(e))
}
pub async fn list_batch_jobs_async(
&self,
) -> Result<batch::BatchJobListResponse, error::ApiError> {
let response = self.get_async("/batch/jobs").await?;
response
.json::<batch::BatchJobListResponse>()
.await
.map_err(|e| self.to_api_error(e))
}
pub fn get_batch_job(
&self,
job_id: &str,
) -> Result<batch::BatchJobResponse, error::ApiError> {
let response = self.get_sync(&format!("/batch/jobs/{}", job_id))?;
response
.json::<batch::BatchJobResponse>()
.map_err(|e| self.to_api_error(e))
}
pub async fn get_batch_job_async(
&self,
job_id: &str,
) -> Result<batch::BatchJobResponse, error::ApiError> {
let response = self
.get_async(&format!("/batch/jobs/{}", job_id))
.await?;
response
.json::<batch::BatchJobResponse>()
.await
.map_err(|e| self.to_api_error(e))
}
pub fn cancel_batch_job(
&self,
job_id: &str,
) -> Result<batch::BatchJobResponse, error::ApiError> {
let response = self.post_sync_empty(&format!("/batch/jobs/{}/cancel", job_id))?;
response
.json::<batch::BatchJobResponse>()
.map_err(|e| self.to_api_error(e))
}
pub async fn cancel_batch_job_async(
&self,
job_id: &str,
) -> Result<batch::BatchJobResponse, error::ApiError> {
let response = self
.post_async_empty(&format!("/batch/jobs/{}/cancel", job_id))
.await?;
response
.json::<batch::BatchJobResponse>()
.await
.map_err(|e| self.to_api_error(e))
}
// =========================================================================
// OCR
// =========================================================================
pub fn ocr(
&self,
request: &ocr::OcrRequest,
) -> Result<ocr::OcrResponse, error::ApiError> {
let response = self.post_sync("/ocr", request)?;
response
.json::<ocr::OcrResponse>()
.map_err(|e| self.to_api_error(e))
}
pub async fn ocr_async(
&self,
request: &ocr::OcrRequest,
) -> Result<ocr::OcrResponse, error::ApiError> {
let response = self.post_async("/ocr", request).await?;
response
.json::<ocr::OcrResponse>()
.await
.map_err(|e| self.to_api_error(e))
}
// =========================================================================
// Audio Transcription
// =========================================================================
pub async fn transcribe_audio_async(
&self,
file_path: &Path,
params: Option<audio::AudioTranscriptionParams>,
) -> Result<audio::AudioTranscriptionResponse, error::ApiError> {
let opts = params.unwrap_or_default();
let reqwest_client = reqwest::Client::new();
let url = format!("{}/audio/transcriptions", self.endpoint);
let file_bytes = tokio::fs::read(file_path).await.map_err(|e| error::ApiError {
message: format!("Failed to read file: {}", e),
})?;
let file_name = file_path
.file_name()
.unwrap_or_default()
.to_string_lossy()
.to_string();
let part = reqwest::multipart::Part::bytes(file_bytes).file_name(file_name);
let mut form = reqwest::multipart::Form::new()
.text("model", opts.model.0)
.part("file", part);
if let Some(lang) = opts.language {
form = form.text("language", lang);
}
if let Some(temp) = opts.temperature {
form = form.text("temperature", temp.to_string());
}
if let Some(diarize) = opts.diarize {
form = form.text("diarize", diarize.to_string());
}
let request = self.build_request_async_no_accept(reqwest_client.post(url).multipart(form));
let result = request.send().await;
match result {
Ok(response) => {
if response.status().is_success() {
response
.json::<audio::AudioTranscriptionResponse>()
.await
.map_err(|e| self.to_api_error(e))
} else {
let status = response.status();
let body = response.text().await.unwrap_or_default();
Err(error::ApiError {
message: format!("{}: {}", status, body),
})
}
}
Err(error) => Err(error::ApiError {
message: error.to_string(),
}),
}
}
// =========================================================================
// Moderations & Classifications
// =========================================================================
pub fn moderations(
&self,
model: constants::Model,
input: Vec<String>,
) -> Result<moderation::ModerationResponse, error::ApiError> {
let request = moderation::ModerationRequest::new(model, input);
let response = self.post_sync("/moderations", &request)?;
response
.json::<moderation::ModerationResponse>()
.map_err(|e| self.to_api_error(e))
}
pub async fn moderations_async(
&self,
model: constants::Model,
input: Vec<String>,
) -> Result<moderation::ModerationResponse, error::ApiError> {
let request = moderation::ModerationRequest::new(model, input);
let response = self.post_async("/moderations", &request).await?;
response
.json::<moderation::ModerationResponse>()
.await
.map_err(|e| self.to_api_error(e))
}
pub fn chat_moderations(
&self,
request: &moderation::ChatModerationRequest,
) -> Result<moderation::ModerationResponse, error::ApiError> {
let response = self.post_sync("/chat/moderations", request)?;
response
.json::<moderation::ModerationResponse>()
.map_err(|e| self.to_api_error(e))
}
pub async fn chat_moderations_async(
&self,
request: &moderation::ChatModerationRequest,
) -> Result<moderation::ModerationResponse, error::ApiError> {
let response = self.post_async("/chat/moderations", request).await?;
response
.json::<moderation::ModerationResponse>()
.await
.map_err(|e| self.to_api_error(e))
}
pub fn classifications(
&self,
request: &moderation::ClassificationRequest,
) -> Result<moderation::ClassificationResponse, error::ApiError> {
let response = self.post_sync("/classifications", request)?;
response
.json::<moderation::ClassificationResponse>()
.map_err(|e| self.to_api_error(e))
}
pub async fn classifications_async(
&self,
request: &moderation::ClassificationRequest,
) -> Result<moderation::ClassificationResponse, error::ApiError> {
let response = self.post_async("/classifications", request).await?;
response
.json::<moderation::ClassificationResponse>()
.await
.map_err(|e| self.to_api_error(e))
}
// =========================================================================
// Agent Completions
// =========================================================================
pub fn agent_completion(
&self,
agent_id: String,
messages: Vec<chat::ChatMessage>,
options: Option<agents::AgentCompletionParams>,
) -> Result<agents::AgentCompletionResponse, error::ApiError> {
let request = agents::AgentCompletionRequest::new(agent_id, messages, false, options);
let response = self.post_sync("/agents/completions", &request)?;
response
.json::<agents::AgentCompletionResponse>()
.map_err(|e| self.to_api_error(e))
}
pub async fn agent_completion_async(
&self,
agent_id: String,
messages: Vec<chat::ChatMessage>,
options: Option<agents::AgentCompletionParams>,
) -> Result<agents::AgentCompletionResponse, error::ApiError> {
let request = agents::AgentCompletionRequest::new(agent_id, messages, false, options);
let response = self.post_async("/agents/completions", &request).await?;
response
.json::<agents::AgentCompletionResponse>()
.await
.map_err(|e| self.to_api_error(e))
}
// =========================================================================
// Function Calling
// =========================================================================

55
src/v1/files.rs Normal file
View File

@@ -0,0 +1,55 @@
use serde::{Deserialize, Serialize};
// -----------------------------------------------------------------------------
// Request
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum FilePurpose {
FineTune,
Batch,
Ocr,
}
// -----------------------------------------------------------------------------
// Response
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct FileListResponse {
pub data: Vec<FileObject>,
pub object: String,
#[serde(default)]
pub total: u32,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct FileObject {
pub id: String,
pub object: String,
pub bytes: u64,
pub created_at: u64,
pub filename: String,
pub purpose: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub sample_type: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub source: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub num_lines: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub mimetype: Option<String>,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct FileDeleteResponse {
pub id: String,
pub object: String,
pub deleted: bool,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct FileUrlResponse {
pub url: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub expires_at: Option<u64>,
}

101
src/v1/fim.rs Normal file
View File

@@ -0,0 +1,101 @@
use serde::{Deserialize, Serialize};
use crate::v1::{common, constants};
// -----------------------------------------------------------------------------
// Request
#[derive(Debug)]
pub struct FimParams {
pub suffix: Option<String>,
pub max_tokens: Option<u32>,
pub min_tokens: Option<u32>,
pub temperature: Option<f32>,
pub top_p: Option<f32>,
pub stop: Option<Vec<String>>,
pub random_seed: Option<u32>,
}
impl Default for FimParams {
fn default() -> Self {
Self {
suffix: None,
max_tokens: None,
min_tokens: None,
temperature: None,
top_p: None,
stop: None,
random_seed: None,
}
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct FimRequest {
pub model: constants::Model,
pub prompt: String,
pub stream: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub suffix: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_tokens: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub min_tokens: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub temperature: Option<f32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub top_p: Option<f32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub stop: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub random_seed: Option<u32>,
}
impl FimRequest {
pub fn new(
model: constants::Model,
prompt: String,
stream: bool,
options: Option<FimParams>,
) -> Self {
let opts = options.unwrap_or_default();
Self {
model,
prompt,
stream,
suffix: opts.suffix,
max_tokens: opts.max_tokens,
min_tokens: opts.min_tokens,
temperature: opts.temperature,
top_p: opts.top_p,
stop: opts.stop,
random_seed: opts.random_seed,
}
}
}
// -----------------------------------------------------------------------------
// Response
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct FimResponse {
pub id: String,
pub object: String,
pub created: u64,
pub model: constants::Model,
pub choices: Vec<FimResponseChoice>,
pub usage: common::ResponseUsage,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct FimResponseChoice {
pub index: u32,
pub message: FimResponseMessage,
pub finish_reason: String,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct FimResponseMessage {
pub role: String,
pub content: String,
}

101
src/v1/fine_tuning.rs Normal file
View File

@@ -0,0 +1,101 @@
use serde::{Deserialize, Serialize};
use crate::v1::constants;
// -----------------------------------------------------------------------------
// Request
#[derive(Debug, Serialize, Deserialize)]
pub struct FineTuningJobRequest {
pub model: constants::Model,
pub training_files: Vec<TrainingFile>,
#[serde(skip_serializing_if = "Option::is_none")]
pub validation_files: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub hyperparameters: Option<Hyperparameters>,
#[serde(skip_serializing_if = "Option::is_none")]
pub suffix: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub auto_start: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub job_type: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub integrations: Option<Vec<Integration>>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct TrainingFile {
pub file_id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub weight: Option<f32>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Hyperparameters {
#[serde(skip_serializing_if = "Option::is_none")]
pub learning_rate: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub training_steps: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub warmup_fraction: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub epochs: Option<f64>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Integration {
pub r#type: String,
pub project: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub api_key: Option<String>,
}
// -----------------------------------------------------------------------------
// Response
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct FineTuningJobResponse {
pub id: String,
pub object: String,
pub model: constants::Model,
pub status: FineTuningJobStatus,
pub created_at: u64,
#[serde(skip_serializing_if = "Option::is_none")]
pub modified_at: Option<u64>,
pub training_files: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub validation_files: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub hyperparameters: Option<Hyperparameters>,
#[serde(skip_serializing_if = "Option::is_none")]
pub fine_tuned_model: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub suffix: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub integrations: Option<Vec<Integration>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub trained_tokens: Option<u64>,
}
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum FineTuningJobStatus {
Queued,
Running,
Success,
Failed,
TimeoutExceeded,
CancellationRequested,
Cancelled,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct FineTuningJobListResponse {
pub data: Vec<FineTuningJobResponse>,
pub object: String,
#[serde(default)]
pub total: u32,
}

View File

@@ -1,3 +1,6 @@
pub mod agents;
pub mod audio;
pub mod batch;
pub mod chat;
pub mod chat_stream;
pub mod client;
@@ -5,6 +8,11 @@ pub mod common;
pub mod constants;
pub mod embedding;
pub mod error;
pub mod files;
pub mod fim;
pub mod fine_tuning;
pub mod model_list;
pub mod moderation;
pub mod ocr;
pub mod tool;
pub mod utils;

70
src/v1/moderation.rs Normal file
View File

@@ -0,0 +1,70 @@
use serde::{Deserialize, Serialize};
use crate::v1::constants;
// -----------------------------------------------------------------------------
// Request
#[derive(Debug, Serialize, Deserialize)]
pub struct ModerationRequest {
pub model: constants::Model,
pub input: Vec<String>,
}
impl ModerationRequest {
pub fn new(model: constants::Model, input: Vec<String>) -> Self {
Self { model, input }
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct ChatModerationRequest {
pub model: constants::Model,
pub input: Vec<ChatModerationInput>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct ChatModerationInput {
pub role: String,
pub content: String,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct ClassificationRequest {
pub model: constants::Model,
pub input: Vec<String>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct ChatClassificationRequest {
pub model: constants::Model,
pub input: Vec<ChatModerationInput>,
}
// -----------------------------------------------------------------------------
// Response
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct ModerationResponse {
pub id: String,
pub model: constants::Model,
pub results: Vec<ModerationResult>,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct ModerationResult {
pub categories: serde_json::Value,
pub category_scores: serde_json::Value,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct ClassificationResponse {
pub id: String,
pub model: constants::Model,
pub results: Vec<ClassificationResult>,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct ClassificationResult {
pub categories: serde_json::Value,
pub category_scores: serde_json::Value,
}

96
src/v1/ocr.rs Normal file
View File

@@ -0,0 +1,96 @@
use serde::{Deserialize, Serialize};
use crate::v1::constants;
// -----------------------------------------------------------------------------
// Request
#[derive(Debug, Serialize, Deserialize)]
pub struct OcrRequest {
pub model: constants::Model,
pub document: OcrDocument,
#[serde(skip_serializing_if = "Option::is_none")]
pub pages: Option<Vec<u32>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub table_format: Option<OcrTableFormat>,
#[serde(skip_serializing_if = "Option::is_none")]
pub include_image_base64: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub image_limit: Option<u32>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct OcrDocument {
#[serde(rename = "type")]
pub type_: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub document_url: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub file_id: Option<String>,
}
impl OcrDocument {
pub fn from_url(url: &str) -> Self {
Self {
type_: "document_url".to_string(),
document_url: Some(url.to_string()),
file_id: None,
}
}
pub fn from_file_id(file_id: &str) -> Self {
Self {
type_: "file_id".to_string(),
document_url: None,
file_id: Some(file_id.to_string()),
}
}
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum OcrTableFormat {
Markdown,
Html,
}
// -----------------------------------------------------------------------------
// Response
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct OcrResponse {
pub pages: Vec<OcrPage>,
#[serde(skip_serializing_if = "Option::is_none")]
pub usage_info: Option<OcrUsageInfo>,
pub model: constants::Model,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct OcrPage {
pub index: u32,
pub markdown: String,
#[serde(default)]
pub images: Vec<OcrImage>,
#[serde(skip_serializing_if = "Option::is_none")]
pub dimensions: Option<OcrPageDimensions>,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct OcrImage {
pub id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub image_base64: Option<String>,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct OcrPageDimensions {
pub width: f32,
pub height: f32,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct OcrUsageInfo {
pub pages_processed: u32,
#[serde(skip_serializing_if = "Option::is_none")]
pub doc_size_bytes: Option<u64>,
}