flow_adapter_ai/request.rs
1use serde::{Deserialize, Serialize};
2
3#[derive(Debug, Clone, Default, Serialize, Deserialize)]
4pub struct CloudAiRequest {
5 pub model: String,
6 pub prompt: String,
7 pub max_tokens: Option<u32>,
8 pub temperature: Option<f32>,
9 pub api_key: String,
10 /// Optional system-role text. Each provider maps it to its native
11 /// shape:
12 ///
13 /// - Claude: top-level `system` field on the request payload.
14 /// - OpenAI: prepended `{role:"system"}` message.
15 /// - Gemini: top-level `system_instruction`.
16 ///
17 /// `None` sends a single user-role message with no system text - the
18 /// cloud-AI **node** path, which has no per-task system prompt.
19 #[serde(default)]
20 pub system: Option<String>,
21 /// Override endpoint for the OpenAI-compatible local provider
22 /// (e.g. `http://localhost:11434/v1/chat/completions`). Cloud
23 /// providers ignore this and use their fixed `ENDPOINT` const; the
24 /// `local` provider requires it.
25 #[serde(default)]
26 pub base_url: Option<String>,
27 /// Nucleus sampling cutoff (0..=1). Forwarded by the OpenAI-compatible
28 /// body builder (`top_p`); cloud providers with their own builders
29 /// (Claude, Gemini) ignore it. `None` means "let the server decide".
30 #[serde(default)]
31 pub top_p: Option<f32>,
32 /// Top-K sampling cutoff. Non-standard in OpenAI's API but accepted
33 /// by llama.cpp / Ollama / LM Studio; the OpenAI-compatible body
34 /// builder forwards it verbatim when set.
35 #[serde(default)]
36 pub top_k: Option<u32>,
37 /// Stop sequences (any element halts generation). Forwarded as the
38 /// OpenAI `stop` field. Empty `None` means no caller-supplied stops.
39 #[serde(default)]
40 pub stop: Option<Vec<String>>,
41 /// When `true` the caller is invoking the provider's `invoke_stream`
42 /// path and expects per-chunk events. Providers always set
43 /// `stream: true` in the HTTP payload when this is set.
44 #[serde(default)]
45 pub stream: bool,
46 /// Caller-supplied correlation id stamped onto every emitted
47 /// `LlmStreamEvent`. Required when `stream = true`; harmless
48 /// otherwise.
49 #[serde(default)]
50 pub call_id: Option<String>,
51 /// Per-request reasoning/thinking toggle for models that support it.
52 /// `Some(true)` asks the provider to enable extended thinking, `Some(false)`
53 /// disables it, `None` leaves the server/model default. Each provider maps
54 /// it to its native shape (OpenAI-compat `chat_template_kwargs`, Claude
55 /// `thinking`, Gemini `thinkingConfig`).
56 #[serde(default)]
57 pub reasoning: Option<bool>,
58 /// Image references for vision models - `http(s)` URLs or `data:` URLs (the
59 /// executor resolves local paths to base64 `data:` URLs before building the
60 /// request). Empty for text-only calls.
61 #[serde(default)]
62 pub images: Vec<String>,
63 /// Tool/function specs the model may call. Empty disables tool use; the
64 /// actual call loop runs in `CloudAiProvider::invoke_tools`.
65 #[serde(default)]
66 pub tools: Vec<ToolSpec>,
67 /// Optional JSON Schema for structured output. When set, the
68 /// OpenAI-compatible body builder adds a `response_format: json_schema` so
69 /// the model is constrained to matching JSON (providers with their own body
70 /// builders - Claude / Gemini - ignore it and rely on the prompt). The
71 /// `"structured"` task sets this from the node's `outputSchema`.
72 #[serde(default, skip_serializing_if = "Option::is_none")]
73 pub response_schema: Option<serde_json::Value>,
74}
75
76/// A tool/function the model may call, in provider-neutral form. `parameters`
77/// is a JSON-Schema object describing the arguments. The executor builds these
78/// from a bound adapter's action descriptor; each provider renders one into its
79/// own tool wire shape.
80#[derive(Debug, Clone, Serialize, Deserialize)]
81pub struct ToolSpec {
82 pub name: String,
83 pub description: String,
84 pub parameters: serde_json::Value,
85}
86
87/// Runs a single tool call on behalf of a provider's tool loop. The provider
88/// parses the model's tool call (name + JSON args) and calls this; the
89/// implementor (the executor) maps the name to an adapter action, runs it, and
90/// returns the result payload - or an `Err` string that is fed back to the
91/// model as the tool result so it can recover.
92#[async_trait::async_trait]
93pub trait ToolDispatcher: Send + Sync {
94 async fn call(&self, name: &str, args: &serde_json::Value)
95 -> Result<serde_json::Value, String>;
96}
97
98/// Result of an embeddings call (`task: "embedding"`). Carries the vector and
99/// its dimensionality so the node envelope can surface both.
100#[derive(Debug, Clone, Serialize, Deserialize)]
101pub struct EmbeddingResponse {
102 pub provider: String,
103 pub model: String,
104 pub embedding: Vec<f32>,
105 pub dims: usize,
106 pub latency_ms: u64,
107}
108
109#[derive(Debug, Clone, Serialize, Deserialize)]
110pub struct CloudAiResponse {
111 pub provider: String,
112 pub model: String,
113 pub text: String,
114 pub finish_reason: String,
115 pub input_tokens: u32,
116 pub output_tokens: u32,
117 pub latency_ms: u64,
118}