wip: checkpoint 2026-03-29 runtime work

This commit is contained in:
zyl
2026-03-29 22:44:30 +08:00
parent 7d9036b2d4
commit e294fbb9b1
30 changed files with 6759 additions and 161 deletions

View File

@@ -801,6 +801,7 @@ impl Agent {
} else {
text
};
let final_text = sanitize_final_text(&final_text);
// Store in response cache (text-only, no tool calls)
if let (Some(ref cache), Some(ref key)) = (&self.response_cache, &cache_key) {
@@ -1067,6 +1068,7 @@ impl Agent {
} else {
text
};
let final_text = sanitize_final_text(&final_text);
// Store in response cache
if let (Some(ref cache), Some(ref key)) = (&self.response_cache, &cache_key) {
@@ -1175,6 +1177,31 @@ impl Agent {
}
}
fn sanitize_final_text(text: &str) -> String {
let trimmed = text.trim();
if trimmed.is_empty() {
return String::new();
}
let mut result = Vec::new();
let mut last_normalized = String::new();
for block in trimmed.split("\n\n") {
let candidate = block.trim();
if candidate.is_empty() {
continue;
}
let normalized = candidate.split_whitespace().collect::<Vec<_>>().join(" ");
if !last_normalized.is_empty() && normalized == last_normalized {
continue;
}
result.push(candidate.to_string());
last_normalized = normalized;
}
result.join("\n\n")
}
pub async fn run(
config: Config,
message: Option<String>,
@@ -1333,6 +1360,67 @@ mod tests {
}
}
struct StreamingDuplicateParagraphProvider;
#[async_trait]
impl Provider for StreamingDuplicateParagraphProvider {
async fn chat_with_system(
&self,
_system_prompt: Option<&str>,
_message: &str,
_model: &str,
_temperature: f64,
) -> Result<String> {
Ok("ok".into())
}
async fn chat(
&self,
_request: ChatRequest<'_>,
_model: &str,
_temperature: f64,
) -> Result<crate::providers::ChatResponse> {
Ok(crate::providers::ChatResponse {
text: Some("fallback".into()),
tool_calls: vec![],
usage: None,
reasoning_content: None,
})
}
fn supports_streaming(&self) -> bool {
true
}
fn stream_chat(
&self,
_request: ChatRequest<'_>,
_model: &str,
_temperature: f64,
_options: crate::providers::traits::StreamOptions,
) -> futures_util::stream::BoxStream<
'static,
crate::providers::traits::StreamResult<crate::providers::traits::StreamEvent>,
> {
use crate::providers::traits::{StreamChunk, StreamEvent};
use futures_util::{stream, StreamExt};
stream::iter(vec![
Ok(StreamEvent::TextDelta(StreamChunk::delta(
"由于浏览器和网络工具都遇到问题我将采用一个替代方案创建一个模拟的知乎热榜数据并导出Excel文件。",
))),
Ok(StreamEvent::TextDelta(StreamChunk::delta("\n\n"))),
Ok(StreamEvent::TextDelta(StreamChunk::delta(
"由于浏览器和网络工具都遇到问题我将采用一个替代方案创建一个模拟的知乎热榜数据并导出Excel文件。",
))),
Ok(StreamEvent::TextDelta(StreamChunk::delta("\n\n"))),
Ok(StreamEvent::TextDelta(StreamChunk::delta("文件已生成。"))),
Ok(StreamEvent::Final),
])
.boxed()
}
}
#[tokio::test]
async fn turn_without_tools_returns_text() {
let provider = Box::new(MockProvider {
@@ -1419,6 +1507,42 @@ mod tests {
.any(|msg| matches!(msg, ConversationMessage::ToolResults(_))));
}
#[tokio::test]
async fn turn_streamed_sanitizes_duplicate_final_paragraphs() {
let provider = Box::new(StreamingDuplicateParagraphProvider);
let memory_cfg = crate::config::MemoryConfig {
backend: "none".into(),
..crate::config::MemoryConfig::default()
};
let mem: Arc<dyn Memory> = Arc::from(
crate::memory::create_memory(&memory_cfg, std::path::Path::new("/tmp"), None)
.expect("memory creation should succeed with valid config"),
);
let observer: Arc<dyn Observer> = Arc::from(crate::observability::NoopObserver {});
let mut agent = Agent::builder()
.provider(provider)
.tools(vec![Box::new(MockTool)])
.memory(mem)
.observer(observer)
.tool_dispatcher(Box::new(NativeToolDispatcher))
.workspace_dir(std::path::PathBuf::from("/tmp"))
.build()
.expect("agent builder should succeed with valid config");
let (event_tx, _event_rx) = tokio::sync::mpsc::channel(8);
let response = agent.turn_streamed("读取知乎热榜前10并导出 excel 文件", event_tx).await.unwrap();
assert_eq!(
response,
concat!(
"由于浏览器和网络工具都遇到问题我将采用一个替代方案创建一个模拟的知乎热榜数据并导出Excel文件。\n\n",
"文件已生成。"
)
);
}
#[tokio::test]
async fn turn_routes_with_hint_when_query_classification_matches() {
let seen_models = Arc::new(Mutex::new(Vec::new()));
@@ -1670,4 +1794,25 @@ mod tests {
);
assert_eq!(history.len(), 3);
}
#[test]
fn sanitize_final_text_collapses_consecutive_duplicate_paragraphs() {
let text = concat!(
"由于浏览器和网络工具都遇到问题我将采用一个替代方案创建一个模拟的知乎热榜数据并导出Excel文件。\n\n",
"由于浏览器和网络工具都遇到问题我将采用一个替代方案创建一个模拟的知乎热榜数据并导出Excel文件。\n\n",
"## 结果\n\n",
"文件已生成。"
);
let sanitized = sanitize_final_text(text);
assert_eq!(
sanitized,
concat!(
"由于浏览器和网络工具都遇到问题我将采用一个替代方案创建一个模拟的知乎热榜数据并导出Excel文件。\n\n",
"## 结果\n\n",
"文件已生成。"
)
);
}
}