Token Classification
Transformers
ONNX
Safetensors
English
Japanese
Chinese
bert
anime
filename-parsing
Eval Results (legacy)
Instructions to use ModerRAS/AniFileBERT with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use ModerRAS/AniFileBERT with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("token-classification", model="ModerRAS/AniFileBERT")# pip install -U transformers accelerate # Load model directly from transformers import AutoTokenizer, AutoModelForTokenClassification tokenizer = AutoTokenizer.from_pretrained("ModerRAS/AniFileBERT") model = AutoModelForTokenClassification.from_pretrained("ModerRAS/AniFileBERT", device_map="auto") - Notebooks
- Google Colab
- Kaggle
Add Pi RPC title-boundary reviewer backend
Browse files
tools/rust_dmhy_template_apply/README.md
CHANGED
|
@@ -53,9 +53,9 @@ cargo run --release --manifest-path tools\rust_dmhy_template_apply\Cargo.toml --
|
|
| 53 |
--audit-max-count 50
|
| 54 |
```
|
| 55 |
|
| 56 |
-
Review ambiguous title-boundary decisions with a local LM Studio model
|
| 57 |
-
an optional final-pass reviewer: it writes a decision cache
|
| 58 |
-
modify recipes or training data directly.
|
| 59 |
|
| 60 |
```powershell
|
| 61 |
cargo run --release --manifest-path tools\rust_dmhy_template_apply\Cargo.toml -- `
|
|
@@ -69,6 +69,17 @@ cargo run --release --manifest-path tools\rust_dmhy_template_apply\Cargo.toml --
|
|
| 69 |
--limit 50
|
| 70 |
```
|
| 71 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 72 |
Optional controls:
|
| 73 |
|
| 74 |
```powershell
|
|
|
|
| 53 |
--audit-max-count 50
|
| 54 |
```
|
| 55 |
|
| 56 |
+
Review ambiguous title-boundary decisions with a local LM Studio model or with
|
| 57 |
+
Pi RPC mode. This is an optional final-pass reviewer: it writes a decision cache
|
| 58 |
+
JSONL and does not modify recipes or training data directly.
|
| 59 |
|
| 60 |
```powershell
|
| 61 |
cargo run --release --manifest-path tools\rust_dmhy_template_apply\Cargo.toml -- `
|
|
|
|
| 69 |
--limit 50
|
| 70 |
```
|
| 71 |
|
| 72 |
+
```powershell
|
| 73 |
+
cargo run --release --manifest-path tools\rust_dmhy_template_apply\Cargo.toml -- `
|
| 74 |
+
--review-title-boundaries-pi-rpc `
|
| 75 |
+
--recipes reports\dmhy_template_recipes.full_top5000.seed.jsonl `
|
| 76 |
+
--title-boundary-decisions-output reports\dmhy_title_boundary_pi_rpc_decisions.jsonl `
|
| 77 |
+
--pi-rpc-model openai/gpt-5.5 `
|
| 78 |
+
--title-boundary-min-similarity 0.25 `
|
| 79 |
+
--title-boundary-max-similarity 0.85 `
|
| 80 |
+
--limit 50
|
| 81 |
+
```
|
| 82 |
+
|
| 83 |
Optional controls:
|
| 84 |
|
| 85 |
```powershell
|
tools/rust_dmhy_template_apply/src/main.rs
CHANGED
|
@@ -10,6 +10,7 @@ use std::collections::{HashMap, HashSet};
|
|
| 10 |
use std::fs::{self, File};
|
| 11 |
use std::io::{BufRead, BufReader, BufWriter, Read, Write};
|
| 12 |
use std::path::PathBuf;
|
|
|
|
| 13 |
use std::sync::atomic::{AtomicUsize, Ordering};
|
| 14 |
|
| 15 |
#[derive(Parser, Debug)]
|
|
@@ -25,6 +26,8 @@ struct Args {
|
|
| 25 |
rich_annotations: bool,
|
| 26 |
#[arg(long)]
|
| 27 |
review_title_boundaries_lmstudio: bool,
|
|
|
|
|
|
|
| 28 |
#[arg(long, default_value = "datasets/AnimeName/dmhy_list.jsonl")]
|
| 29 |
input: PathBuf,
|
| 30 |
#[arg(long, default_value = "reports/dmhy_template_recipes.seed.jsonl")]
|
|
@@ -76,6 +79,8 @@ struct Args {
|
|
| 76 |
lmstudio_temperature: f64,
|
| 77 |
#[arg(long, default_value_t = 120)]
|
| 78 |
lmstudio_timeout_seconds: u64,
|
|
|
|
|
|
|
| 79 |
#[arg(long, default_value_t = 50)]
|
| 80 |
audit_max_count: u64,
|
| 81 |
#[arg(long)]
|
|
@@ -329,8 +334,8 @@ fn main() -> Result<()> {
|
|
| 329 |
if args.rich_annotations {
|
| 330 |
return run_rich_annotations(&args);
|
| 331 |
}
|
| 332 |
-
if args.review_title_boundaries_lmstudio {
|
| 333 |
-
return
|
| 334 |
}
|
| 335 |
if args.expand != "all" && args.expand != "sample" {
|
| 336 |
bail!("--expand must be all or sample");
|
|
@@ -1171,7 +1176,7 @@ fn run_rich_annotations(args: &Args) -> Result<()> {
|
|
| 1171 |
Ok(())
|
| 1172 |
}
|
| 1173 |
|
| 1174 |
-
fn
|
| 1175 |
if !(0.0..=1.0).contains(&args.title_boundary_min_similarity)
|
| 1176 |
|| !(0.0..=1.0).contains(&args.title_boundary_max_similarity)
|
| 1177 |
|| args.title_boundary_min_similarity > args.title_boundary_max_similarity
|
|
@@ -1188,10 +1193,18 @@ fn run_review_title_boundaries_lmstudio(args: &Args) -> Result<()> {
|
|
| 1188 |
let mut writer = BufWriter::new(File::create(&args.title_boundary_decisions_output)?);
|
| 1189 |
let mut reviewed = 0usize;
|
| 1190 |
let mut skipped = 0usize;
|
|
|
|
|
|
|
|
|
|
| 1191 |
let endpoint = format!(
|
| 1192 |
"{}/chat/completions",
|
| 1193 |
args.lmstudio_base_url.trim_end_matches('/')
|
| 1194 |
);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1195 |
|
| 1196 |
for recipe in recipes {
|
| 1197 |
let template_id = recipe
|
|
@@ -1226,27 +1239,34 @@ fn run_review_title_boundaries_lmstudio(args: &Args) -> Result<()> {
|
|
| 1226 |
}
|
| 1227 |
}
|
| 1228 |
let prompt = title_boundary_review_prompt(&recipe, &decision);
|
| 1229 |
-
let
|
| 1230 |
-
&
|
| 1231 |
-
|
| 1232 |
-
|
| 1233 |
-
|
| 1234 |
-
|
| 1235 |
-
|
| 1236 |
-
|
| 1237 |
-
|
| 1238 |
-
|
|
|
|
| 1239 |
)
|
| 1240 |
-
|
| 1241 |
-
|
| 1242 |
-
|
| 1243 |
-
|
| 1244 |
-
|
| 1245 |
-
|
| 1246 |
-
|
| 1247 |
-
|
| 1248 |
-
|
| 1249 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1250 |
json!({
|
| 1251 |
"decision": "skip",
|
| 1252 |
"kind": "parse_error",
|
|
@@ -1265,8 +1285,8 @@ fn run_review_title_boundaries_lmstudio(args: &Args) -> Result<()> {
|
|
| 1265 |
"model_kind": parsed.get("kind").cloned().unwrap_or(Value::Null),
|
| 1266 |
"confidence": parsed.get("confidence").cloned().unwrap_or(Value::Null),
|
| 1267 |
"reason": parsed.get("reason").cloned().unwrap_or(Value::Null),
|
| 1268 |
-
"source": "lmstudio",
|
| 1269 |
-
"model": args.lmstudio_model,
|
| 1270 |
"reviewed_at": Utc::now().to_rfc3339(),
|
| 1271 |
});
|
| 1272 |
serde_json::to_writer(&mut writer, &output)?;
|
|
@@ -1280,8 +1300,9 @@ fn run_review_title_boundaries_lmstudio(args: &Args) -> Result<()> {
|
|
| 1280 |
"output": args.title_boundary_decisions_output.to_string_lossy(),
|
| 1281 |
"reviewed": reviewed,
|
| 1282 |
"skipped": skipped,
|
| 1283 |
-
"model": args.lmstudio_model,
|
| 1284 |
-
"
|
|
|
|
| 1285 |
"similarity_range": [args.title_boundary_min_similarity, args.title_boundary_max_similarity],
|
| 1286 |
"generated_at": Utc::now().to_rfc3339(),
|
| 1287 |
});
|
|
@@ -1369,6 +1390,149 @@ fn title_boundary_review_prompt(recipe: &Value, decision: &Value) -> String {
|
|
| 1369 |
)
|
| 1370 |
}
|
| 1371 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1372 |
fn call_lmstudio_title_boundary(
|
| 1373 |
endpoint: &str,
|
| 1374 |
model: &str,
|
|
|
|
| 10 |
use std::fs::{self, File};
|
| 11 |
use std::io::{BufRead, BufReader, BufWriter, Read, Write};
|
| 12 |
use std::path::PathBuf;
|
| 13 |
+
use std::process::{Child, ChildStdin, Command, Stdio};
|
| 14 |
use std::sync::atomic::{AtomicUsize, Ordering};
|
| 15 |
|
| 16 |
#[derive(Parser, Debug)]
|
|
|
|
| 26 |
rich_annotations: bool,
|
| 27 |
#[arg(long)]
|
| 28 |
review_title_boundaries_lmstudio: bool,
|
| 29 |
+
#[arg(long)]
|
| 30 |
+
review_title_boundaries_pi_rpc: bool,
|
| 31 |
#[arg(long, default_value = "datasets/AnimeName/dmhy_list.jsonl")]
|
| 32 |
input: PathBuf,
|
| 33 |
#[arg(long, default_value = "reports/dmhy_template_recipes.seed.jsonl")]
|
|
|
|
| 79 |
lmstudio_temperature: f64,
|
| 80 |
#[arg(long, default_value_t = 120)]
|
| 81 |
lmstudio_timeout_seconds: u64,
|
| 82 |
+
#[arg(long)]
|
| 83 |
+
pi_rpc_model: Option<String>,
|
| 84 |
#[arg(long, default_value_t = 50)]
|
| 85 |
audit_max_count: u64,
|
| 86 |
#[arg(long)]
|
|
|
|
| 334 |
if args.rich_annotations {
|
| 335 |
return run_rich_annotations(&args);
|
| 336 |
}
|
| 337 |
+
if args.review_title_boundaries_lmstudio || args.review_title_boundaries_pi_rpc {
|
| 338 |
+
return run_review_title_boundaries(&args);
|
| 339 |
}
|
| 340 |
if args.expand != "all" && args.expand != "sample" {
|
| 341 |
bail!("--expand must be all or sample");
|
|
|
|
| 1176 |
Ok(())
|
| 1177 |
}
|
| 1178 |
|
| 1179 |
+
fn run_review_title_boundaries(args: &Args) -> Result<()> {
|
| 1180 |
if !(0.0..=1.0).contains(&args.title_boundary_min_similarity)
|
| 1181 |
|| !(0.0..=1.0).contains(&args.title_boundary_max_similarity)
|
| 1182 |
|| args.title_boundary_min_similarity > args.title_boundary_max_similarity
|
|
|
|
| 1193 |
let mut writer = BufWriter::new(File::create(&args.title_boundary_decisions_output)?);
|
| 1194 |
let mut reviewed = 0usize;
|
| 1195 |
let mut skipped = 0usize;
|
| 1196 |
+
if args.review_title_boundaries_lmstudio && args.review_title_boundaries_pi_rpc {
|
| 1197 |
+
bail!("choose only one title-boundary review backend: LM Studio or Pi RPC");
|
| 1198 |
+
}
|
| 1199 |
let endpoint = format!(
|
| 1200 |
"{}/chat/completions",
|
| 1201 |
args.lmstudio_base_url.trim_end_matches('/')
|
| 1202 |
);
|
| 1203 |
+
let mut pi_rpc = if args.review_title_boundaries_pi_rpc {
|
| 1204 |
+
Some(PiRpcClient::start(args.pi_rpc_model.as_deref())?)
|
| 1205 |
+
} else {
|
| 1206 |
+
None
|
| 1207 |
+
};
|
| 1208 |
|
| 1209 |
for recipe in recipes {
|
| 1210 |
let template_id = recipe
|
|
|
|
| 1239 |
}
|
| 1240 |
}
|
| 1241 |
let prompt = title_boundary_review_prompt(&recipe, &decision);
|
| 1242 |
+
let content = if let Some(client) = pi_rpc.as_mut() {
|
| 1243 |
+
client.review(&prompt).with_context(|| {
|
| 1244 |
+
format!("Pi RPC title-boundary review failed for template {template_id}")
|
| 1245 |
+
})?
|
| 1246 |
+
} else {
|
| 1247 |
+
let response = call_lmstudio_title_boundary(
|
| 1248 |
+
&endpoint,
|
| 1249 |
+
&args.lmstudio_model,
|
| 1250 |
+
args.lmstudio_temperature,
|
| 1251 |
+
args.lmstudio_timeout_seconds,
|
| 1252 |
+
&prompt,
|
| 1253 |
)
|
| 1254 |
+
.with_context(|| {
|
| 1255 |
+
format!(
|
| 1256 |
+
"LM Studio title-boundary review failed for template {template_id} via {endpoint}"
|
| 1257 |
+
)
|
| 1258 |
+
})?;
|
| 1259 |
+
response
|
| 1260 |
+
.get("choices")
|
| 1261 |
+
.and_then(Value::as_array)
|
| 1262 |
+
.and_then(|choices| choices.first())
|
| 1263 |
+
.and_then(|choice| choice.get("message"))
|
| 1264 |
+
.and_then(|message| message.get("content"))
|
| 1265 |
+
.and_then(Value::as_str)
|
| 1266 |
+
.unwrap_or("")
|
| 1267 |
+
.to_string()
|
| 1268 |
+
};
|
| 1269 |
+
let parsed = parse_lmstudio_json_object(&content).unwrap_or_else(|| {
|
| 1270 |
json!({
|
| 1271 |
"decision": "skip",
|
| 1272 |
"kind": "parse_error",
|
|
|
|
| 1285 |
"model_kind": parsed.get("kind").cloned().unwrap_or(Value::Null),
|
| 1286 |
"confidence": parsed.get("confidence").cloned().unwrap_or(Value::Null),
|
| 1287 |
"reason": parsed.get("reason").cloned().unwrap_or(Value::Null),
|
| 1288 |
+
"source": if args.review_title_boundaries_pi_rpc { "pi-rpc" } else { "lmstudio" },
|
| 1289 |
+
"model": if args.review_title_boundaries_pi_rpc { args.pi_rpc_model.as_deref().unwrap_or("pi-default") } else { args.lmstudio_model.as_str() },
|
| 1290 |
"reviewed_at": Utc::now().to_rfc3339(),
|
| 1291 |
});
|
| 1292 |
serde_json::to_writer(&mut writer, &output)?;
|
|
|
|
| 1300 |
"output": args.title_boundary_decisions_output.to_string_lossy(),
|
| 1301 |
"reviewed": reviewed,
|
| 1302 |
"skipped": skipped,
|
| 1303 |
+
"model": if args.review_title_boundaries_pi_rpc { args.pi_rpc_model.as_deref().unwrap_or("pi-default") } else { args.lmstudio_model.as_str() },
|
| 1304 |
+
"backend": if args.review_title_boundaries_pi_rpc { "pi-rpc" } else { "lmstudio" },
|
| 1305 |
+
"base_url": if args.review_title_boundaries_pi_rpc { "pi --mode rpc" } else { args.lmstudio_base_url.as_str() },
|
| 1306 |
"similarity_range": [args.title_boundary_min_similarity, args.title_boundary_max_similarity],
|
| 1307 |
"generated_at": Utc::now().to_rfc3339(),
|
| 1308 |
});
|
|
|
|
| 1390 |
)
|
| 1391 |
}
|
| 1392 |
|
| 1393 |
+
struct PiRpcClient {
|
| 1394 |
+
child: Child,
|
| 1395 |
+
stdin: Option<ChildStdin>,
|
| 1396 |
+
stdout: BufReader<std::process::ChildStdout>,
|
| 1397 |
+
next_id: usize,
|
| 1398 |
+
}
|
| 1399 |
+
|
| 1400 |
+
impl PiRpcClient {
|
| 1401 |
+
fn start(model: Option<&str>) -> Result<Self> {
|
| 1402 |
+
let mut command = Command::new("pi");
|
| 1403 |
+
command.args(["--mode", "rpc", "--no-session"]);
|
| 1404 |
+
if let Some(model) = model {
|
| 1405 |
+
command.args(["--model", model]);
|
| 1406 |
+
}
|
| 1407 |
+
let mut child = command
|
| 1408 |
+
.stdin(Stdio::piped())
|
| 1409 |
+
.stdout(Stdio::piped())
|
| 1410 |
+
.stderr(Stdio::inherit())
|
| 1411 |
+
.spawn()
|
| 1412 |
+
.context("failed to start pi --mode rpc")?;
|
| 1413 |
+
let stdin = child
|
| 1414 |
+
.stdin
|
| 1415 |
+
.take()
|
| 1416 |
+
.ok_or_else(|| anyhow::anyhow!("failed to open Pi RPC stdin"))?;
|
| 1417 |
+
let stdout = child
|
| 1418 |
+
.stdout
|
| 1419 |
+
.take()
|
| 1420 |
+
.ok_or_else(|| anyhow::anyhow!("failed to open Pi RPC stdout"))?;
|
| 1421 |
+
Ok(Self {
|
| 1422 |
+
child,
|
| 1423 |
+
stdin: Some(stdin),
|
| 1424 |
+
stdout: BufReader::new(stdout),
|
| 1425 |
+
next_id: 0,
|
| 1426 |
+
})
|
| 1427 |
+
}
|
| 1428 |
+
|
| 1429 |
+
fn review(&mut self, prompt: &str) -> Result<String> {
|
| 1430 |
+
let prompt_id = self.next_request_id("prompt");
|
| 1431 |
+
self.send(json!({"id": prompt_id, "type": "prompt", "message": prompt}))?;
|
| 1432 |
+
let mut accepted = false;
|
| 1433 |
+
loop {
|
| 1434 |
+
let event = self.read_event()?;
|
| 1435 |
+
if event.get("type").and_then(Value::as_str) == Some("response")
|
| 1436 |
+
&& event.get("id").and_then(Value::as_str) == Some(prompt_id.as_str())
|
| 1437 |
+
{
|
| 1438 |
+
if !event
|
| 1439 |
+
.get("success")
|
| 1440 |
+
.and_then(Value::as_bool)
|
| 1441 |
+
.unwrap_or(false)
|
| 1442 |
+
{
|
| 1443 |
+
bail!("Pi RPC prompt rejected: {event}");
|
| 1444 |
+
}
|
| 1445 |
+
accepted = true;
|
| 1446 |
+
continue;
|
| 1447 |
+
}
|
| 1448 |
+
if event.get("type").and_then(Value::as_str) == Some("agent_end") && accepted {
|
| 1449 |
+
break;
|
| 1450 |
+
}
|
| 1451 |
+
}
|
| 1452 |
+
let text = self.get_last_assistant_text()?;
|
| 1453 |
+
self.new_session()?;
|
| 1454 |
+
Ok(text)
|
| 1455 |
+
}
|
| 1456 |
+
|
| 1457 |
+
fn get_last_assistant_text(&mut self) -> Result<String> {
|
| 1458 |
+
let id = self.next_request_id("last");
|
| 1459 |
+
self.send(json!({"id": id, "type": "get_last_assistant_text"}))?;
|
| 1460 |
+
loop {
|
| 1461 |
+
let event = self.read_event()?;
|
| 1462 |
+
if event.get("type").and_then(Value::as_str) == Some("response")
|
| 1463 |
+
&& event.get("id").and_then(Value::as_str) == Some(id.as_str())
|
| 1464 |
+
{
|
| 1465 |
+
if !event
|
| 1466 |
+
.get("success")
|
| 1467 |
+
.and_then(Value::as_bool)
|
| 1468 |
+
.unwrap_or(false)
|
| 1469 |
+
{
|
| 1470 |
+
bail!("Pi RPC get_last_assistant_text failed: {event}");
|
| 1471 |
+
}
|
| 1472 |
+
return Ok(event
|
| 1473 |
+
.get("data")
|
| 1474 |
+
.and_then(|data| data.get("text"))
|
| 1475 |
+
.and_then(Value::as_str)
|
| 1476 |
+
.unwrap_or("")
|
| 1477 |
+
.to_string());
|
| 1478 |
+
}
|
| 1479 |
+
}
|
| 1480 |
+
}
|
| 1481 |
+
|
| 1482 |
+
fn new_session(&mut self) -> Result<()> {
|
| 1483 |
+
let id = self.next_request_id("new");
|
| 1484 |
+
self.send(json!({"id": id, "type": "new_session"}))?;
|
| 1485 |
+
loop {
|
| 1486 |
+
let event = self.read_event()?;
|
| 1487 |
+
if event.get("type").and_then(Value::as_str) == Some("response")
|
| 1488 |
+
&& event.get("id").and_then(Value::as_str) == Some(id.as_str())
|
| 1489 |
+
{
|
| 1490 |
+
if !event
|
| 1491 |
+
.get("success")
|
| 1492 |
+
.and_then(Value::as_bool)
|
| 1493 |
+
.unwrap_or(false)
|
| 1494 |
+
{
|
| 1495 |
+
bail!("Pi RPC new_session failed: {event}");
|
| 1496 |
+
}
|
| 1497 |
+
return Ok(());
|
| 1498 |
+
}
|
| 1499 |
+
}
|
| 1500 |
+
}
|
| 1501 |
+
|
| 1502 |
+
fn next_request_id(&mut self, prefix: &str) -> String {
|
| 1503 |
+
self.next_id += 1;
|
| 1504 |
+
format!("{prefix}-{}", self.next_id)
|
| 1505 |
+
}
|
| 1506 |
+
|
| 1507 |
+
fn send(&mut self, value: Value) -> Result<()> {
|
| 1508 |
+
let stdin = self
|
| 1509 |
+
.stdin
|
| 1510 |
+
.as_mut()
|
| 1511 |
+
.ok_or_else(|| anyhow::anyhow!("Pi RPC stdin is closed"))?;
|
| 1512 |
+
serde_json::to_writer(&mut *stdin, &value)?;
|
| 1513 |
+
stdin.write_all(b"\n")?;
|
| 1514 |
+
stdin.flush()?;
|
| 1515 |
+
Ok(())
|
| 1516 |
+
}
|
| 1517 |
+
|
| 1518 |
+
fn read_event(&mut self) -> Result<Value> {
|
| 1519 |
+
let mut line = String::new();
|
| 1520 |
+
let bytes = self.stdout.read_line(&mut line)?;
|
| 1521 |
+
if bytes == 0 {
|
| 1522 |
+
bail!("Pi RPC process exited");
|
| 1523 |
+
}
|
| 1524 |
+
let line = line.trim_end_matches(['\r', '\n']);
|
| 1525 |
+
serde_json::from_str(line).with_context(|| format!("invalid Pi RPC JSON line: {line}"))
|
| 1526 |
+
}
|
| 1527 |
+
}
|
| 1528 |
+
|
| 1529 |
+
impl Drop for PiRpcClient {
|
| 1530 |
+
fn drop(&mut self) {
|
| 1531 |
+
let _ = self.stdin.take();
|
| 1532 |
+
let _ = self.child.wait();
|
| 1533 |
+
}
|
| 1534 |
+
}
|
| 1535 |
+
|
| 1536 |
fn call_lmstudio_title_boundary(
|
| 1537 |
endpoint: &str,
|
| 1538 |
model: &str,
|