mirror of
https://github.com/alibaba/higress.git
synced 2026-06-10 05:07:30 +08:00
Ai data masking msg window (#1775)
This commit is contained in:
@@ -13,8 +13,10 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
mod deny_word;
|
mod deny_word;
|
||||||
|
mod msg_window;
|
||||||
|
|
||||||
use crate::deny_word::DenyWord;
|
use crate::deny_word::DenyWord;
|
||||||
|
use crate::msg_window::MsgWindow;
|
||||||
use fancy_regex::Regex;
|
use fancy_regex::Regex;
|
||||||
use grok::patterns;
|
use grok::patterns;
|
||||||
use higress_wasm_rust::log::Log;
|
use higress_wasm_rust::log::Log;
|
||||||
@@ -27,8 +29,8 @@ use proxy_wasm::traits::{Context, HttpContext, RootContext};
|
|||||||
use proxy_wasm::types::{Bytes, ContextType, DataAction, HeaderAction, LogLevel};
|
use proxy_wasm::types::{Bytes, ContextType, DataAction, HeaderAction, LogLevel};
|
||||||
use rust_embed::Embed;
|
use rust_embed::Embed;
|
||||||
use serde::de::Error;
|
use serde::de::Error;
|
||||||
use serde::Deserialize;
|
|
||||||
use serde::Deserializer;
|
use serde::Deserializer;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::{json, Value};
|
use serde_json::{json, Value};
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::collections::{BTreeMap, HashMap, VecDeque};
|
use std::collections::{BTreeMap, HashMap, VecDeque};
|
||||||
@@ -66,9 +68,12 @@ struct AiDataMasking {
|
|||||||
config: Option<Rc<AiDataMaskingConfig>>,
|
config: Option<Rc<AiDataMaskingConfig>>,
|
||||||
mask_map: HashMap<String, Option<String>>,
|
mask_map: HashMap<String, Option<String>>,
|
||||||
is_openai: bool,
|
is_openai: bool,
|
||||||
|
is_openai_stream: Option<bool>,
|
||||||
stream: bool,
|
stream: bool,
|
||||||
res_body: Bytes,
|
|
||||||
log: Log,
|
log: Log,
|
||||||
|
msg_window: MsgWindow,
|
||||||
|
char_window_size: usize,
|
||||||
|
byte_window_size: usize,
|
||||||
}
|
}
|
||||||
fn deserialize_regexp<'de, D>(deserializer: D) -> Result<Regex, D::Error>
|
fn deserialize_regexp<'de, D>(deserializer: D) -> Result<Regex, D::Error>
|
||||||
where
|
where
|
||||||
@@ -213,10 +218,33 @@ struct ResMessage {
|
|||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
delta: Option<Message>,
|
delta: Option<Message>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Debug, Deserialize, Serialize, Clone)]
|
||||||
|
struct Usage {
|
||||||
|
completion_tokens: i32,
|
||||||
|
prompt_tokens: i32,
|
||||||
|
total_tokens: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Usage {
|
||||||
|
pub fn add(&mut self, usage: &Usage) {
|
||||||
|
self.completion_tokens += usage.completion_tokens;
|
||||||
|
self.prompt_tokens += usage.prompt_tokens;
|
||||||
|
self.total_tokens += usage.total_tokens;
|
||||||
|
}
|
||||||
|
pub fn reset(&mut self) {
|
||||||
|
self.completion_tokens = 0;
|
||||||
|
self.prompt_tokens = 0;
|
||||||
|
self.total_tokens = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug, Deserialize)]
|
#[derive(Default, Debug, Deserialize)]
|
||||||
struct Res {
|
struct Res {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
choices: Vec<ResMessage>,
|
choices: Vec<ResMessage>,
|
||||||
|
#[serde(default)]
|
||||||
|
usage: Usage,
|
||||||
}
|
}
|
||||||
|
|
||||||
static SYSTEM_PATTERNS: &[(&str, &str)] = &[
|
static SYSTEM_PATTERNS: &[(&str, &str)] = &[
|
||||||
@@ -334,9 +362,12 @@ impl RootContextWrapper<AiDataMaskingConfig> for AiDataMaskingRoot {
|
|||||||
mask_map: HashMap::new(),
|
mask_map: HashMap::new(),
|
||||||
config: None,
|
config: None,
|
||||||
is_openai: false,
|
is_openai: false,
|
||||||
|
is_openai_stream: None,
|
||||||
stream: false,
|
stream: false,
|
||||||
res_body: Bytes::new(),
|
msg_window: MsgWindow::new(),
|
||||||
log: Log::new(PLUGIN_NAME.to_string()),
|
log: Log::new(PLUGIN_NAME.to_string()),
|
||||||
|
char_window_size: 0,
|
||||||
|
byte_window_size: 0,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -416,32 +447,6 @@ impl AiDataMasking {
|
|||||||
DataAction::StopIterationAndBuffer
|
DataAction::StopIterationAndBuffer
|
||||||
}
|
}
|
||||||
|
|
||||||
fn process_sse_message(&mut self, sse_message: &str) -> Vec<String> {
|
|
||||||
let mut messages = Vec::new();
|
|
||||||
for msg in sse_message.split('\n') {
|
|
||||||
if !msg.starts_with("data:") {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let res: Res = if let Some(m) = msg.strip_prefix("data:") {
|
|
||||||
match serde_json::from_str(m) {
|
|
||||||
Ok(r) => r,
|
|
||||||
Err(_) => continue,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
|
|
||||||
if res.choices.is_empty() {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
for choice in &res.choices {
|
|
||||||
if let Some(delta) = &choice.delta {
|
|
||||||
messages.push(delta.content.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
messages
|
|
||||||
}
|
|
||||||
fn replace_request_msg(&mut self, message: &str) -> String {
|
fn replace_request_msg(&mut self, message: &str) -> String {
|
||||||
let config = self.config.as_ref().unwrap();
|
let config = self.config.as_ref().unwrap();
|
||||||
let mut msg = message.to_string();
|
let mut msg = message.to_string();
|
||||||
@@ -464,6 +469,13 @@ impl AiDataMasking {
|
|||||||
}
|
}
|
||||||
Type::Replace => rule.regex.replace(from_word, &rule.value).to_string(),
|
Type::Replace => rule.regex.replace(from_word, &rule.value).to_string(),
|
||||||
};
|
};
|
||||||
|
if to_word.len() > self.byte_window_size {
|
||||||
|
self.byte_window_size = to_word.len();
|
||||||
|
}
|
||||||
|
if to_word.chars().count() > self.char_window_size {
|
||||||
|
self.char_window_size = to_word.chars().count();
|
||||||
|
}
|
||||||
|
|
||||||
replace_pair.push((from_word.to_string(), to_word.clone()));
|
replace_pair.push((from_word.to_string(), to_word.clone()));
|
||||||
|
|
||||||
if rule.restore && !to_word.is_empty() {
|
if rule.restore && !to_word.is_empty() {
|
||||||
@@ -499,6 +511,7 @@ impl HttpContext for AiDataMasking {
|
|||||||
_end_of_stream: bool,
|
_end_of_stream: bool,
|
||||||
) -> HeaderAction {
|
) -> HeaderAction {
|
||||||
if has_request_body() {
|
if has_request_body() {
|
||||||
|
self.set_http_request_header("Content-Length", None);
|
||||||
HeaderAction::StopIteration
|
HeaderAction::StopIteration
|
||||||
} else {
|
} else {
|
||||||
HeaderAction::Continue
|
HeaderAction::Continue
|
||||||
@@ -512,58 +525,41 @@ impl HttpContext for AiDataMasking {
|
|||||||
self.set_http_response_header("Content-Length", None);
|
self.set_http_response_header("Content-Length", None);
|
||||||
HeaderAction::Continue
|
HeaderAction::Continue
|
||||||
}
|
}
|
||||||
fn on_http_response_body(&mut self, body_size: usize, _end_of_stream: bool) -> DataAction {
|
fn on_http_response_body(&mut self, body_size: usize, end_of_stream: bool) -> DataAction {
|
||||||
if !self.stream {
|
if !self.stream {
|
||||||
return DataAction::Continue;
|
return DataAction::Continue;
|
||||||
}
|
}
|
||||||
if let Some(body) = self.get_http_response_body(0, body_size) {
|
if body_size > 0 {
|
||||||
self.res_body.extend(&body);
|
if let Some(body) = self.get_http_response_body(0, body_size) {
|
||||||
|
if self.is_openai && self.is_openai_stream.is_none() {
|
||||||
if let Ok(body_str) = String::from_utf8(self.res_body.clone()) {
|
self.is_openai_stream = Some(body.starts_with(b"data:"));
|
||||||
if self.is_openai {
|
}
|
||||||
let messages = self.process_sse_message(&body_str);
|
self.msg_window.push(&body, self.is_openai_stream.unwrap());
|
||||||
|
if let Ok(mut msg) = String::from_utf8(self.msg_window.message.clone()) {
|
||||||
if self.check_message(&messages.join("")) {
|
if self.check_message(&msg) {
|
||||||
return self.deny(true);
|
return self.deny(true);
|
||||||
}
|
}
|
||||||
} else if self.check_message(&body_str) {
|
if !self.mask_map.is_empty() {
|
||||||
return self.deny(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if self.mask_map.is_empty() {
|
|
||||||
return DataAction::Continue;
|
|
||||||
}
|
|
||||||
if let Ok(body_str) = std::str::from_utf8(&body) {
|
|
||||||
let mut new_str = body_str.to_string();
|
|
||||||
if self.is_openai {
|
|
||||||
let messages = self.process_sse_message(body_str);
|
|
||||||
|
|
||||||
for message in messages {
|
|
||||||
let mut new_message = message.clone();
|
|
||||||
for (from_word, to_word) in self.mask_map.iter() {
|
for (from_word, to_word) in self.mask_map.iter() {
|
||||||
if let Some(to) = to_word {
|
if let Some(to) = to_word {
|
||||||
new_message = new_message.replace(from_word, to);
|
msg = msg.replace(from_word, to);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if new_message != message {
|
|
||||||
new_str = new_str.replace(
|
|
||||||
&json!(message).to_string(),
|
|
||||||
&json!(new_message).to_string(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
self.msg_window.message = msg.as_bytes().to_vec();
|
||||||
for (from_word, to_word) in self.mask_map.iter() {
|
|
||||||
if let Some(to) = to_word {
|
|
||||||
new_str = new_str.replace(from_word, to);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if new_str != body_str {
|
|
||||||
self.replace_http_response_body(new_str.as_bytes());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
let new_body = if end_of_stream {
|
||||||
|
self.msg_window.finish(self.is_openai_stream.unwrap())
|
||||||
|
} else {
|
||||||
|
self.msg_window.pop(
|
||||||
|
self.char_window_size * 2,
|
||||||
|
self.byte_window_size * 2,
|
||||||
|
self.is_openai_stream.unwrap(),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
self.replace_http_response_body(&new_body);
|
||||||
DataAction::Continue
|
DataAction::Continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -586,7 +582,6 @@ impl HttpContextWrapper<AiDataMaskingConfig> for AiDataMasking {
|
|||||||
return DataAction::Continue;
|
return DataAction::Continue;
|
||||||
}
|
}
|
||||||
let config = self.config.as_ref().unwrap();
|
let config = self.config.as_ref().unwrap();
|
||||||
|
|
||||||
let mut req_body = match String::from_utf8(req_body.clone()) {
|
let mut req_body = match String::from_utf8(req_body.clone()) {
|
||||||
Ok(r) => r,
|
Ok(r) => r,
|
||||||
Err(_) => return DataAction::Continue,
|
Err(_) => return DataAction::Continue,
|
||||||
|
|||||||
338
plugins/wasm-rust/extensions/ai-data-masking/src/msg_window.rs
Normal file
338
plugins/wasm-rust/extensions/ai-data-masking/src/msg_window.rs
Normal file
@@ -0,0 +1,338 @@
|
|||||||
|
use higress_wasm_rust::event_stream::EventStream;
|
||||||
|
use serde_json::json;
|
||||||
|
|
||||||
|
use crate::{Res, Usage};
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub(crate) struct MsgWindow {
|
||||||
|
stream_parser: EventStream,
|
||||||
|
pub(crate) message: Vec<u8>,
|
||||||
|
usage: Usage,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MsgWindow {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
MsgWindow::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_event(&mut self, event: Vec<u8>) -> Option<Vec<u8>> {
|
||||||
|
if event.is_empty() || !event.starts_with(b"data:") {
|
||||||
|
Some(event)
|
||||||
|
} else if let Ok(res) = serde_json::from_slice::<Res>(&event[b"data:".len()..]) {
|
||||||
|
for choice in &res.choices {
|
||||||
|
if let Some(delta) = &choice.delta {
|
||||||
|
self.message.extend(delta.content.as_bytes());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.usage.add(&res.usage);
|
||||||
|
None
|
||||||
|
} else if event.starts_with(b"data: [DONE]") {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn push(&mut self, data: &[u8], is_openai: bool) {
|
||||||
|
if is_openai {
|
||||||
|
self.stream_parser.update(data.to_vec());
|
||||||
|
while let Some(event) = self.stream_parser.next() {
|
||||||
|
if let Some(msg) = self.update_event(event) {
|
||||||
|
self.message.extend(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.message.extend(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pop(
|
||||||
|
&mut self,
|
||||||
|
char_window_size: usize,
|
||||||
|
byte_window_size: usize,
|
||||||
|
is_openai: bool,
|
||||||
|
) -> Vec<u8> {
|
||||||
|
if let Ok(message) = String::from_utf8(self.message.clone()) {
|
||||||
|
let chars = message.chars().collect::<Vec<char>>();
|
||||||
|
if chars.len() <= char_window_size {
|
||||||
|
return Vec::new();
|
||||||
|
}
|
||||||
|
let ret = chars[..chars.len() - char_window_size]
|
||||||
|
.iter()
|
||||||
|
.collect::<String>();
|
||||||
|
self.message = chars[chars.len() - char_window_size..]
|
||||||
|
.iter()
|
||||||
|
.collect::<String>()
|
||||||
|
.as_bytes()
|
||||||
|
.to_vec();
|
||||||
|
|
||||||
|
if is_openai {
|
||||||
|
let usage = self.usage.clone();
|
||||||
|
self.usage.reset();
|
||||||
|
format!(
|
||||||
|
"data: {}\n\n",
|
||||||
|
json!({"choices": [{"index": 0, "delta": {"role": "assistant", "content": ret}}], "usage": usage})
|
||||||
|
).as_bytes().to_vec()
|
||||||
|
} else {
|
||||||
|
ret.as_bytes().to_vec()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let ret = self.message[..self.message.len() - byte_window_size].to_vec();
|
||||||
|
self.message = self.message[self.message.len() - byte_window_size..].to_vec();
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn finish(&mut self, is_openai: bool) -> Vec<u8> {
|
||||||
|
if let Some(event) = self.stream_parser.flush() {
|
||||||
|
self.update_event(event);
|
||||||
|
}
|
||||||
|
if self.message.is_empty() {
|
||||||
|
Vec::new()
|
||||||
|
} else if is_openai {
|
||||||
|
format!(
|
||||||
|
"data: {}\n\ndata: [DONE]\n\n",
|
||||||
|
json!({"choices": [{"index": 0, "delta": {"role": "assistant", "content": String::from_utf8_lossy(&self.message)}}], "usage": self.usage})
|
||||||
|
).as_bytes().to_vec()
|
||||||
|
} else {
|
||||||
|
self.message.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_msg() {
|
||||||
|
let mut msg_win = MsgWindow::default();
|
||||||
|
let data = r#"data: {"id":"chatcmpl-936","object":"chat.completion.chunk","created":1739872009,"model":"qwen2.5-coder:32b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"H"},"finish_reason":null}]}
|
||||||
|
|
||||||
|
data: {"id":"chatcmpl-936","object":"chat.completion.chunk","created":1739872009,"model":"qwen2.5-coder:32b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"ig"},"finish_reason":null}]}
|
||||||
|
|
||||||
|
data: {"id":"chatcmpl-936","object":"chat.completion.chunk","created":1739872009,"model":"qwen2.5-coder:32b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"ress"},"finish_reason":null}]}
|
||||||
|
|
||||||
|
data: {"id":"chatcmpl-936","object":"chat.completion.chunk","created":1739872009,"model":"qwen2.5-coder:32b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":" 是"},"finish_reason":null}]}
|
||||||
|
|
||||||
|
data: {"id":"chatcmpl-936","object":"chat.completion.chunk","created":1739872009,"model":"qwen2.5-coder:32b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"一个"},"finish_reason":null}]}
|
||||||
|
|
||||||
|
data: {"id":"chatcmpl-936","object":"chat.completion.chunk","created":1739872009,"model":"qwen2.5-coder:32b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"基于"},"finish_reason":null}]}
|
||||||
|
|
||||||
|
data: {"id":"chatcmpl-936","object":"chat.completion.chunk","created":1739872009,"model":"qwen2.5-coder:32b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":" Ist"},"finish_reason":null}]}
|
||||||
|
|
||||||
|
data: {"id":"chatcmpl-936","object":"chat.completion.chunk","created":1739872009,"model":"qwen2.5-coder:32b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"io"},"finish_reason":null}]}
|
||||||
|
|
||||||
|
data: {"id":"chatcmpl-936","object":"chat.completion.chunk","created":1739872009,"model":"qwen2.5-coder:32b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":" 的"},"finish_reason":null}]}
|
||||||
|
|
||||||
|
data: {"id":"chatcmpl-936","object":"chat.completion.chunk","created":1739872009,"model":"qwen2.5-coder:32b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"高性能"},"finish_reason":null}]}
|
||||||
|
|
||||||
|
data: {"id":"chatcmpl-936","object":"chat.completion.chunk","created":1739872009,"model":"qwen2.5-coder:32b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"服务"},"finish_reason":null}]}
|
||||||
|
|
||||||
|
data: {"id":"chatcmpl-936","object":"chat.completion.chunk","created":1739872009,"model":"qwen2.5-coder:32b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"网格"},"finish_reason":null}]}
|
||||||
|
|
||||||
|
data: {"id":"chatcmpl-936","object":"chat.completion.chunk","created":1739872009,"model":"qwen2.5-coder:32b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"数据"},"finish_reason":null}]}
|
||||||
|
|
||||||
|
data: {"id":"chatcmpl-936","object":"chat.completion.chunk","created":1739872009,"model":"qwen2.5-coder:32b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"平面"},"finish_reason":null}]}
|
||||||
|
|
||||||
|
data: {"id":"chatcmpl-936","object":"chat.completion.chunk","created":1739872009,"model":"qwen2.5-coder:32b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"项目"},"finish_reason":null}]}
|
||||||
|
|
||||||
|
data: {"id":"chatcmpl-936","object":"chat.completion.chunk","created":1739872009,"model":"qwen2.5-coder:32b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":","},"finish_reason":null}]}
|
||||||
|
|
||||||
|
data: {"id":"chatcmpl-936","object":"chat.completion.chunk","created":1739872009,"model":"qwen2.5-coder:32b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"旨在"},"finish_reason":null}]}
|
||||||
|
|
||||||
|
data: {"id":"chatcmpl-936","object":"chat.completion.chunk","created":1739872009,"model":"qwen2.5-coder:32b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"提供"},"finish_reason":null}]}
|
||||||
|
|
||||||
|
data: {"id":"chatcmpl-936","object":"chat.completion.chunk","created":1739872009,"model":"qwen2.5-coder:32b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"高"},"finish_reason":null}]}
|
||||||
|
|
||||||
|
data: {"id":"chatcmpl-936","object":"chat.completion.chunk","created":1739872009,"model":"qwen2.5-coder:32b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"吞"},"finish_reason":null}]}
|
||||||
|
|
||||||
|
data: {"id":"chatcmpl-936","object":"chat.completion.chunk","created":1739872010,"model":"qwen2.5-coder:32b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"吐"},"finish_reason":null}]}
|
||||||
|
|
||||||
|
data: {"id":"chatcmpl-936","object":"chat.completion.chunk","created":1739872010,"model":"qwen2.5-coder:32b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"量"},"finish_reason":null}]}
|
||||||
|
|
||||||
|
data: {"id":"chatcmpl-936","object":"chat.completion.chunk","created":1739872010,"model":"qwen2.5-coder:32b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"、"},"finish_reason":null}]}
|
||||||
|
|
||||||
|
data: {"id":"chatcmpl-936","object":"chat.completion.chunk","created":1739872010,"model":"qwen2.5-coder:32b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"低"},"finish_reason":null}]}
|
||||||
|
|
||||||
|
data: {"id":"chatcmpl-936","object":"chat.completion.chunk","created":1739872010,"model":"qwen2.5-coder:32b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"延迟"},"finish_reason":null}]}
|
||||||
|
|
||||||
|
data: {"id":"chatcmpl-936","object":"chat.completion.chunk","created":1739872010,"model":"qwen2.5-coder:32b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"和"},"finish_reason":null}]}
|
||||||
|
|
||||||
|
data: {"id":"chatcmpl-936","object":"chat.completion.chunk","created":1739872010,"model":"qwen2.5-coder:32b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"可"},"finish_reason":null}]}
|
||||||
|
|
||||||
|
data: {"id":"chatcmpl-936","object":"chat.completion.chunk","created":1739872010,"model":"qwen2.5-coder:32b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"扩展"},"finish_reason":null}]}
|
||||||
|
|
||||||
|
data: {"id":"chatcmpl-936","object":"chat.completion.chunk","created":1739872010,"model":"qwen2.5-coder:32b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"的服务"},"finish_reason":null}]}
|
||||||
|
|
||||||
|
data: {"id":"chatcmpl-936","object":"chat.completion.chunk","created":1739872010,"model":"qwen2.5-coder:32b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"通信"},"finish_reason":null}]}
|
||||||
|
|
||||||
|
data: {"id":"chatcmpl-936","object":"chat.completion.chunk","created":1739872010,"model":"qwen2.5-coder:32b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"管理"},"finish_reason":null}]}
|
||||||
|
|
||||||
|
data: {"id":"chatcmpl-936","object":"chat.completion.chunk","created":1739872010,"model":"qwen2.5-coder:32b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"。"},"finish_reason":null}]}
|
||||||
|
|
||||||
|
data: {"id":"chatcmpl-936","object":"chat.completion.chunk","created":1739872010,"model":"qwen2.5-coder:32b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"它"},"finish_reason":null}]}
|
||||||
|
|
||||||
|
data: {"id":"chatcmpl-936","object":"chat.completion.chunk","created":1739872010,"model":"qwen2.5-coder:32b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"为企业"},"finish_reason":null}]}
|
||||||
|
|
||||||
|
data: {"id":"chatcmpl-936","object":"chat.completion.chunk","created":1739872010,"model":"qwen2.5-coder:32b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"级"},"finish_reason":null}]}
|
||||||
|
|
||||||
|
data: {"id":"chatcmpl-936","object":"chat.completion.chunk","created":1739872010,"model":"qwen2.5-coder:32b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"应用"},"finish_reason":null}]}
|
||||||
|
|
||||||
|
data: {"id":"chatcmpl-936","object":"chat.completion.chunk","created":1739872010,"model":"qwen2.5-coder:32b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"提供了"},"finish_reason":null}]}
|
||||||
|
|
||||||
|
data: {"id":"chatcmpl-936","object":"chat.completion.chunk","created":1739872010,"model":"qwen2.5-coder:32b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"丰富的"},"finish_reason":null}]}
|
||||||
|
|
||||||
|
data: {"id":"chatcmpl-936","object":"chat.completion.chunk","created":1739872010,"model":"qwen2.5-coder:32b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"流量"},"finish_reason":null}]}
|
||||||
|
|
||||||
|
data: {"id":"chatcmpl-936","object":"chat.completion.chunk","created":1739872010,"model":"qwen2.5-coder:32b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"治理"},"finish_reason":null}]}
|
||||||
|
|
||||||
|
data: {"id":"chatcmpl-936","object":"chat.completion.chunk","created":1739872010,"model":"qwen2.5-coder:32b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"功能"},"finish_reason":null}]}
|
||||||
|
|
||||||
|
data: {"id":"chatcmpl-936","object":"chat.completion.chunk","created":1739872010,"model":"qwen2.5-coder:32b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":","},"finish_reason":null}]}
|
||||||
|
|
||||||
|
data: {"id":"chatcmpl-936","object":"chat.completion.chunk","created":1739872010,"model":"qwen2.5-coder:32b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"如"},"finish_reason":null}]}
|
||||||
|
|
||||||
|
data: {"id":"chatcmpl-936","object":"chat.completion.chunk","created":1739872010,"model":"qwen2.5-coder:32b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"负载"},"finish_reason":null}]}
|
||||||
|
|
||||||
|
data: {"id":"chatcmpl-936","object":"chat.completion.chunk","created":1739872010,"model":"qwen2.5-coder:32b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"均衡"},"finish_reason":null}]}
|
||||||
|
|
||||||
|
data: {"id":"chatcmpl-936","object":"chat.completion.chunk","created":1739872010,"model":"qwen2.5-coder:32b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"、"},"finish_reason":null}]}
|
||||||
|
|
||||||
|
data: {"id":"chatcmpl-936","object":"chat.completion.chunk","created":1739872010,"model":"qwen2.5-coder:32b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"熔"},"finish_reason":null}]}
|
||||||
|
|
||||||
|
data: {"id":"chatcmpl-936","object":"chat.completion.chunk","created":1739872010,"model":"qwen2.5-coder:32b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"断"},"finish_reason":null}]}
|
||||||
|
|
||||||
|
data: {"id":"chatcmpl-936","object":"chat.completion.chunk","created":1739872010,"model":"qwen2.5-coder:32b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"、"},"finish_reason":null}]}
|
||||||
|
|
||||||
|
data: {"id":"chatcmpl-936","object":"chat.completion.chunk","created":1739872010,"model":"qwen2.5-coder:32b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"限"},"finish_reason":null}]}
|
||||||
|
|
||||||
|
data: {"id":"chatcmpl-936","object":"chat.completion.chunk","created":1739872010,"model":"qwen2.5-coder:32b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"流"},"finish_reason":null}]}
|
||||||
|
|
||||||
|
data: {"id":"chatcmpl-936","object":"chat.completion.chunk","created":1739872010,"model":"qwen2.5-coder:32b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"等"},"finish_reason":null}]}
|
||||||
|
|
||||||
|
data: {"id":"chatcmpl-936","object":"chat.completion.chunk","created":1739872011,"model":"qwen2.5-coder:32b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":",并"},"finish_reason":null}]}
|
||||||
|
|
||||||
|
data: {"id":"chatcmpl-936","object":"chat.completion.chunk","created":1739872011,"model":"qwen2.5-coder:32b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"支持"},"finish_reason":null}]}
|
||||||
|
|
||||||
|
data: {"id":"chatcmpl-936","object":"chat.completion.chunk","created":1739872011,"model":"qwen2.5-coder:32b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"多"},"finish_reason":null}]}
|
||||||
|
|
||||||
|
data: {"id":"chatcmpl-936","object":"chat.completion.chunk","created":1739872011,"model":"qwen2.5-coder:32b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"协议"},"finish_reason":null}]}
|
||||||
|
|
||||||
|
data: {"id":"chatcmpl-936","object":"chat.completion.chunk","created":1739872011,"model":"qwen2.5-coder:32b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"代理"},"finish_reason":null}]}
|
||||||
|
|
||||||
|
data: {"id":"chatcmpl-936","object":"chat.completion.chunk","created":1739872011,"model":"qwen2.5-coder:32b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"("},"finish_reason":null}]}
|
||||||
|
|
||||||
|
data: {"id":"chatcmpl-936","object":"chat.completion.chunk","created":1739872011,"model":"qwen2.5-coder:32b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"包括"},"finish_reason":null}]}
|
||||||
|
|
||||||
|
data: {"id":"chatcmpl-936","object":"chat.completion.chunk","created":1739872011,"model":"qwen2.5-coder:32b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":" HTTP"},"finish_reason":null}]}
|
||||||
|
|
||||||
|
data: {"id":"chatcmpl-936","object":"chat.completion.chunk","created":1739872011,"model":"qwen2.5-coder:32b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"/"},"finish_reason":null}]}
|
||||||
|
|
||||||
|
data: {"id":"chatcmpl-936","object":"chat.completion.chunk","created":1739872011,"model":"qwen2.5-coder:32b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"1"},"finish_reason":null}]}
|
||||||
|
|
||||||
|
data: {"id":"chatcmpl-936","object":"chat.completion.chunk","created":1739872011,"model":"qwen2.5-coder:32b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"."},"finish_reason":null}]}
|
||||||
|
|
||||||
|
data: {"id":"chatcmpl-936","object":"chat.completion.chunk","created":1739872011,"model":"qwen2.5-coder:32b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"1"},"finish_reason":null}]}
|
||||||
|
|
||||||
|
data: {"id":"chatcmpl-936","object":"chat.completion.chunk","created":1739872011,"model":"qwen2.5-coder:32b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":","},"finish_reason":null}]}
|
||||||
|
|
||||||
|
data: {"id":"chatcmpl-936","object":"chat.completion.chunk","created":1739872011,"model":"qwen2.5-coder:32b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":" HTTP"},"finish_reason":null}]}
|
||||||
|
|
||||||
|
data: {"id":"chatcmpl-936","object":"chat.completion.chunk","created":1739872011,"model":"qwen2.5-coder:32b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"/"},"finish_reason":null}]}
|
||||||
|
|
||||||
|
data: {"id":"chatcmpl-936","object":"chat.completion.chunk","created":1739872011,"model":"qwen2.5-coder:32b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"2"},"finish_reason":null}]}
|
||||||
|
|
||||||
|
data: {"id":"chatcmpl-936","object":"chat.completion.chunk","created":1739872011,"model":"qwen2.5-coder:32b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":","},"finish_reason":null}]}
|
||||||
|
|
||||||
|
data: {"id":"chatcmpl-936","object":"chat.completion.chunk","created":1739872011,"model":"qwen2.5-coder:32b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":" g"},"finish_reason":null}]}
|
||||||
|
|
||||||
|
data: {"id":"chatcmpl-936","object":"chat.completion.chunk","created":1739872011,"model":"qwen2.5-coder:32b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"RPC"},"finish_reason":null}]}
|
||||||
|
|
||||||
|
data: {"id":"chatcmpl-936","object":"chat.completion.chunk","created":1739872011,"model":"qwen2.5-coder:32b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":")。"},"finish_reason":null}]}
|
||||||
|
|
||||||
|
data: {"id":"chatcmpl-936","object":"chat.completion.chunk","created":1739872011,"model":"qwen2.5-coder:32b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"H"},"finish_reason":null}]}
|
||||||
|
|
||||||
|
data: {"id":"chatcmpl-936","object":"chat.completion.chunk","created":1739872011,"model":"qwen2.5-coder:32b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"ig"},"finish_reason":null}]}
|
||||||
|
|
||||||
|
data: {"id":"chatcmpl-936","object":"chat.completion.chunk","created":1739872011,"model":"qwen2.5-coder:32b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"ress"},"finish_reason":null}]}
|
||||||
|
|
||||||
|
data: {"id":"chatcmpl-936","object":"chat.completion.chunk","created":1739872011,"model":"qwen2.5-coder:32b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":" 的"},"finish_reason":null}]}
|
||||||
|
|
||||||
|
data: {"id":"chatcmpl-936","object":"chat.completion.chunk","created":1739872011,"model":"qwen2.5-coder:32b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"设计"},"finish_reason":null}]}
|
||||||
|
|
||||||
|
data: {"id":"chatcmpl-936","object":"chat.completion.chunk","created":1739872011,"model":"qwen2.5-coder:32b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"目标"},"finish_reason":null}]}
|
||||||
|
|
||||||
|
data: {"id":"chatcmpl-936","object":"chat.completion.chunk","created":1739872011,"model":"qwen2.5-coder:32b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"是"},"finish_reason":null}]}
|
||||||
|
|
||||||
|
data: {"id":"chatcmpl-936","object":"chat.completion.chunk","created":1739872011,"model":"qwen2.5-coder:32b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"优化"},"finish_reason":null}]}
|
||||||
|
|
||||||
|
data: {"id":"chatcmpl-936","object":"chat.completion.chunk","created":1739872011,"model":"qwen2.5-coder:32b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":" Ist"},"finish_reason":null}]}
|
||||||
|
|
||||||
|
data: {"id":"chatcmpl-936","object":"chat.completion.chunk","created":1739872011,"model":"qwen2.5-coder:32b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"io"},"finish_reason":null}]}
|
||||||
|
|
||||||
|
data: {"id":"chatcmpl-936","object":"chat.completion.chunk","created":1739872011,"model":"qwen2.5-coder:32b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":" 在"},"finish_reason":null}]}
|
||||||
|
|
||||||
|
data: {"id":"chatcmpl-936","object":"chat.completion.chunk","created":1739872011,"model":"qwen2.5-coder:32b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"大规模"},"finish_reason":null}]}
|
||||||
|
|
||||||
|
data: {"id":"chatcmpl-936","object":"chat.completion.chunk","created":1739872011,"model":"qwen2.5-coder:32b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"集群"},"finish_reason":null}]}
|
||||||
|
|
||||||
|
data: {"id":"chatcmpl-936","object":"chat.completion.chunk","created":1739872011,"model":"qwen2.5-coder:32b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"中的"},"finish_reason":null}]}
|
||||||
|
|
||||||
|
data: {"id":"chatcmpl-936","object":"chat.completion.chunk","created":1739872012,"model":"qwen2.5-coder:32b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"性能"},"finish_reason":null}]}
|
||||||
|
|
||||||
|
data: {"id":"chatcmpl-936","object":"chat.completion.chunk","created":1739872012,"model":"qwen2.5-coder:32b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"表现"},"finish_reason":null}]}
|
||||||
|
|
||||||
|
data: {"id":"chatcmpl-936","object":"chat.completion.chunk","created":1739872012,"model":"qwen2.5-coder:32b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":","},"finish_reason":null}]}
|
||||||
|
|
||||||
|
data: {"id":"chatcmpl-936","object":"chat.completion.chunk","created":1739872012,"model":"qwen2.5-coder:32b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"满足"},"finish_reason":null}]}
|
||||||
|
|
||||||
|
data: {"id":"chatcmpl-936","object":"chat.completion.chunk","created":1739872012,"model":"qwen2.5-coder:32b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"高"},"finish_reason":null}]}
|
||||||
|
|
||||||
|
data: {"id":"chatcmpl-936","object":"chat.completion.chunk","created":1739872012,"model":"qwen2.5-coder:32b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"并发"},"finish_reason":null}]}
|
||||||
|
|
||||||
|
data: {"id":"chatcmpl-936","object":"chat.completion.chunk","created":1739872012,"model":"qwen2.5-coder:32b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"场景"},"finish_reason":null}]}
|
||||||
|
|
||||||
|
data: {"id":"chatcmpl-936","object":"chat.completion.chunk","created":1739872012,"model":"qwen2.5-coder:32b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"下的"},"finish_reason":null}]}
|
||||||
|
|
||||||
|
data: {"id":"chatcmpl-936","object":"chat.completion.chunk","created":1739872012,"model":"qwen2.5-coder:32b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"需求"},"finish_reason":null}]}
|
||||||
|
|
||||||
|
data: {"id":"chatcmpl-936","object":"chat.completion.chunk","created":1739872012,"model":"qwen2.5-coder:32b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"。"},"finish_reason":null}]}
|
||||||
|
|
||||||
|
data: {"id":"chatcmpl-936","object":"chat.completion.chunk","created":1739872012,"model":"qwen2.5-coder:32b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":"stop"}]}
|
||||||
|
|
||||||
|
data: [DONE]
|
||||||
|
|
||||||
|
"#;
|
||||||
|
let mut buffer = Vec::new();
|
||||||
|
for line in data.split("\n\n") {
|
||||||
|
msg_win.push(line.as_bytes(), true);
|
||||||
|
msg_win.push(b"\n\n", true);
|
||||||
|
if let Ok(mut msg) = String::from_utf8(msg_win.message.clone()) {
|
||||||
|
msg = msg.replace("Higress", "***higress***");
|
||||||
|
msg_win.message = msg.as_bytes().to_vec();
|
||||||
|
}
|
||||||
|
buffer.extend(msg_win.pop(7, 7, true));
|
||||||
|
}
|
||||||
|
buffer.extend(msg_win.finish(true));
|
||||||
|
let mut message = String::new();
|
||||||
|
for line in buffer.split(|&x| x == b'\n') {
|
||||||
|
if line.is_empty() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
assert!(line.starts_with(b"data:"));
|
||||||
|
if line.starts_with(b"data: [DONE]") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let des = serde_json::from_slice(&line[b"data:".len()..]);
|
||||||
|
assert!(des.is_ok());
|
||||||
|
let res: Res = des.unwrap();
|
||||||
|
for choice in &res.choices {
|
||||||
|
if let Some(delta) = &choice.delta {
|
||||||
|
message.push_str(&delta.content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert_eq!(message, "***higress*** 是一个基于 Istio 的高性能服务网格数据平面项目,旨在提供高吞吐量、低延迟和可扩展的服务通信管理。它为企业级应用提供了丰富的流量治理功能,如负载均衡、熔断、限流等,并支持多协议代理(包括 HTTP/1.1, HTTP/2, gRPC)。***higress*** 的设计目标是优化 Istio 在大规模集群中的性能表现,满足高并发场景下的需求。");
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user