fix: 解析 llama-cli 输出,只提取生成回复 (去除 banner/进度动画/统计)
This commit is contained in:
@@ -194,9 +194,18 @@ impl AiBackend for LocalCliBackend {
|
||||
);
|
||||
}
|
||||
|
||||
// llama-cli 输出到 stdout,取生成部分
|
||||
let reply = String::from_utf8_lossy(&output.stdout);
|
||||
Ok(reply.trim().to_string())
|
||||
// llama-cli --single-turn 输出格式:
|
||||
// [banner/logo]
|
||||
// > <user_input> ← 用户输入回显
|
||||
// [进度动画 |/-\]
|
||||
// <生成的回复> ← 我们要提取的部分
|
||||
// [空行]
|
||||
// [ Prompt: ... | Generation: ... ] ← 性能统计
|
||||
//
|
||||
// 解析策略:找到 "> " 开头的行,跳过进度动画行,取实际回复行
|
||||
let raw = String::from_utf8_lossy(&output.stdout);
|
||||
let reply = parse_llama_output(&raw);
|
||||
Ok(reply)
|
||||
}
|
||||
|
||||
fn tts(&self, text: &str, model_path: &Path, output_path: &Path) -> Result<()> {
|
||||
@@ -275,3 +284,86 @@ impl AiBackend for CloudBackend {
|
||||
"cloud"
|
||||
}
|
||||
}
|
||||
|
||||
/// 解析 llama-cli --single-turn 的 stdout 输出,提取生成的回复文本
|
||||
///
|
||||
/// 输出格式:
|
||||
/// ```text
|
||||
/// [banner/logo ASCII art]
|
||||
/// build : ...
|
||||
/// model : ...
|
||||
/// ...
|
||||
/// > <user_input>
|
||||
///
|
||||
/// [进度动画 |/-\]
|
||||
/// <生成的回复>
|
||||
///
|
||||
/// [ Prompt: ... | Generation: ... ]
|
||||
///
|
||||
/// Exiting...
|
||||
/// ```
|
||||
fn parse_llama_output(raw: &str) -> String {
|
||||
let lines: Vec<&str> = raw.lines().collect();
|
||||
let mut reply_lines = Vec::new();
|
||||
let mut after_user_input = false;
|
||||
|
||||
for line in &lines {
|
||||
let trimmed = line.trim();
|
||||
|
||||
// 跳过空行(但在回复区内保留)
|
||||
if trimmed.is_empty() && !after_user_input {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 找到用户输入回显行 "> xxx"
|
||||
if trimmed.starts_with('>') {
|
||||
after_user_input = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if !after_user_input {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 跳过进度动画行(只含 |/-\ 和空格)
|
||||
let cleaned: String = trimmed.chars().filter(|c| !"|\\-/ ".contains(*c)).collect();
|
||||
if cleaned.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 遇到性能统计行 [ Prompt: ... ] 停止
|
||||
if trimmed.starts_with('[') && trimmed.contains("Generation") {
|
||||
break;
|
||||
}
|
||||
// 遇到 "Exiting..." 停止
|
||||
if trimmed == "Exiting..." {
|
||||
break;
|
||||
}
|
||||
|
||||
reply_lines.push(trimmed);
|
||||
}
|
||||
|
||||
reply_lines.join("\n").trim().to_string()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_parse_llama_output() {
|
||||
let raw = " █ █ build : b1-fdb1db8\n\
|
||||
model : model_store/llm/qwen2.5-0.5b-q4_k_m.gguf\n\
|
||||
\n\
|
||||
> hello\n\
|
||||
\n\
|
||||
|/-\\|/-\\ 汪!\n\
|
||||
\n\
|
||||
[ Prompt: 31.1 t/s | Generation: 26.3 t/s ]\n\
|
||||
\n\
|
||||
Exiting...\n";
|
||||
|
||||
let reply = parse_llama_output(raw);
|
||||
assert_eq!(reply, "汪!");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user