1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
//! Native client and data contract for the Needle 3 localhost runner
use super::localhost;
use crate::io::ApiResult;
use acorn_core::prelude::{String, Vec};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use strum::{Display, EnumIs};
/// Kind of response returned by Needle's `/complete` endpoint.
#[derive(Clone, Copy, Debug, Deserialize, Display, EnumIs, Eq, JsonSchema, PartialEq, Serialize)]
#[serde(rename_all = "lowercase")]
#[strum(serialize_all = "lowercase")]
pub enum ResponseType {
/// One or more tool calls were selected.
Call,
/// The request was refused.
Refuse,
/// A final response was generated.
Respond,
}
/// Typed client for a locally bound Needle server
#[derive(Clone, Debug)]
pub struct Client {
localhost: localhost::Client,
}
/// One completion request accepted by the standalone Needle server.
#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct CompleteRequest {
/// User query or serialized tool result for the next inference turn.
pub input: String,
}
/// Native response returned by Needle's `/complete` endpoint.
#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
pub struct CompleteResponse {
/// Response kind such as `call`, `respond`, or `refuse`.
#[serde(rename = "type")]
pub response_type: ResponseType,
/// Whether inference completed successfully.
#[serde(default)]
pub success: bool,
/// Human-readable inference failure.
pub error: Option<String>,
/// Stable upstream failure code when present.
pub error_code: Option<String>,
/// Selected function calls.
#[serde(default)]
pub function_calls: Vec<FunctionCall>,
/// Stable machine-readable reason for a refusal or validation decision.
pub reason: Option<String>,
/// Short unconstrained derivation emitted by Needle.
pub reasoning: Option<String>,
/// Calibrated confidence for base-model responses.
pub confidence: Option<f64>,
/// Calls withheld by Needle's confidence or grounding policy.
#[serde(default)]
pub suppressed_calls: Vec<FunctionCall>,
/// Prompt-processing throughput.
pub prefill_tps: Option<f64>,
/// Decode throughput.
pub decode_tps: Option<f64>,
/// Peak session memory reported by the runner.
pub peak_ram_mb: Option<f64>,
/// Grounding and negation checks reported by Needle.
pub validation: Option<ResponseValidation>,
}
/// Function selected by Needle with grammar-constrained arguments.
#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct FunctionCall {
/// Registered function name.
pub name: String,
/// Function arguments.
#[serde(default)]
pub arguments: Value,
}
/// Validation metadata attached to a Needle 3 completion.
#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
pub struct ResponseValidation {
/// Argument fields that are not grounded in the conversation.
#[serde(default)]
pub ungrounded: Vec<String>,
/// Whether the selected call conflicts with a negated request.
#[serde(default)]
pub negation: bool,
}
impl Client {
/// Connect to a validated loopback Needle endpoint
pub fn new(base_url: impl Into<String>) -> ApiResult<Self> {
localhost::Client::new(base_url).map(|localhost| Self { localhost })
}
/// Complete one Needle inference turn
pub async fn complete(&self, input: impl Into<String>) -> ApiResult<CompleteResponse> {
self.localhost.post_json("/complete", &CompleteRequest { input: input.into() }).await
}
/// Return the validated endpoint URL
pub fn endpoint(&self) -> &str {
self.localhost.base_url()
}
}