mirror of
https://github.com/alibaba/higress.git
synced 2026-03-19 17:57:31 +08:00
Feat: Ai data masking msg window support reasoning_content in response and n in request (#2404)
This commit is contained in:
@@ -8,7 +8,8 @@ COPY . .
|
||||
WORKDIR /workspace/extensions/$PLUGIN_NAME
|
||||
RUN if [ -f $PREBUILD ]; then sh $PREBUILD; fi
|
||||
RUN cargo build --target wasm32-wasip1 $BUILD_OPTS \
|
||||
&& cp target/wasm32-wasip1/release/*.wasm /main.wasm
|
||||
&& cp target/wasm32-wasip1/release/*.wasm /main.wasm \
|
||||
&& cargo clean
|
||||
|
||||
FROM scratch
|
||||
COPY --from=builder /main.wasm plugin.wasm
|
||||
|
||||
@@ -23,15 +23,19 @@ build:
|
||||
lint-base:
|
||||
cargo fmt --all --check
|
||||
cargo clippy --workspace --all-features --all-targets
|
||||
cargo clean
|
||||
lint:
|
||||
cargo fmt --all --check --manifest-path extensions/${PLUGIN_NAME}/Cargo.toml
|
||||
cargo clippy --workspace --all-features --all-targets --manifest-path extensions/${PLUGIN_NAME}/Cargo.toml
|
||||
cargo clean --manifest-path extensions/${PLUGIN_NAME}/Cargo.toml
|
||||
|
||||
test-base:
|
||||
cargo test --lib
|
||||
cargo clean
|
||||
|
||||
test:
|
||||
cargo test --manifest-path extensions/${PLUGIN_NAME}/Cargo.toml
|
||||
cargo clean --manifest-path extensions/${PLUGIN_NAME}/Cargo.toml
|
||||
|
||||
builder:
|
||||
DOCKER_BUILDKIT=1 docker build \
|
||||
|
||||
@@ -36,10 +36,10 @@ dependencies = [
|
||||
"fancy-regex",
|
||||
"grok",
|
||||
"higress-wasm-rust",
|
||||
"hmac-sha256",
|
||||
"jieba-rs",
|
||||
"jsonpath-rust",
|
||||
"lazy_static",
|
||||
"md5",
|
||||
"proxy-wasm",
|
||||
"rust-embed",
|
||||
"serde",
|
||||
@@ -295,6 +295,12 @@ dependencies = [
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hmac-sha256"
|
||||
version = "1.1.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ad6880c8d4a9ebf39c6e8b77007ce223f646a4d21ce29d99f70cb16420545425"
|
||||
|
||||
[[package]]
|
||||
name = "http"
|
||||
version = "1.2.0"
|
||||
@@ -559,12 +565,6 @@ version = "0.4.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f"
|
||||
|
||||
[[package]]
|
||||
name = "md5"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.4"
|
||||
|
||||
@@ -14,7 +14,7 @@ proxy-wasm = { git="https://github.com/higress-group/proxy-wasm-rust-sdk", branc
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
fancy-regex = "0"
|
||||
md5 = "0"
|
||||
hmac-sha256 = "1"
|
||||
grok = "2"
|
||||
lazy_static = "1"
|
||||
jieba-rs = "0"
|
||||
|
||||
@@ -0,0 +1,736 @@
|
||||
// Copyright (c) 2025 Alibaba Group Holding Ltd.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use crate::deny_word::DenyWord;
|
||||
use crate::msg_win_openai::MsgWindow;
|
||||
use fancy_regex::Regex;
|
||||
use grok::patterns;
|
||||
use higress_wasm_rust::log::Log;
|
||||
use higress_wasm_rust::plugin_wrapper::{HttpContextWrapper, RootContextWrapper};
|
||||
use higress_wasm_rust::request_wrapper::has_request_body;
|
||||
use higress_wasm_rust::rule_matcher::{on_configure, RuleMatcher, SharedRuleMatcher};
|
||||
use jsonpath_rust::{JsonPath, JsonPathValue};
|
||||
use lazy_static::lazy_static;
|
||||
use proxy_wasm::traits::{Context, HttpContext, RootContext};
|
||||
use proxy_wasm::types::{Bytes, ContextType, DataAction, HeaderAction, LogLevel};
|
||||
use serde::de::Error;
|
||||
use serde::Deserialize;
|
||||
use serde::Deserializer;
|
||||
use serde_json::{json, Value};
|
||||
use std::cell::RefCell;
|
||||
use std::collections::{BTreeMap, HashMap, VecDeque};
|
||||
use std::fmt::Write;
|
||||
use std::ops::DerefMut;
|
||||
use std::rc::{Rc, Weak};
|
||||
use std::str::FromStr;
|
||||
use std::vec;
|
||||
|
||||
proxy_wasm::main! {{
|
||||
proxy_wasm::set_log_level(LogLevel::Trace);
|
||||
proxy_wasm::set_root_context(|_|Box::new(AiDataMaskingRoot::new()));
|
||||
}}
|
||||
|
||||
const PLUGIN_NAME: &str = "ai-data-masking";
|
||||
const GROK_PATTERN: &str = r"%\{(?<name>(?<pattern>[A-z0-9]+)(?::(?<alias>[A-z0-9_:;\/\s\.]+))?)\}";
|
||||
|
||||
struct System {
|
||||
deny_word: DenyWord,
|
||||
grok_regex: Regex,
|
||||
grok_patterns: BTreeMap<String, String>,
|
||||
}
|
||||
lazy_static! {
|
||||
static ref SYSTEM: System = System::new();
|
||||
}
|
||||
|
||||
struct AiDataMaskingRoot {
|
||||
log: Log,
|
||||
rule_matcher: SharedRuleMatcher<AiDataMaskingConfig>,
|
||||
}
|
||||
struct AiDataMasking {
|
||||
weak: Weak<RefCell<Box<dyn HttpContextWrapper<AiDataMaskingConfig>>>>,
|
||||
config: Option<Rc<AiDataMaskingConfig>>,
|
||||
mask_map: HashMap<String, Option<String>>,
|
||||
is_openai: bool,
|
||||
is_openai_stream: Option<bool>,
|
||||
stream: bool,
|
||||
log: Log,
|
||||
msg_window: MsgWindow,
|
||||
char_window_size: usize,
|
||||
byte_window_size: usize,
|
||||
}
|
||||
fn deserialize_regexp<'de, D>(deserializer: D) -> Result<Regex, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let value: Value = Deserialize::deserialize(deserializer)?;
|
||||
if let Some(pattern) = value.as_str() {
|
||||
let (p, _) = SYSTEM.grok_to_pattern(pattern);
|
||||
if let Ok(reg) = Regex::new(&p) {
|
||||
Ok(reg)
|
||||
} else if let Ok(reg) = Regex::new(pattern) {
|
||||
Ok(reg)
|
||||
} else {
|
||||
Err(Error::custom(format!("regexp error field {}", pattern)))
|
||||
}
|
||||
} else {
|
||||
Err(Error::custom("regexp error not string".to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
fn deserialize_type<'de, D>(deserializer: D) -> Result<Type, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let value: Value = Deserialize::deserialize(deserializer)?;
|
||||
if let Some(t) = value.as_str() {
|
||||
if t == "replace" {
|
||||
Ok(Type::Replace)
|
||||
} else if t == "hash" {
|
||||
Ok(Type::Hash)
|
||||
} else {
|
||||
Err(Error::custom(format!("regexp error value {}", t)))
|
||||
}
|
||||
} else {
|
||||
Err(Error::custom("type error not string".to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
fn deserialize_denyword<'de, D>(deserializer: D) -> Result<DenyWord, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let value: Vec<String> = Deserialize::deserialize(deserializer)?;
|
||||
Ok(DenyWord::from_iter(value))
|
||||
}
|
||||
|
||||
fn deserialize_jsonpath<'de, D>(deserializer: D) -> Result<Vec<JsonPath>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let value: Vec<String> = Deserialize::deserialize(deserializer)?;
|
||||
let mut ret = Vec::new();
|
||||
for v in value {
|
||||
if v.is_empty() {
|
||||
continue;
|
||||
}
|
||||
match JsonPath::from_str(&v) {
|
||||
Ok(jp) => ret.push(jp),
|
||||
Err(_) => return Err(Error::custom(format!("jsonpath error value {}", v))),
|
||||
}
|
||||
}
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
enum Type {
|
||||
Replace,
|
||||
Hash,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
struct Rule {
|
||||
#[serde(deserialize_with = "deserialize_regexp")]
|
||||
regex: Regex,
|
||||
#[serde(deserialize_with = "deserialize_type", alias = "type")]
|
||||
type_: Type,
|
||||
#[serde(default)]
|
||||
restore: bool,
|
||||
#[serde(default)]
|
||||
value: String,
|
||||
}
|
||||
fn default_deny_openai() -> bool {
|
||||
true
|
||||
}
|
||||
fn default_deny_raw() -> bool {
|
||||
false
|
||||
}
|
||||
fn default_system_deny() -> bool {
|
||||
false
|
||||
}
|
||||
fn default_deny_code() -> u16 {
|
||||
200
|
||||
}
|
||||
fn default_deny_content_type() -> String {
|
||||
"application/json".to_string()
|
||||
}
|
||||
fn default_deny_raw_message() -> String {
|
||||
"{\"errmsg\":\"提问或回答中包含敏感词,已被屏蔽\"}".to_string()
|
||||
}
|
||||
fn default_deny_message() -> String {
|
||||
"提问或回答中包含敏感词,已被屏蔽".to_string()
|
||||
}
|
||||
#[derive(Default, Debug, Deserialize, Clone)]
|
||||
pub struct AiDataMaskingConfig {
|
||||
#[serde(default = "default_deny_openai")]
|
||||
deny_openai: bool,
|
||||
#[serde(default = "default_deny_raw")]
|
||||
deny_raw: bool,
|
||||
#[serde(default, deserialize_with = "deserialize_jsonpath")]
|
||||
deny_jsonpath: Vec<JsonPath>,
|
||||
#[serde(default = "default_system_deny")]
|
||||
system_deny: bool,
|
||||
#[serde(default = "default_deny_code")]
|
||||
deny_code: u16,
|
||||
#[serde(default = "default_deny_message")]
|
||||
deny_message: String,
|
||||
#[serde(default = "default_deny_raw_message")]
|
||||
deny_raw_message: String,
|
||||
#[serde(default = "default_deny_content_type")]
|
||||
deny_content_type: String,
|
||||
#[serde(default)]
|
||||
replace_roles: Vec<Rule>,
|
||||
#[serde(deserialize_with = "deserialize_denyword", default = "DenyWord::empty")]
|
||||
deny_words: DenyWord,
|
||||
}
|
||||
|
||||
impl AiDataMaskingConfig {
|
||||
fn check_message(&self, message: &str, log: &Log) -> bool {
|
||||
if let Some(word) = self.deny_words.check(message) {
|
||||
log.warn(&format!(
|
||||
"custom deny word {} matched from {}",
|
||||
word, message
|
||||
));
|
||||
return true;
|
||||
} else if self.system_deny {
|
||||
if let Some(word) = SYSTEM.deny_word.check(message) {
|
||||
log.warn(&format!(
|
||||
"system deny word {} matched from {}",
|
||||
word, message
|
||||
));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
struct Message {
|
||||
#[serde(default)]
|
||||
content: String,
|
||||
#[serde(default)]
|
||||
reasoning_content: String,
|
||||
}
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
struct Req {
|
||||
#[serde(default)]
|
||||
stream: bool,
|
||||
messages: Vec<Message>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Deserialize)]
|
||||
struct ResMessage {
|
||||
#[serde(default)]
|
||||
message: Option<Message>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Deserialize)]
|
||||
struct Res {
|
||||
#[serde(default)]
|
||||
choices: Vec<ResMessage>,
|
||||
}
|
||||
|
||||
static SYSTEM_PATTERNS: &[(&str, &str)] = &[
|
||||
("MOBILE", r#"\d{8,11}"#),
|
||||
("IDCARD", r#"\d{17}[0-9xX]|\d{15}"#),
|
||||
];
|
||||
|
||||
impl System {
|
||||
fn new() -> Self {
|
||||
let grok_regex = Regex::new(GROK_PATTERN).unwrap();
|
||||
let grok_patterns = BTreeMap::new();
|
||||
let mut system = System {
|
||||
deny_word: DenyWord::system(),
|
||||
grok_regex,
|
||||
grok_patterns,
|
||||
};
|
||||
system.init();
|
||||
system
|
||||
}
|
||||
fn init(&mut self) {
|
||||
let mut grok_temp_patterns = VecDeque::new();
|
||||
for patterns in [patterns(), SYSTEM_PATTERNS] {
|
||||
for &(key, value) in patterns {
|
||||
if self.grok_regex.is_match(value).is_ok_and(|r| r) {
|
||||
grok_temp_patterns.push_back((String::from(key), String::from(value)));
|
||||
} else {
|
||||
self.grok_patterns
|
||||
.insert(String::from(key), String::from(value));
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut last_ok: Option<String> = None;
|
||||
|
||||
while let Some((key, value)) = grok_temp_patterns.pop_front() {
|
||||
if let Some(k) = &last_ok {
|
||||
if k == &key {
|
||||
break;
|
||||
}
|
||||
}
|
||||
let (v, ok) = self.grok_to_pattern(&value);
|
||||
if ok {
|
||||
self.grok_patterns.insert(key, v);
|
||||
last_ok = None;
|
||||
} else {
|
||||
if last_ok.is_none() {
|
||||
last_ok = Some(key.clone());
|
||||
}
|
||||
grok_temp_patterns.push_back((key, v));
|
||||
}
|
||||
}
|
||||
}
|
||||
fn grok_to_pattern(&self, pattern: &str) -> (String, bool) {
|
||||
let mut ok = true;
|
||||
let mut ret = pattern.to_string();
|
||||
for capture in self.grok_regex.captures_iter(pattern) {
|
||||
if capture.is_err() {
|
||||
ok = false;
|
||||
continue;
|
||||
}
|
||||
let c = capture.unwrap();
|
||||
if let (Some(full), Some(name)) = (c.get(0), c.name("pattern")) {
|
||||
if let Some(p) = self.grok_patterns.get(name.as_str()) {
|
||||
if let Some(alias) = c.name("alias") {
|
||||
ret = ret.replace(full.as_str(), &format!("(?P<{}>{})", alias.as_str(), p));
|
||||
} else {
|
||||
ret = ret.replace(full.as_str(), p);
|
||||
}
|
||||
} else {
|
||||
ok = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
(ret, ok)
|
||||
}
|
||||
}
|
||||
|
||||
impl AiDataMaskingRoot {
|
||||
fn new() -> Self {
|
||||
AiDataMaskingRoot {
|
||||
log: Log::new(PLUGIN_NAME.to_string()),
|
||||
rule_matcher: Rc::new(RefCell::new(RuleMatcher::default())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Context for AiDataMaskingRoot {}
|
||||
|
||||
impl RootContext for AiDataMaskingRoot {
|
||||
fn on_configure(&mut self, plugin_configuration_size: usize) -> bool {
|
||||
on_configure(
|
||||
self,
|
||||
plugin_configuration_size,
|
||||
self.rule_matcher.borrow_mut().deref_mut(),
|
||||
&self.log,
|
||||
)
|
||||
}
|
||||
fn create_http_context(&self, context_id: u32) -> Option<Box<dyn HttpContext>> {
|
||||
self.create_http_context_use_wrapper(context_id)
|
||||
}
|
||||
fn get_type(&self) -> Option<ContextType> {
|
||||
Some(ContextType::HttpContext)
|
||||
}
|
||||
}
|
||||
|
||||
impl RootContextWrapper<AiDataMaskingConfig> for AiDataMaskingRoot {
|
||||
fn rule_matcher(&self) -> &SharedRuleMatcher<AiDataMaskingConfig> {
|
||||
&self.rule_matcher
|
||||
}
|
||||
|
||||
fn create_http_context_wrapper(
|
||||
&self,
|
||||
_context_id: u32,
|
||||
) -> Option<Box<dyn HttpContextWrapper<AiDataMaskingConfig>>> {
|
||||
Some(Box::new(AiDataMasking {
|
||||
weak: Weak::default(),
|
||||
mask_map: HashMap::new(),
|
||||
config: None,
|
||||
is_openai: false,
|
||||
is_openai_stream: None,
|
||||
stream: false,
|
||||
msg_window: MsgWindow::default(),
|
||||
log: Log::new(PLUGIN_NAME.to_string()),
|
||||
char_window_size: 0,
|
||||
byte_window_size: 0,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
impl AiDataMasking {
|
||||
fn check_message(&self, message: &str) -> bool {
|
||||
if let Some(config) = &self.config {
|
||||
config.check_message(message, self.log())
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
fn msg_to_response(&self, msg: &str, raw_msg: &str, content_type: &str) -> (String, String) {
|
||||
if !self.is_openai {
|
||||
(raw_msg.to_string(), content_type.to_string())
|
||||
} else if self.stream {
|
||||
(
|
||||
format!(
|
||||
"data:{}\n\n",
|
||||
json!({"choices": [{"index": 0, "delta": {"role": "assistant", "content": msg}}], "usage": {}})
|
||||
),
|
||||
"text/event-stream;charset=UTF-8".to_string(),
|
||||
)
|
||||
} else {
|
||||
(
|
||||
json!({"choices": [{"index": 0, "message": {"role": "assistant", "content": msg}}], "usage": {}}).to_string(),
|
||||
"application/json".to_string()
|
||||
)
|
||||
}
|
||||
}
|
||||
fn deny(&mut self, in_response: bool) -> DataAction {
|
||||
if in_response && self.stream {
|
||||
self.replace_http_response_body(&[]);
|
||||
return DataAction::Continue;
|
||||
}
|
||||
let (deny_code, (deny_message, content_type)) = if let Some(config) = &self.config {
|
||||
(
|
||||
config.deny_code,
|
||||
self.msg_to_response(
|
||||
&config.deny_message,
|
||||
&config.deny_raw_message,
|
||||
&config.deny_content_type,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
(
|
||||
default_deny_code(),
|
||||
self.msg_to_response(
|
||||
&default_deny_message(),
|
||||
&default_deny_raw_message(),
|
||||
&default_deny_content_type(),
|
||||
),
|
||||
)
|
||||
};
|
||||
if in_response {
|
||||
self.replace_http_response_body(deny_message.as_bytes());
|
||||
return DataAction::Continue;
|
||||
}
|
||||
self.send_http_response(
|
||||
deny_code as u32,
|
||||
vec![("Content-Type", &content_type)],
|
||||
Some(deny_message.as_bytes()),
|
||||
);
|
||||
DataAction::StopIterationAndBuffer
|
||||
}
|
||||
|
||||
fn replace_request_msg(&mut self, message: &str) -> String {
|
||||
let config = self.config.as_ref().unwrap();
|
||||
let mut msg = message.to_string();
|
||||
for rule in &config.replace_roles {
|
||||
let mut replace_pair = Vec::new();
|
||||
if rule.type_ == Type::Replace && !rule.restore {
|
||||
msg = rule.regex.replace_all(&msg, &rule.value).to_string();
|
||||
} else {
|
||||
for mc in rule.regex.find_iter(&msg) {
|
||||
if mc.is_err() {
|
||||
continue;
|
||||
}
|
||||
let m = mc.unwrap();
|
||||
let from_word = m.as_str();
|
||||
|
||||
let to_word = match rule.type_ {
|
||||
Type::Hash => {
|
||||
let digest = hmac_sha256::Hash::hash(from_word.as_bytes());
|
||||
digest.iter().fold(String::new(), |mut output, b| {
|
||||
let _ = write!(output, "{b:02x}");
|
||||
output
|
||||
})
|
||||
}
|
||||
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()));
|
||||
|
||||
if rule.restore && !to_word.is_empty() {
|
||||
match self.mask_map.entry(to_word) {
|
||||
std::collections::hash_map::Entry::Occupied(mut e) => {
|
||||
e.insert(None);
|
||||
}
|
||||
std::collections::hash_map::Entry::Vacant(e) => {
|
||||
e.insert(Some(from_word.to_string()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (from_word, to_word) in replace_pair {
|
||||
msg = msg.replace(&from_word, &to_word);
|
||||
}
|
||||
}
|
||||
}
|
||||
if msg != message {
|
||||
self.log()
|
||||
.debug(&format!("replace_request_msg from {} to {}", message, msg));
|
||||
}
|
||||
msg
|
||||
}
|
||||
}
|
||||
|
||||
impl Context for AiDataMasking {}
|
||||
|
||||
impl HttpContext for AiDataMasking {
|
||||
fn on_http_request_headers(
|
||||
&mut self,
|
||||
_num_headers: usize,
|
||||
_end_of_stream: bool,
|
||||
) -> HeaderAction {
|
||||
if has_request_body() {
|
||||
self.set_http_request_header("Content-Length", None);
|
||||
HeaderAction::StopIteration
|
||||
} else {
|
||||
HeaderAction::Continue
|
||||
}
|
||||
}
|
||||
fn on_http_response_headers(
|
||||
&mut self,
|
||||
_num_headers: usize,
|
||||
_end_of_stream: bool,
|
||||
) -> HeaderAction {
|
||||
self.set_http_response_header("Content-Length", None);
|
||||
HeaderAction::Continue
|
||||
}
|
||||
|
||||
fn on_http_response_body(&mut self, body_size: usize, end_of_stream: bool) -> DataAction {
|
||||
if !self.stream {
|
||||
return DataAction::Continue;
|
||||
}
|
||||
if body_size > 0 {
|
||||
if let Some(body) = self.get_http_response_body(0, body_size) {
|
||||
if self.is_openai && self.is_openai_stream.is_none() {
|
||||
self.is_openai_stream = Some(body.starts_with(b"data:"));
|
||||
}
|
||||
self.msg_window
|
||||
.push(&body, self.is_openai_stream.unwrap_or_default());
|
||||
let mut deny = false;
|
||||
let log = Log::new(PLUGIN_NAME.to_string());
|
||||
for message in self.msg_window.messages_iter_mut() {
|
||||
if let Ok(mut msg) = String::from_utf8(message.clone()) {
|
||||
if let Some(config) = &self.config {
|
||||
if config.check_message(&msg, &log) {
|
||||
deny = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if !self.mask_map.is_empty() {
|
||||
for (from_word, to_word) in self.mask_map.iter() {
|
||||
if let Some(to) = to_word {
|
||||
msg = msg.replace(from_word, to);
|
||||
}
|
||||
}
|
||||
}
|
||||
message.clear();
|
||||
message.extend_from_slice(msg.as_bytes());
|
||||
}
|
||||
}
|
||||
if deny {
|
||||
return self.deny(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
impl HttpContextWrapper<AiDataMaskingConfig> for AiDataMasking {
|
||||
fn init_self_weak(
|
||||
&mut self,
|
||||
self_weak: std::rc::Weak<RefCell<Box<dyn HttpContextWrapper<AiDataMaskingConfig>>>>,
|
||||
) {
|
||||
self.weak = self_weak;
|
||||
}
|
||||
fn log(&self) -> &Log {
|
||||
&self.log
|
||||
}
|
||||
fn on_config(&mut self, config: Rc<AiDataMaskingConfig>) {
|
||||
self.config = Some(config.clone());
|
||||
}
|
||||
fn cache_request_body(&self) -> bool {
|
||||
true
|
||||
}
|
||||
fn cache_response_body(&self) -> bool {
|
||||
!self.stream
|
||||
}
|
||||
fn on_http_request_complete_body(&mut self, req_body: &Bytes) -> DataAction {
|
||||
if self.config.is_none() {
|
||||
return DataAction::Continue;
|
||||
}
|
||||
let config = self.config.as_ref().unwrap();
|
||||
let mut req_body = match serde_json::from_slice::<Value>(req_body) {
|
||||
Ok(r) => r.to_string(),
|
||||
Err(_) => {
|
||||
if let Ok(r) = String::from_utf8(req_body.clone()) {
|
||||
r
|
||||
} else {
|
||||
return DataAction::Continue;
|
||||
}
|
||||
}
|
||||
};
|
||||
if config.deny_openai {
|
||||
if let Ok(req) = serde_json::from_str::<Req>(req_body.as_str()) {
|
||||
self.is_openai = true;
|
||||
self.stream = req.stream;
|
||||
for msg in req.messages {
|
||||
if self.check_message(&msg.content)
|
||||
|| self.check_message(&msg.reasoning_content)
|
||||
{
|
||||
return self.deny(false);
|
||||
}
|
||||
let new_content = self.replace_request_msg(&msg.content);
|
||||
let new_reasoning_content = self.replace_request_msg(&msg.reasoning_content);
|
||||
if new_content != msg.content {
|
||||
req_body = req_body.replace(
|
||||
&Value::String(msg.content).to_string(),
|
||||
&Value::String(new_content).to_string(),
|
||||
);
|
||||
}
|
||||
if new_reasoning_content != msg.reasoning_content {
|
||||
req_body = req_body.replace(
|
||||
&Value::String(msg.reasoning_content).to_string(),
|
||||
&Value::String(new_reasoning_content).to_string(),
|
||||
);
|
||||
}
|
||||
}
|
||||
self.replace_http_request_body(req_body.as_bytes());
|
||||
return DataAction::Continue;
|
||||
}
|
||||
}
|
||||
if !config.deny_jsonpath.is_empty() {
|
||||
if let Ok(json) = serde_json::from_str::<Value>(req_body.as_str()) {
|
||||
for jsonpath in config.deny_jsonpath.clone() {
|
||||
for v in jsonpath.find_slice(&json) {
|
||||
if let JsonPathValue::Slice(d, _) = v {
|
||||
if let Some(s) = d.as_str() {
|
||||
if self.check_message(s) {
|
||||
return self.deny(false);
|
||||
}
|
||||
let content = s.to_string();
|
||||
let new_content = self.replace_request_msg(&content);
|
||||
if new_content != content {
|
||||
req_body = req_body.replace(
|
||||
&Value::String(content).to_string(),
|
||||
&Value::String(new_content).to_string(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
self.replace_http_request_body(req_body.as_bytes());
|
||||
return DataAction::Continue;
|
||||
}
|
||||
}
|
||||
if config.deny_raw {
|
||||
if self.check_message(&req_body) {
|
||||
return self.deny(false);
|
||||
}
|
||||
let new_body = self.replace_request_msg(&req_body);
|
||||
if new_body != req_body {
|
||||
self.replace_http_request_body(new_body.as_bytes())
|
||||
}
|
||||
return DataAction::Continue;
|
||||
}
|
||||
DataAction::Continue
|
||||
}
|
||||
fn on_http_response_complete_body(&mut self, res_body: &Bytes) -> DataAction {
|
||||
if self.config.is_none() {
|
||||
return DataAction::Continue;
|
||||
}
|
||||
let config = self.config.as_ref().unwrap();
|
||||
let mut res_body = match serde_json::from_slice::<Value>(res_body) {
|
||||
Ok(r) => r.to_string(),
|
||||
Err(_) => {
|
||||
if let Ok(r) = String::from_utf8(res_body.clone()) {
|
||||
r
|
||||
} else {
|
||||
return DataAction::Continue;
|
||||
}
|
||||
}
|
||||
};
|
||||
if config.deny_openai && self.is_openai {
|
||||
if let Ok(res) = serde_json::from_str::<Res>(res_body.as_str()) {
|
||||
for msg in res.choices {
|
||||
if let Some(message) = msg.message {
|
||||
if self.check_message(&message.content)
|
||||
|| self.check_message(&message.reasoning_content)
|
||||
{
|
||||
return self.deny(true);
|
||||
}
|
||||
|
||||
if self.mask_map.is_empty() {
|
||||
continue;
|
||||
}
|
||||
let mut new_content = message.content.clone();
|
||||
let mut new_reasoning_content = message.reasoning_content.clone();
|
||||
for (from_word, to_word) in self.mask_map.iter() {
|
||||
if let Some(to) = to_word {
|
||||
new_content = new_content.replace(from_word, to);
|
||||
new_reasoning_content =
|
||||
new_reasoning_content.replace(from_word, to);
|
||||
}
|
||||
}
|
||||
if new_content != message.content {
|
||||
res_body = res_body.replace(
|
||||
&Value::String(message.content).to_string(),
|
||||
&Value::String(new_content).to_string(),
|
||||
);
|
||||
}
|
||||
if new_reasoning_content != message.reasoning_content {
|
||||
res_body = res_body.replace(
|
||||
&Value::String(message.reasoning_content).to_string(),
|
||||
&Value::String(new_reasoning_content).to_string(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
self.replace_http_response_body(res_body.as_bytes());
|
||||
|
||||
return DataAction::Continue;
|
||||
}
|
||||
}
|
||||
if config.deny_raw {
|
||||
if self.check_message(&res_body) {
|
||||
return self.deny(true);
|
||||
}
|
||||
if !self.mask_map.is_empty() {
|
||||
for (from_word, to_word) in self.mask_map.iter() {
|
||||
if let Some(to) = to_word {
|
||||
res_body = res_body.replace(from_word, to);
|
||||
}
|
||||
}
|
||||
}
|
||||
self.replace_http_response_body(res_body.as_bytes());
|
||||
return DataAction::Continue;
|
||||
}
|
||||
DataAction::Continue
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,25 @@
|
||||
// Copyright (c) 2025 Alibaba Group Holding Ltd.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use std::collections::HashSet;
|
||||
|
||||
use jieba_rs::Jieba;
|
||||
use rust_embed::Embed;
|
||||
|
||||
use crate::Asset;
|
||||
#[derive(Embed)]
|
||||
#[folder = "res/"]
|
||||
struct Asset;
|
||||
|
||||
#[derive(Default, Debug, Clone)]
|
||||
pub(crate) struct DenyWord {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2023 Alibaba Group Holding Ltd.
|
||||
// Copyright (c) 2025 Alibaba Group Holding Ltd.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
@@ -12,705 +12,8 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
mod ai_data_masking;
|
||||
mod deny_word;
|
||||
mod msg_win_openai;
|
||||
mod msg_window;
|
||||
|
||||
use crate::deny_word::DenyWord;
|
||||
use crate::msg_window::MsgWindow;
|
||||
use fancy_regex::Regex;
|
||||
use grok::patterns;
|
||||
use higress_wasm_rust::log::Log;
|
||||
use higress_wasm_rust::plugin_wrapper::{HttpContextWrapper, RootContextWrapper};
|
||||
use higress_wasm_rust::request_wrapper::has_request_body;
|
||||
use higress_wasm_rust::rule_matcher::{on_configure, RuleMatcher, SharedRuleMatcher};
|
||||
use jsonpath_rust::{JsonPath, JsonPathValue};
|
||||
use lazy_static::lazy_static;
|
||||
use proxy_wasm::traits::{Context, HttpContext, RootContext};
|
||||
use proxy_wasm::types::{Bytes, ContextType, DataAction, HeaderAction, LogLevel};
|
||||
use rust_embed::Embed;
|
||||
use serde::de::Error;
|
||||
use serde::Deserializer;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{json, Value};
|
||||
use std::cell::RefCell;
|
||||
use std::collections::{BTreeMap, HashMap, VecDeque};
|
||||
use std::ops::DerefMut;
|
||||
use std::rc::Rc;
|
||||
use std::str::FromStr;
|
||||
use std::vec;
|
||||
|
||||
proxy_wasm::main! {{
|
||||
proxy_wasm::set_log_level(LogLevel::Trace);
|
||||
proxy_wasm::set_root_context(|_|Box::new(AiDataMaskingRoot::new()));
|
||||
}}
|
||||
|
||||
const PLUGIN_NAME: &str = "ai-data-masking";
|
||||
const GROK_PATTERN: &str = r"%\{(?<name>(?<pattern>[A-z0-9]+)(?::(?<alias>[A-z0-9_:;\/\s\.]+))?)\}";
|
||||
|
||||
#[derive(Embed)]
|
||||
#[folder = "res/"]
|
||||
struct Asset;
|
||||
|
||||
struct System {
|
||||
deny_word: DenyWord,
|
||||
grok_regex: Regex,
|
||||
grok_patterns: BTreeMap<String, String>,
|
||||
}
|
||||
lazy_static! {
|
||||
static ref SYSTEM: System = System::new();
|
||||
}
|
||||
|
||||
struct AiDataMaskingRoot {
|
||||
log: Log,
|
||||
rule_matcher: SharedRuleMatcher<AiDataMaskingConfig>,
|
||||
}
|
||||
struct AiDataMasking {
|
||||
config: Option<Rc<AiDataMaskingConfig>>,
|
||||
mask_map: HashMap<String, Option<String>>,
|
||||
is_openai: bool,
|
||||
is_openai_stream: Option<bool>,
|
||||
stream: bool,
|
||||
log: Log,
|
||||
msg_window: MsgWindow,
|
||||
char_window_size: usize,
|
||||
byte_window_size: usize,
|
||||
}
|
||||
fn deserialize_regexp<'de, D>(deserializer: D) -> Result<Regex, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let value: Value = Deserialize::deserialize(deserializer)?;
|
||||
if let Some(pattern) = value.as_str() {
|
||||
let (p, _) = SYSTEM.grok_to_pattern(pattern);
|
||||
if let Ok(reg) = Regex::new(&p) {
|
||||
Ok(reg)
|
||||
} else if let Ok(reg) = Regex::new(pattern) {
|
||||
Ok(reg)
|
||||
} else {
|
||||
Err(Error::custom(format!("regexp error field {}", pattern)))
|
||||
}
|
||||
} else {
|
||||
Err(Error::custom("regexp error not string".to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
fn deserialize_type<'de, D>(deserializer: D) -> Result<Type, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let value: Value = Deserialize::deserialize(deserializer)?;
|
||||
if let Some(t) = value.as_str() {
|
||||
if t == "replace" {
|
||||
Ok(Type::Replace)
|
||||
} else if t == "hash" {
|
||||
Ok(Type::Hash)
|
||||
} else {
|
||||
Err(Error::custom(format!("regexp error value {}", t)))
|
||||
}
|
||||
} else {
|
||||
Err(Error::custom("type error not string".to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
fn deserialize_denyword<'de, D>(deserializer: D) -> Result<DenyWord, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let value: Vec<String> = Deserialize::deserialize(deserializer)?;
|
||||
Ok(DenyWord::from_iter(value))
|
||||
}
|
||||
|
||||
fn deserialize_jsonpath<'de, D>(deserializer: D) -> Result<Vec<JsonPath>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let value: Vec<String> = Deserialize::deserialize(deserializer)?;
|
||||
let mut ret = Vec::new();
|
||||
for v in value {
|
||||
if v.is_empty() {
|
||||
continue;
|
||||
}
|
||||
match JsonPath::from_str(&v) {
|
||||
Ok(jp) => ret.push(jp),
|
||||
Err(_) => return Err(Error::custom(format!("jsonpath error value {}", v))),
|
||||
}
|
||||
}
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
enum Type {
|
||||
Replace,
|
||||
Hash,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
struct Rule {
|
||||
#[serde(deserialize_with = "deserialize_regexp")]
|
||||
regex: Regex,
|
||||
#[serde(deserialize_with = "deserialize_type", alias = "type")]
|
||||
type_: Type,
|
||||
#[serde(default)]
|
||||
restore: bool,
|
||||
#[serde(default)]
|
||||
value: String,
|
||||
}
|
||||
fn default_deny_openai() -> bool {
|
||||
true
|
||||
}
|
||||
fn default_deny_raw() -> bool {
|
||||
false
|
||||
}
|
||||
fn default_system_deny() -> bool {
|
||||
false
|
||||
}
|
||||
fn default_deny_code() -> u16 {
|
||||
200
|
||||
}
|
||||
fn default_deny_content_type() -> String {
|
||||
"application/json".to_string()
|
||||
}
|
||||
fn default_deny_raw_message() -> String {
|
||||
"{\"errmsg\":\"提问或回答中包含敏感词,已被屏蔽\"}".to_string()
|
||||
}
|
||||
fn default_deny_message() -> String {
|
||||
"提问或回答中包含敏感词,已被屏蔽".to_string()
|
||||
}
|
||||
#[derive(Default, Debug, Deserialize, Clone)]
|
||||
pub struct AiDataMaskingConfig {
|
||||
#[serde(default = "default_deny_openai")]
|
||||
deny_openai: bool,
|
||||
#[serde(default = "default_deny_raw")]
|
||||
deny_raw: bool,
|
||||
#[serde(default, deserialize_with = "deserialize_jsonpath")]
|
||||
deny_jsonpath: Vec<JsonPath>,
|
||||
#[serde(default = "default_system_deny")]
|
||||
system_deny: bool,
|
||||
#[serde(default = "default_deny_code")]
|
||||
deny_code: u16,
|
||||
#[serde(default = "default_deny_message")]
|
||||
deny_message: String,
|
||||
#[serde(default = "default_deny_raw_message")]
|
||||
deny_raw_message: String,
|
||||
#[serde(default = "default_deny_content_type")]
|
||||
deny_content_type: String,
|
||||
#[serde(default)]
|
||||
replace_roles: Vec<Rule>,
|
||||
#[serde(deserialize_with = "deserialize_denyword", default = "DenyWord::empty")]
|
||||
deny_words: DenyWord,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
struct Message {
|
||||
#[serde(default)]
|
||||
content: String,
|
||||
}
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
struct Req {
|
||||
#[serde(default)]
|
||||
stream: bool,
|
||||
messages: Vec<Message>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Deserialize)]
|
||||
struct ResMessage {
|
||||
#[serde(default)]
|
||||
message: Option<Message>,
|
||||
#[serde(default)]
|
||||
delta: Option<Message>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Deserialize, Serialize, Clone)]
|
||||
struct Usage {
|
||||
#[serde(default)]
|
||||
completion_tokens: i32,
|
||||
#[serde(default)]
|
||||
prompt_tokens: i32,
|
||||
#[serde(default)]
|
||||
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)]
|
||||
struct Res {
|
||||
#[serde(default)]
|
||||
choices: Vec<ResMessage>,
|
||||
#[serde(default)]
|
||||
usage: Usage,
|
||||
}
|
||||
|
||||
static SYSTEM_PATTERNS: &[(&str, &str)] = &[
|
||||
("MOBILE", r#"\d{8,11}"#),
|
||||
("IDCARD", r#"\d{17}[0-9xX]|\d{15}"#),
|
||||
];
|
||||
|
||||
impl System {
|
||||
fn new() -> Self {
|
||||
let grok_regex = Regex::new(GROK_PATTERN).unwrap();
|
||||
let grok_patterns = BTreeMap::new();
|
||||
let mut system = System {
|
||||
deny_word: DenyWord::system(),
|
||||
grok_regex,
|
||||
grok_patterns,
|
||||
};
|
||||
system.init();
|
||||
system
|
||||
}
|
||||
fn init(&mut self) {
|
||||
let mut grok_temp_patterns = VecDeque::new();
|
||||
for patterns in [patterns(), SYSTEM_PATTERNS] {
|
||||
for &(key, value) in patterns {
|
||||
if self.grok_regex.is_match(value).is_ok_and(|r| r) {
|
||||
grok_temp_patterns.push_back((String::from(key), String::from(value)));
|
||||
} else {
|
||||
self.grok_patterns
|
||||
.insert(String::from(key), String::from(value));
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut last_ok: Option<String> = None;
|
||||
|
||||
while let Some((key, value)) = grok_temp_patterns.pop_front() {
|
||||
if let Some(k) = &last_ok {
|
||||
if k == &key {
|
||||
break;
|
||||
}
|
||||
}
|
||||
let (v, ok) = self.grok_to_pattern(&value);
|
||||
if ok {
|
||||
self.grok_patterns.insert(key, v);
|
||||
last_ok = None;
|
||||
} else {
|
||||
if last_ok.is_none() {
|
||||
last_ok = Some(key.clone());
|
||||
}
|
||||
grok_temp_patterns.push_back((key, v));
|
||||
}
|
||||
}
|
||||
}
|
||||
fn grok_to_pattern(&self, pattern: &str) -> (String, bool) {
|
||||
let mut ok = true;
|
||||
let mut ret = pattern.to_string();
|
||||
for capture in self.grok_regex.captures_iter(pattern) {
|
||||
if capture.is_err() {
|
||||
ok = false;
|
||||
continue;
|
||||
}
|
||||
let c = capture.unwrap();
|
||||
if let (Some(full), Some(name)) = (c.get(0), c.name("pattern")) {
|
||||
if let Some(p) = self.grok_patterns.get(name.as_str()) {
|
||||
if let Some(alias) = c.name("alias") {
|
||||
ret = ret.replace(full.as_str(), &format!("(?P<{}>{})", alias.as_str(), p));
|
||||
} else {
|
||||
ret = ret.replace(full.as_str(), p);
|
||||
}
|
||||
} else {
|
||||
ok = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
(ret, ok)
|
||||
}
|
||||
}
|
||||
|
||||
impl AiDataMaskingRoot {
|
||||
fn new() -> Self {
|
||||
AiDataMaskingRoot {
|
||||
log: Log::new(PLUGIN_NAME.to_string()),
|
||||
rule_matcher: Rc::new(RefCell::new(RuleMatcher::default())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Context for AiDataMaskingRoot {}
|
||||
|
||||
impl RootContext for AiDataMaskingRoot {
|
||||
fn on_configure(&mut self, plugin_configuration_size: usize) -> bool {
|
||||
on_configure(
|
||||
self,
|
||||
plugin_configuration_size,
|
||||
self.rule_matcher.borrow_mut().deref_mut(),
|
||||
&self.log,
|
||||
)
|
||||
}
|
||||
fn create_http_context(&self, context_id: u32) -> Option<Box<dyn HttpContext>> {
|
||||
self.create_http_context_use_wrapper(context_id)
|
||||
}
|
||||
fn get_type(&self) -> Option<ContextType> {
|
||||
Some(ContextType::HttpContext)
|
||||
}
|
||||
}
|
||||
|
||||
impl RootContextWrapper<AiDataMaskingConfig> for AiDataMaskingRoot {
|
||||
fn rule_matcher(&self) -> &SharedRuleMatcher<AiDataMaskingConfig> {
|
||||
&self.rule_matcher
|
||||
}
|
||||
|
||||
fn create_http_context_wrapper(
|
||||
&self,
|
||||
_context_id: u32,
|
||||
) -> Option<Box<dyn HttpContextWrapper<AiDataMaskingConfig>>> {
|
||||
Some(Box::new(AiDataMasking {
|
||||
mask_map: HashMap::new(),
|
||||
config: None,
|
||||
is_openai: false,
|
||||
is_openai_stream: None,
|
||||
stream: false,
|
||||
msg_window: MsgWindow::new(),
|
||||
log: Log::new(PLUGIN_NAME.to_string()),
|
||||
char_window_size: 0,
|
||||
byte_window_size: 0,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
impl AiDataMasking {
|
||||
fn check_message(&self, message: &str) -> bool {
|
||||
if let Some(config) = &self.config {
|
||||
if let Some(word) = config.deny_words.check(message) {
|
||||
self.log().warn(&format!(
|
||||
"custom deny word {} matched from {}",
|
||||
word, message
|
||||
));
|
||||
return true;
|
||||
} else if config.system_deny {
|
||||
if let Some(word) = SYSTEM.deny_word.check(message) {
|
||||
self.log().warn(&format!(
|
||||
"system deny word {} matched from {}",
|
||||
word, message
|
||||
));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
fn msg_to_response(&self, msg: &str, raw_msg: &str, content_type: &str) -> (String, String) {
|
||||
if !self.is_openai {
|
||||
(raw_msg.to_string(), content_type.to_string())
|
||||
} else if self.stream {
|
||||
(
|
||||
format!(
|
||||
"data:{}\n\n",
|
||||
json!({"choices": [{"index": 0, "delta": {"role": "assistant", "content": msg}}], "usage": {}})
|
||||
),
|
||||
"text/event-stream;charset=UTF-8".to_string(),
|
||||
)
|
||||
} else {
|
||||
(
|
||||
json!({"choices": [{"index": 0, "message": {"role": "assistant", "content": msg}}], "usage": {}}).to_string(),
|
||||
"application/json".to_string()
|
||||
)
|
||||
}
|
||||
}
|
||||
fn deny(&mut self, in_response: bool) -> DataAction {
|
||||
if in_response && self.stream {
|
||||
self.replace_http_response_body(&[]);
|
||||
return DataAction::Continue;
|
||||
}
|
||||
let (deny_code, (deny_message, content_type)) = if let Some(config) = &self.config {
|
||||
(
|
||||
config.deny_code,
|
||||
self.msg_to_response(
|
||||
&config.deny_message,
|
||||
&config.deny_raw_message,
|
||||
&config.deny_content_type,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
(
|
||||
default_deny_code(),
|
||||
self.msg_to_response(
|
||||
&default_deny_message(),
|
||||
&default_deny_raw_message(),
|
||||
&default_deny_content_type(),
|
||||
),
|
||||
)
|
||||
};
|
||||
if in_response {
|
||||
self.replace_http_response_body(deny_message.as_bytes());
|
||||
return DataAction::Continue;
|
||||
}
|
||||
self.send_http_response(
|
||||
deny_code as u32,
|
||||
vec![("Content-Type", &content_type)],
|
||||
Some(deny_message.as_bytes()),
|
||||
);
|
||||
DataAction::StopIterationAndBuffer
|
||||
}
|
||||
|
||||
fn replace_request_msg(&mut self, message: &str) -> String {
|
||||
let config = self.config.as_ref().unwrap();
|
||||
let mut msg = message.to_string();
|
||||
for rule in &config.replace_roles {
|
||||
let mut replace_pair = Vec::new();
|
||||
if rule.type_ == Type::Replace && !rule.restore {
|
||||
msg = rule.regex.replace_all(&msg, &rule.value).to_string();
|
||||
} else {
|
||||
for mc in rule.regex.find_iter(&msg) {
|
||||
if mc.is_err() {
|
||||
continue;
|
||||
}
|
||||
let m = mc.unwrap();
|
||||
let from_word = m.as_str();
|
||||
|
||||
let to_word = match rule.type_ {
|
||||
Type::Hash => {
|
||||
let digest = md5::compute(from_word.as_bytes());
|
||||
format!("{:x}", digest)
|
||||
}
|
||||
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()));
|
||||
|
||||
if rule.restore && !to_word.is_empty() {
|
||||
match self.mask_map.entry(to_word) {
|
||||
std::collections::hash_map::Entry::Occupied(mut e) => {
|
||||
e.insert(None);
|
||||
}
|
||||
std::collections::hash_map::Entry::Vacant(e) => {
|
||||
e.insert(Some(from_word.to_string()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (from_word, to_word) in replace_pair {
|
||||
msg = msg.replace(&from_word, &to_word);
|
||||
}
|
||||
}
|
||||
}
|
||||
if msg != message {
|
||||
self.log()
|
||||
.debug(&format!("replace_request_msg from {} to {}", message, msg));
|
||||
}
|
||||
msg
|
||||
}
|
||||
}
|
||||
|
||||
impl Context for AiDataMasking {}
|
||||
|
||||
impl HttpContext for AiDataMasking {
|
||||
fn on_http_request_headers(
|
||||
&mut self,
|
||||
_num_headers: usize,
|
||||
_end_of_stream: bool,
|
||||
) -> HeaderAction {
|
||||
if has_request_body() {
|
||||
self.set_http_request_header("Content-Length", None);
|
||||
HeaderAction::StopIteration
|
||||
} else {
|
||||
HeaderAction::Continue
|
||||
}
|
||||
}
|
||||
fn on_http_response_headers(
|
||||
&mut self,
|
||||
_num_headers: usize,
|
||||
_end_of_stream: bool,
|
||||
) -> HeaderAction {
|
||||
self.set_http_response_header("Content-Length", None);
|
||||
HeaderAction::Continue
|
||||
}
|
||||
fn on_http_response_body(&mut self, body_size: usize, end_of_stream: bool) -> DataAction {
|
||||
if !self.stream {
|
||||
return DataAction::Continue;
|
||||
}
|
||||
if body_size > 0 {
|
||||
if let Some(body) = self.get_http_response_body(0, body_size) {
|
||||
if self.is_openai && self.is_openai_stream.is_none() {
|
||||
self.is_openai_stream = Some(body.starts_with(b"data:"));
|
||||
}
|
||||
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(&msg) {
|
||||
return self.deny(true);
|
||||
}
|
||||
if !self.mask_map.is_empty() {
|
||||
for (from_word, to_word) in self.mask_map.iter() {
|
||||
if let Some(to) = to_word {
|
||||
msg = msg.replace(from_word, to);
|
||||
}
|
||||
}
|
||||
}
|
||||
self.msg_window.message = msg.as_bytes().to_vec();
|
||||
}
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
impl HttpContextWrapper<AiDataMaskingConfig> for AiDataMasking {
|
||||
fn log(&self) -> &Log {
|
||||
&self.log
|
||||
}
|
||||
fn on_config(&mut self, config: Rc<AiDataMaskingConfig>) {
|
||||
self.config = Some(config.clone());
|
||||
}
|
||||
fn cache_request_body(&self) -> bool {
|
||||
true
|
||||
}
|
||||
fn cache_response_body(&self) -> bool {
|
||||
!self.stream
|
||||
}
|
||||
fn on_http_request_complete_body(&mut self, req_body: &Bytes) -> DataAction {
|
||||
if self.config.is_none() {
|
||||
return DataAction::Continue;
|
||||
}
|
||||
let config = self.config.as_ref().unwrap();
|
||||
let mut req_body = match String::from_utf8(req_body.clone()) {
|
||||
Ok(r) => r,
|
||||
Err(_) => return DataAction::Continue,
|
||||
};
|
||||
if config.deny_openai {
|
||||
if let Ok(r) = serde_json::from_str(req_body.as_str()) {
|
||||
let req: Req = r;
|
||||
self.is_openai = true;
|
||||
self.stream = req.stream;
|
||||
for msg in req.messages {
|
||||
if self.check_message(&msg.content) {
|
||||
return self.deny(false);
|
||||
}
|
||||
let new_content = self.replace_request_msg(&msg.content);
|
||||
if new_content != msg.content {
|
||||
if let (Ok(from), Ok(to)) = (
|
||||
serde_json::to_string(&msg.content),
|
||||
serde_json::to_string(&new_content),
|
||||
) {
|
||||
req_body = req_body.replace(&from, &to);
|
||||
}
|
||||
}
|
||||
}
|
||||
self.replace_http_request_body(req_body.as_bytes());
|
||||
return DataAction::Continue;
|
||||
}
|
||||
}
|
||||
if !config.deny_jsonpath.is_empty() {
|
||||
if let Ok(r) = serde_json::from_str(req_body.as_str()) {
|
||||
let json: Value = r;
|
||||
for jsonpath in config.deny_jsonpath.clone() {
|
||||
for v in jsonpath.find_slice(&json) {
|
||||
if let JsonPathValue::Slice(d, _) = v {
|
||||
if let Some(s) = d.as_str() {
|
||||
if self.check_message(s) {
|
||||
return self.deny(false);
|
||||
}
|
||||
let content = s.to_string();
|
||||
let new_content = self.replace_request_msg(&content);
|
||||
if new_content != content {
|
||||
if let (Ok(from), Ok(to)) = (
|
||||
serde_json::to_string(&content),
|
||||
serde_json::to_string(&new_content),
|
||||
) {
|
||||
req_body = req_body.replace(&from, &to);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
self.replace_http_request_body(req_body.as_bytes());
|
||||
return DataAction::Continue;
|
||||
}
|
||||
}
|
||||
if config.deny_raw {
|
||||
if self.check_message(&req_body) {
|
||||
return self.deny(false);
|
||||
}
|
||||
let new_body = self.replace_request_msg(&req_body);
|
||||
if new_body != req_body {
|
||||
self.replace_http_request_body(new_body.as_bytes())
|
||||
}
|
||||
return DataAction::Continue;
|
||||
}
|
||||
DataAction::Continue
|
||||
}
|
||||
fn on_http_response_complete_body(&mut self, res_body: &Bytes) -> DataAction {
|
||||
if self.config.is_none() {
|
||||
return DataAction::Continue;
|
||||
}
|
||||
let config = self.config.as_ref().unwrap();
|
||||
let mut res_body = match String::from_utf8(res_body.clone()) {
|
||||
Ok(r) => r,
|
||||
Err(_) => {
|
||||
return DataAction::Continue;
|
||||
}
|
||||
};
|
||||
if config.deny_openai && self.is_openai {
|
||||
if let Ok(r) = serde_json::from_str(res_body.as_str()) {
|
||||
let res: Res = r;
|
||||
for msg in res.choices {
|
||||
if let Some(meesage) = msg.message {
|
||||
if self.check_message(&meesage.content) {
|
||||
return self.deny(true);
|
||||
}
|
||||
|
||||
if self.mask_map.is_empty() {
|
||||
continue;
|
||||
}
|
||||
let mut m = meesage.content.clone();
|
||||
for (from_word, to_word) in self.mask_map.iter() {
|
||||
if let Some(to) = to_word {
|
||||
m = m.replace(from_word, to);
|
||||
}
|
||||
}
|
||||
if m != meesage.content {
|
||||
if let (Ok(from), Ok(to)) = (
|
||||
serde_json::to_string(&meesage.content),
|
||||
serde_json::to_string(&m),
|
||||
) {
|
||||
res_body = res_body.replace(&from, &to);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
self.replace_http_response_body(res_body.as_bytes());
|
||||
|
||||
return DataAction::Continue;
|
||||
}
|
||||
}
|
||||
if config.deny_raw {
|
||||
if self.check_message(&res_body) {
|
||||
return self.deny(true);
|
||||
}
|
||||
if !self.mask_map.is_empty() {
|
||||
for (from_word, to_word) in self.mask_map.iter() {
|
||||
if let Some(to) = to_word {
|
||||
res_body = res_body.replace(from_word, to);
|
||||
}
|
||||
}
|
||||
}
|
||||
self.replace_http_response_body(res_body.as_bytes());
|
||||
return DataAction::Continue;
|
||||
}
|
||||
DataAction::Continue
|
||||
}
|
||||
}
|
||||
mod number_merge;
|
||||
|
||||
@@ -0,0 +1,356 @@
|
||||
// Copyright (c) 2025 Alibaba Group Holding Ltd.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use higress_wasm_rust::event_stream::EventStream;
|
||||
use serde::Deserialize;
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::msg_window::MessageWindow;
|
||||
use crate::number_merge::NumberMerge;
|
||||
|
||||
#[derive(PartialEq, Eq, Clone, Copy)]
|
||||
enum MsgFlag {
|
||||
None,
|
||||
Content,
|
||||
ReasoningContent,
|
||||
}
|
||||
impl Default for MsgFlag {
|
||||
fn default() -> Self {
|
||||
Self::None
|
||||
}
|
||||
}
|
||||
#[derive(Deserialize)]
|
||||
struct Delta {
|
||||
#[serde(default)]
|
||||
content: Option<String>,
|
||||
#[serde(default)]
|
||||
reasoning_content: Option<String>,
|
||||
}
|
||||
#[derive(Deserialize)]
|
||||
struct Choices {
|
||||
#[serde(default)]
|
||||
index: i64,
|
||||
#[serde(default)]
|
||||
delta: Option<Delta>,
|
||||
#[serde(default)]
|
||||
finish_reason: Option<String>,
|
||||
}
|
||||
|
||||
impl Delta {
|
||||
fn get_flag_msg(&self, default_flag: &MsgFlag) -> (MsgFlag, &[u8]) {
|
||||
if let Some(msg) = &self.content {
|
||||
if !msg.is_empty() {
|
||||
return (MsgFlag::Content, msg.as_bytes());
|
||||
}
|
||||
}
|
||||
if let Some(msg) = &self.reasoning_content {
|
||||
if !msg.is_empty() {
|
||||
return (MsgFlag::ReasoningContent, msg.as_bytes());
|
||||
}
|
||||
}
|
||||
(*default_flag, &[])
|
||||
}
|
||||
}
|
||||
const USAGE_PATH: &str = "usage";
|
||||
const CHOICES_PATH: &str = "choices";
|
||||
|
||||
type MessageLine = Vec<(MsgFlag, Vec<u8>)>;
|
||||
|
||||
#[derive(Default)]
|
||||
struct MessageWindowOpenAi {
|
||||
message_window: MessageWindow,
|
||||
ret_messages: MessageLine,
|
||||
flag: MsgFlag,
|
||||
last_value: Value,
|
||||
finish_reason: Option<String>,
|
||||
}
|
||||
|
||||
impl MessageWindowOpenAi {
|
||||
fn update(
|
||||
&mut self,
|
||||
data: &[u8],
|
||||
flag: MsgFlag,
|
||||
value: &Value,
|
||||
finish_reason: &Option<String>,
|
||||
) {
|
||||
self.last_value = value.clone();
|
||||
if data.is_empty() {
|
||||
return;
|
||||
}
|
||||
if self.flag == MsgFlag::None {
|
||||
self.flag = flag;
|
||||
}
|
||||
if self.flag != flag {
|
||||
let last_flag = core::mem::replace(&mut self.flag, flag);
|
||||
let msg = self.message_window.finish();
|
||||
self.ret_messages.push((last_flag, msg));
|
||||
}
|
||||
self.message_window.update(data);
|
||||
if let Some(fr) = finish_reason {
|
||||
self.finish_reason = Some(fr.clone());
|
||||
}
|
||||
}
|
||||
|
||||
fn gen_value(&self, flag: &MsgFlag, msg: &[u8], finish: bool) -> Value {
|
||||
let mut ret = self.last_value.clone();
|
||||
match flag {
|
||||
MsgFlag::Content => {
|
||||
ret["delta"]["content"] = Value::String(String::from_utf8_lossy(msg).to_string());
|
||||
if let Some(m) = ret["delta"].as_object_mut() {
|
||||
m.remove("reasoning_content");
|
||||
}
|
||||
}
|
||||
MsgFlag::ReasoningContent => {
|
||||
ret["delta"]["reasoning_content"] =
|
||||
Value::String(String::from_utf8_lossy(msg).to_string());
|
||||
ret["delta"]["content"] = Value::String(String::new());
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
if finish {
|
||||
ret["finish_reason"] = self
|
||||
.finish_reason
|
||||
.as_ref()
|
||||
.map_or(Value::Null, |v| Value::String(v.to_string()));
|
||||
} else {
|
||||
ret["finish_reason"] = Value::Null;
|
||||
}
|
||||
ret
|
||||
}
|
||||
|
||||
fn messages_to_value(&mut self) -> Vec<Value> {
|
||||
let mut ret = Vec::new();
|
||||
for (flag, msg) in core::mem::take(&mut self.ret_messages) {
|
||||
ret.push(self.gen_value(&flag, &msg, false));
|
||||
}
|
||||
ret
|
||||
}
|
||||
|
||||
fn pop(&mut self, char_window_size: usize, byte_window_size: usize) -> Vec<Value> {
|
||||
let mut ret = self.messages_to_value();
|
||||
|
||||
let msg = self.message_window.pop(char_window_size, byte_window_size);
|
||||
if !msg.is_empty() {
|
||||
ret.push(self.gen_value(&self.flag, &msg, false));
|
||||
}
|
||||
|
||||
ret
|
||||
}
|
||||
fn finish(&mut self) -> Vec<Value> {
|
||||
let mut ret = self.messages_to_value();
|
||||
let msg = self.message_window.finish();
|
||||
let flag = core::mem::replace(&mut self.flag, MsgFlag::None);
|
||||
ret.push(self.gen_value(&flag, &msg, true));
|
||||
|
||||
ret
|
||||
}
|
||||
fn iter_mut(&mut self) -> impl Iterator<Item = &mut Vec<u8>> {
|
||||
self.ret_messages
|
||||
.iter_mut()
|
||||
.map(|(_, msg)| msg)
|
||||
.chain(self.message_window.iter_mut())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub(crate) struct MsgWindow {
|
||||
stream_parser: EventStream,
|
||||
base_message_window: MessageWindow,
|
||||
message_windows: HashMap<i64, MessageWindowOpenAi>,
|
||||
last_value: Value,
|
||||
usage: NumberMerge,
|
||||
}
|
||||
|
||||
impl MsgWindow {
|
||||
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::<Value>(&event[b"data:".len()..]) {
|
||||
self.last_value = res;
|
||||
if let Some(r) = self.last_value.as_object() {
|
||||
if let Some(v) = r.get(USAGE_PATH) {
|
||||
self.usage.add(v);
|
||||
}
|
||||
if let Some(v) = r.get(CHOICES_PATH) {
|
||||
if let Some(a) = v.as_array() {
|
||||
for item in a {
|
||||
if let Ok(c) = serde_json::from_value::<Choices>(item.clone()) {
|
||||
if let Some(d) = &c.delta {
|
||||
let mw = self.message_windows.entry(c.index).or_default();
|
||||
let (flag, msg) = d.get_flag_msg(&mw.flag);
|
||||
mw.update(msg, flag, item, &c.finish_reason);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
} else if event.starts_with(b"data: [DONE]") {
|
||||
None
|
||||
} else {
|
||||
Some(event)
|
||||
}
|
||||
}
|
||||
fn push_base(&mut self, data: &[u8]) {
|
||||
self.base_message_window.update(data);
|
||||
}
|
||||
pub(crate) 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.push_base(&msg);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.push_base(data);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn pop(
|
||||
&mut self,
|
||||
char_window_size: usize,
|
||||
byte_window_size: usize,
|
||||
is_openai: bool,
|
||||
) -> Vec<u8> {
|
||||
if !is_openai {
|
||||
return self
|
||||
.base_message_window
|
||||
.pop(char_window_size, byte_window_size);
|
||||
}
|
||||
let mut ret = Vec::new();
|
||||
for mw in self.message_windows.values_mut() {
|
||||
for value in mw.pop(char_window_size, byte_window_size) {
|
||||
let usage = self.usage.finish();
|
||||
let mut ret_value = self.last_value.clone();
|
||||
ret_value[CHOICES_PATH] = Value::Array(vec![value]);
|
||||
ret_value[USAGE_PATH] = usage;
|
||||
ret.extend(format!("data: {}\n\n", ret_value).as_bytes())
|
||||
}
|
||||
}
|
||||
ret
|
||||
}
|
||||
pub(crate) fn finish(&mut self, is_openai: bool) -> Vec<u8> {
|
||||
if !is_openai {
|
||||
return self.base_message_window.finish();
|
||||
}
|
||||
if let Some(event) = self.stream_parser.flush() {
|
||||
self.update_event(event);
|
||||
}
|
||||
let mut ret = Vec::new();
|
||||
for mw in &mut self.message_windows.values_mut() {
|
||||
for value in mw.finish() {
|
||||
let usage = self.usage.finish();
|
||||
let mut ret_value = self.last_value.clone();
|
||||
ret_value[CHOICES_PATH] = Value::Array(vec![value]);
|
||||
ret_value[USAGE_PATH] = usage;
|
||||
ret.extend(format!("data: {}\n\n", ret_value).as_bytes())
|
||||
}
|
||||
}
|
||||
ret
|
||||
}
|
||||
pub(crate) fn messages_iter_mut(&mut self) -> impl Iterator<Item = &mut Vec<u8>> {
|
||||
self.base_message_window.iter_mut().chain(
|
||||
self.message_windows
|
||||
.values_mut()
|
||||
.flat_map(|mw| mw.iter_mut()),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use rust_embed::Embed;
|
||||
|
||||
use super::*;
|
||||
#[derive(Embed)]
|
||||
#[folder = "test/"]
|
||||
struct Asset;
|
||||
#[derive(Deserialize)]
|
||||
struct Res {
|
||||
choices: Vec<Choices>,
|
||||
}
|
||||
|
||||
impl Res {
|
||||
fn get_text(&self) -> (String, String) {
|
||||
let mut content = String::new();
|
||||
let mut reasoning_content = String::new();
|
||||
for choice in self.choices.iter() {
|
||||
if let Some(delta) = &choice.delta {
|
||||
if let Some(c) = &delta.content {
|
||||
content += c;
|
||||
}
|
||||
if let Some(rc) = &delta.reasoning_content {
|
||||
reasoning_content += rc;
|
||||
}
|
||||
}
|
||||
}
|
||||
(content, reasoning_content)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_msg() {
|
||||
let mut msg_win = MsgWindow::default();
|
||||
let data = raw_message("raw_message.txt");
|
||||
let mut buffer = Vec::new();
|
||||
for line in data.split("\n") {
|
||||
msg_win.push(line.as_bytes(), true);
|
||||
msg_win.push(b"\n\n", true);
|
||||
for message in msg_win.messages_iter_mut() {
|
||||
if let Ok(mut msg) = String::from_utf8(message.clone()) {
|
||||
msg = msg.replace("Higress", "***higress***");
|
||||
message.clear();
|
||||
message.extend_from_slice(msg.as_bytes());
|
||||
}
|
||||
}
|
||||
|
||||
buffer.extend(msg_win.pop(7, 7, true));
|
||||
}
|
||||
buffer.extend(msg_win.finish(true));
|
||||
let mut message = String::new();
|
||||
let mut reasoning_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::<Res>(&line[b"data:".len()..]);
|
||||
assert!(des.is_ok());
|
||||
let res = des.unwrap();
|
||||
let (c, rc) = res.get_text();
|
||||
message.push_str(&c);
|
||||
reasoning_message.push_str(&rc);
|
||||
}
|
||||
let res = "***higress*** 是一个基于 Istio 的高性能服务网格数据平面项目,旨在提供高吞吐量、低延迟和可扩展的服务通信管理。它为企业级应用提供了丰富的流量治理功能,如负载均衡、熔断、限流等,并支持多协议代理(包括 HTTP/1.1, HTTP/2, gRPC)。***higress*** 的设计目标是优化 Istio 在大规模集群中的性能表现,满足高并发场景下的需求。";
|
||||
assert_eq!(message, res);
|
||||
assert_eq!(reasoning_message, res);
|
||||
}
|
||||
|
||||
fn raw_message(file_name: &str) -> String {
|
||||
if let Some(file) = Asset::get(file_name) {
|
||||
if let Ok(data) = std::str::from_utf8(file.data.as_ref()) {
|
||||
return data.to_string();
|
||||
}
|
||||
}
|
||||
String::new()
|
||||
}
|
||||
}
|
||||
@@ -1,56 +1,28 @@
|
||||
use higress_wasm_rust::event_stream::EventStream;
|
||||
use serde_json::json;
|
||||
|
||||
use crate::{Res, Usage};
|
||||
// Copyright (c) 2025 Alibaba Group Holding Ltd.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#[derive(Default)]
|
||||
pub(crate) struct MsgWindow {
|
||||
stream_parser: EventStream,
|
||||
pub(crate) message: Vec<u8>,
|
||||
usage: Usage,
|
||||
pub(crate) struct MessageWindow {
|
||||
message: Vec<u8>,
|
||||
}
|
||||
|
||||
impl MsgWindow {
|
||||
pub fn new() -> Self {
|
||||
MsgWindow::default()
|
||||
impl MessageWindow {
|
||||
pub(crate) fn update(&mut self, data: &[u8]) {
|
||||
self.message.extend(data);
|
||||
}
|
||||
|
||||
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> {
|
||||
pub(crate) fn pop(&mut self, char_window_size: usize, byte_window_size: usize) -> 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 {
|
||||
@@ -64,277 +36,31 @@ impl MsgWindow {
|
||||
.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()
|
||||
}
|
||||
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()
|
||||
}
|
||||
pub(crate) fn finish(&mut self) -> Vec<u8> {
|
||||
core::mem::take(&mut self.message)
|
||||
}
|
||||
pub(crate) fn iter_mut(&mut self) -> impl Iterator<Item = &mut Vec<u8>> {
|
||||
std::iter::once(&mut self.message)
|
||||
}
|
||||
}
|
||||
|
||||
#[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":{},"finish_reason":"stop"}],"usage":{}}
|
||||
|
||||
data: {"id":"chatcmpl-936","object":"chat.completion.chunk","created":1739872012,"model":"qwen2.5-coder:32b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{}}],"usage":{"prompt_tokens":372,"completion_tokens":9,"total_tokens":381}}
|
||||
|
||||
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 在大规模集群中的性能表现,满足高并发场景下的需求。");
|
||||
fn test_msg_window() {
|
||||
let mut msg_window = super::MessageWindow::default();
|
||||
msg_window.update(b"hello world");
|
||||
assert_eq!(msg_window.pop(5, 5), b"hello ");
|
||||
assert_eq!(msg_window.pop(5, 5), b"");
|
||||
msg_window.update(b"hello world");
|
||||
assert_eq!(msg_window.pop(5, 5), b"worldhello ");
|
||||
assert_eq!(msg_window.finish(), b"world");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
// Copyright (c) 2025 Alibaba Group Holding Ltd.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use serde_json::{json, Number, Value};
|
||||
|
||||
fn merge_number(target: &mut Value, add: &Value) {
|
||||
if target.is_null() {
|
||||
if add.is_object() {
|
||||
*target = json!({});
|
||||
} else if add.is_number() {
|
||||
*target = Value::from(0i64);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
match (target, add) {
|
||||
(Value::Object(t), Value::Object(a)) => {
|
||||
for (key, value) in a.iter() {
|
||||
if let Some(v) = t.get_mut(key) {
|
||||
merge_number(v, value);
|
||||
} else {
|
||||
t.insert(key.clone(), value.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
(Value::Number(t), Value::Number(a)) => {
|
||||
*t = Number::from(
|
||||
t.as_i64()
|
||||
.unwrap_or_default()
|
||||
.saturating_add(a.as_i64().unwrap_or_default()),
|
||||
);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
#[derive(Default, Clone)]
|
||||
pub(crate) struct NumberMerge {
|
||||
value: Value,
|
||||
}
|
||||
|
||||
impl NumberMerge {
|
||||
pub(crate) fn add(&mut self, number: &Value) {
|
||||
merge_number(&mut self.value, number);
|
||||
}
|
||||
pub(crate) fn finish(&mut self) -> Value {
|
||||
core::mem::replace(&mut self.value, Value::Null)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
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","reasoning_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","reasoning_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","reasoning_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","reasoning_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","reasoning_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","reasoning_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","reasoning_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","reasoning_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","reasoning_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","reasoning_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","reasoning_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","reasoning_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","reasoning_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","reasoning_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","reasoning_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","reasoning_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","reasoning_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","reasoning_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","reasoning_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","reasoning_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","reasoning_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","reasoning_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","reasoning_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","reasoning_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","reasoning_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","reasoning_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","reasoning_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","reasoning_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","reasoning_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","reasoning_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","reasoning_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","reasoning_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","reasoning_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","reasoning_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","reasoning_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","reasoning_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","reasoning_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","reasoning_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","reasoning_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","reasoning_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","reasoning_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","reasoning_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","reasoning_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","reasoning_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","reasoning_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","reasoning_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","reasoning_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","reasoning_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","reasoning_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","reasoning_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","reasoning_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","reasoning_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","reasoning_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","reasoning_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","reasoning_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","reasoning_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","reasoning_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","reasoning_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","reasoning_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","reasoning_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","reasoning_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","reasoning_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","reasoning_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","reasoning_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","reasoning_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","reasoning_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","reasoning_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","reasoning_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","reasoning_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","reasoning_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","reasoning_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","reasoning_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","reasoning_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","reasoning_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","reasoning_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","reasoning_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","reasoning_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","reasoning_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","reasoning_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","reasoning_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","reasoning_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","reasoning_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","reasoning_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","reasoning_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","reasoning_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","reasoning_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","reasoning_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","reasoning_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","reasoning_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","reasoning_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","reasoning_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","reasoning_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","reasoning_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","reasoning_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","reasoning_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","reasoning_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":"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":null,"finish_reason":"stop"}],"usage":null}
|
||||
data: {"id":"chatcmpl-936","object":"chat.completion.chunk","created":1739872012,"model":"qwen2.5-coder:32b","system_fingerprint":"fp_ollama","choices":[{"index":1,"delta":{},"finish_reason":"stop"}],"usage":{}}
|
||||
data: {"id":"chatcmpl-936","object":"chat.completion.chunk","created":1739872012,"model":"qwen2.5-coder:32b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{}}],"usage":{"prompt_tokens":372,"completion_tokens":9,"total_tokens":381}}
|
||||
data: [DONE]
|
||||
@@ -183,7 +183,7 @@ var RustWasmPluginsAiDataMasking = suite.ConformanceTest{
|
||||
"replace.raw.com",
|
||||
true,
|
||||
[]byte("{\"test\":[{\"test\":\"test\", \"test1\":\"127.0.0.1 admin@gmail.com sk-12345\"}]}"),
|
||||
[]byte("{\"res\":\"***.***.***.*** 48a7e98a91d93896d8dac522c5853948 ****@gmail.com\"}"),
|
||||
[]byte("{\"res\":\"***.***.***.*** c11e7177eb60c80cf983ddf8ca98f2dc1272d4c612204ce9bedd2460b18939cc ****@gmail.com\"}"),
|
||||
))
|
||||
|
||||
t.Run("WasmPlugins ai-data-masking", func(t *testing.T) {
|
||||
|
||||
@@ -62,7 +62,7 @@ spec:
|
||||
config:
|
||||
headers:
|
||||
- Content-Type=application/json
|
||||
"body": "{\"choices\":[{\"index\":0,\"message\":{\"role\":\"assistant\",\"content\":\"***.***.***.*** 48a7e98a91d93896d8dac522c5853948 ****@gmail.com\"}}],\"usage\":{}}"
|
||||
"body": "{\"choices\":[{\"index\":0,\"message\":{\"role\":\"assistant\",\"content\":\"***.***.***.*** c11e7177eb60c80cf983ddf8ca98f2dc1272d4c612204ce9bedd2460b18939cc ****@gmail.com\"}}],\"usage\":{}}"
|
||||
|
||||
- domain:
|
||||
- system_deny.openai.com
|
||||
@@ -93,7 +93,7 @@ spec:
|
||||
config:
|
||||
headers:
|
||||
- Content-Type=application/json
|
||||
"body": "{\"res\":\"***.***.***.*** 48a7e98a91d93896d8dac522c5853948 ****@gmail.com\"}"
|
||||
"body": "{\"res\":\"***.***.***.*** c11e7177eb60c80cf983ddf8ca98f2dc1272d4c612204ce9bedd2460b18939cc ****@gmail.com\"}"
|
||||
- domain:
|
||||
- system_deny.raw.com
|
||||
config:
|
||||
|
||||
Reference in New Issue
Block a user