use std::borrow::Cow;
use std::io::{BufRead, Write};
use serde::{Deserialize, Serialize};
use crate::special_input::SpecialInputPolicy;
use crate::tokenization::{
SpecialTokenInsertion, TextTokenizationPolicy, TokenBudget, TokenizationSession,
};
use crate::{Result, invalid};
pub const MAX_WORKER_FRAME_BYTES: usize = 1024 * 1024;
#[derive(Clone, Copy, Debug)]
pub struct WorkerConfig {
pub text_policy: TextTokenizationPolicy,
pub max_frame_bytes: usize,
}
impl WorkerConfig {
pub fn validate(self) -> Result<()> {
self.text_policy.budget.prompt_token_limit()?;
if !(1..=MAX_WORKER_FRAME_BYTES).contains(&self.max_frame_bytes) {
return Err(invalid("invalid token worker frame bound"));
}
Ok(())
}
}
#[derive(Deserialize)]
#[serde(tag = "op", rename_all = "snake_case", deny_unknown_fields)]
enum Request<'a> {
Count {
id: u64,
#[serde(borrow)]
text: Cow<'a, str>,
},
Encode {
id: u64,
#[serde(borrow)]
text: Cow<'a, str>,
},
CountTokens {
id: u64,
token_ids: Vec<u32>,
},
EncodeTokens {
id: u64,
token_ids: Vec<u32>,
},
}
impl Request<'_> {
const fn id(&self) -> u64 {
match *self {
Self::Count { id, .. }
| Self::Encode { id, .. }
| Self::CountTokens { id, .. }
| Self::EncodeTokens { id, .. } => id,
}
}
}
#[derive(Serialize)]
#[serde(tag = "type", rename_all = "snake_case")]
enum Response<'a> {
Ready {
protocol: &'static str,
model_sha256: &'a str,
vocab_size: usize,
context: usize,
reserved_output_tokens: usize,
max_frame_bytes: usize,
special_input: &'static str,
insert_special: &'static str,
},
Count {
id: u64,
prompt_token_count: usize,
},
Encoded {
id: u64,
prompt_token_count: usize,
token_ids: &'a [u32],
},
Error {
id: Option<u64>,
code: &'static str,
},
}
pub fn serve(
session: &TokenizationSession,
config: WorkerConfig,
input: &mut impl BufRead,
output: &mut impl Write,
) -> Result<()> {
config.validate()?;
let TokenBudget {
context,
reserved_output_tokens,
} = config.text_policy.budget;
let ready = Response::Ready {
protocol: "bonsai-token-worker-v1",
model_sha256: session.identity(),
vocab_size: session.vocab_size(),
context,
reserved_output_tokens,
max_frame_bytes: config.max_frame_bytes,
special_input: match config.text_policy.special_input {
SpecialInputPolicy::Literal => "literal",
SpecialInputPolicy::Parse => "parse",
},
insert_special: match config.text_policy.insert_special {
SpecialTokenInsertion::None => "none",
SpecialTokenInsertion::Model => "model",
},
};
let mut frame = Vec::new();
frame.try_reserve_exact(config.max_frame_bytes)?;
write_record(output, &ready)?;
loop {
match read_frame(input, &mut frame, config.max_frame_bytes)? {
Frame::Eof => return Ok(()),
Frame::Unterminated => {
write_invalid_request(output)?;
return Ok(());
}
Frame::Complete => (),
}
if frame.trim_ascii_start().first() != Some(&b'{') {
write_invalid_request(output)?;
continue;
}
let Ok(request) = serde_json::from_slice::<Request<'_>>(&frame) else {
write_invalid_request(output)?;
continue;
};
let result = match &request {
Request::Count { text, .. } | Request::Encode { text, .. } => session
.encode(text, config.text_policy)
.map(|result| Cow::Owned(result.token_ids)),
Request::CountTokens { token_ids, .. } | Request::EncodeTokens { token_ids, .. } => {
session
.validate_tokens(token_ids, config.text_policy.budget)
.map(|()| Cow::Borrowed(token_ids.as_slice()))
}
};
let Ok(token_ids) = result else {
write_record(
output,
&Response::Error {
id: Some(request.id()),
code: "rejected_input",
},
)?;
continue;
};
let response = match &request {
Request::Count { id, .. } | Request::CountTokens { id, .. } => Response::Count {
id: *id,
prompt_token_count: token_ids.len(),
},
Request::Encode { id, .. } | Request::EncodeTokens { id, .. } => Response::Encoded {
id: *id,
prompt_token_count: token_ids.len(),
token_ids: &token_ids,
},
};
write_record(output, &response)?;
}
}
enum Frame {
Eof,
Complete,
Unterminated,
}
fn read_frame(input: &mut impl BufRead, frame: &mut Vec<u8>, limit: usize) -> Result<Frame> {
frame.clear();
loop {
let available = input.fill_buf()?;
if available.is_empty() {
return Ok(if frame.is_empty() {
Frame::Eof
} else {
Frame::Unterminated
});
}
let newline = available.iter().position(|&byte| byte == b'\n');
let count = newline.unwrap_or(available.len());
if count > limit - frame.len() {
return Err(invalid("token worker frame exceeds byte limit"));
}
frame.extend_from_slice(&available[..count]);
input.consume(count + usize::from(newline.is_some()));
if newline.is_some() {
return Ok(Frame::Complete);
}
}
}
fn write_invalid_request(output: &mut impl Write) -> Result<()> {
write_record(
output,
&Response::Error {
id: None,
code: "invalid_request",
},
)
}
fn write_record(output: &mut impl Write, response: &Response<'_>) -> Result<()> {
serde_json::to_writer(&mut *output, response)?;
output.write_all(b"\n")?;
output.flush()?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::{Cow, Request};
use crate::{Result, invalid};
#[test]
fn text_requests_borrow_plain_frames_and_decode_escaped_frames() -> Result<()> {
for bytes in [
br#"{"id":1,"op":"count","text":"ab"}"#.as_slice(),
br#"{"id":1,"op":"encode","text":"ab"}"#,
] {
let request: Request<'_> = serde_json::from_slice(bytes)?;
match request {
Request::Count { text, .. } | Request::Encode { text, .. } => {
assert!(matches!(text, Cow::Borrowed("ab")));
}
_ => return Err(invalid("expected a text request")),
}
}
for bytes in [
br#"{"id":1,"op":"count","text":"\u0061b"}"#.as_slice(),
br#"{"id":1,"op":"encode","text":"a\u0062"}"#,
] {
let request: Request<'_> = serde_json::from_slice(bytes)?;
match request {
Request::Count { text, .. } | Request::Encode { text, .. } => {
assert!(matches!(&text, Cow::Owned(_)));
assert_eq!(text, "ab");
}
_ => return Err(invalid("expected a text request")),
}
}
Ok(())
}
}