[model-gateway] reduce cpu overhead in grpc router (#14663)
This commit is contained in:
@@ -74,7 +74,9 @@ fn merge_prefill_logprobs(
|
|||||||
) {
|
) {
|
||||||
// Only SGLang supports PD mode and has input_logprobs
|
// Only SGLang supports PD mode and has input_logprobs
|
||||||
if let Some(ProtoGenerateComplete::Sglang(prefill_first)) = prefill_responses.first() {
|
if let Some(ProtoGenerateComplete::Sglang(prefill_first)) = prefill_responses.first() {
|
||||||
if let Some(prefill_input_logprobs) = prefill_first.input_logprobs.clone() {
|
// Use ref to borrow input_logprobs instead of cloning upfront
|
||||||
|
// This avoids one allocation when the Option is Some
|
||||||
|
if let Some(ref prefill_input_logprobs) = prefill_first.input_logprobs {
|
||||||
for response in decode_responses.iter_mut() {
|
for response in decode_responses.iter_mut() {
|
||||||
if let ProtoGenerateComplete::Sglang(decode_resp) = response {
|
if let ProtoGenerateComplete::Sglang(decode_resp) = response {
|
||||||
decode_resp.input_logprobs = Some(prefill_input_logprobs.clone());
|
decode_resp.input_logprobs = Some(prefill_input_logprobs.clone());
|
||||||
|
|||||||
@@ -124,11 +124,9 @@ impl WorkerSelectionStage {
|
|||||||
false, // get all workers, we'll filter by is_available() next
|
false, // get all workers, we'll filter by is_available() next
|
||||||
);
|
);
|
||||||
|
|
||||||
let available: Vec<Arc<dyn Worker>> = workers
|
// Use into_iter() to take ownership of Arcs without cloning (avoids atomic inc/dec)
|
||||||
.iter()
|
let available: Vec<Arc<dyn Worker>> =
|
||||||
.filter(|w| w.is_available())
|
workers.into_iter().filter(|w| w.is_available()).collect();
|
||||||
.cloned()
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
if available.is_empty() {
|
if available.is_empty() {
|
||||||
return None;
|
return None;
|
||||||
|
|||||||
@@ -128,7 +128,7 @@ impl HarmonyResponseProcessor {
|
|||||||
.created(dispatch.created)
|
.created(dispatch.created)
|
||||||
.choices(choices)
|
.choices(choices)
|
||||||
.usage(usage)
|
.usage(usage)
|
||||||
.maybe_system_fingerprint(dispatch.weight_version.clone())
|
.maybe_system_fingerprint(dispatch.weight_version.as_deref())
|
||||||
.build(),
|
.build(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -459,7 +459,7 @@ impl HarmonyStreamingProcessor {
|
|||||||
)
|
)
|
||||||
.created(dispatch.created)
|
.created(dispatch.created)
|
||||||
.add_choice_role(index, "assistant")
|
.add_choice_role(index, "assistant")
|
||||||
.maybe_system_fingerprint(dispatch.weight_version.clone())
|
.maybe_system_fingerprint(dispatch.weight_version.as_deref())
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
let chunk_json = serde_json::to_string(&role_chunk)
|
let chunk_json = serde_json::to_string(&role_chunk)
|
||||||
@@ -499,7 +499,7 @@ impl HarmonyStreamingProcessor {
|
|||||||
finish_reason: None,
|
finish_reason: None,
|
||||||
matched_stop: None,
|
matched_stop: None,
|
||||||
})
|
})
|
||||||
.maybe_system_fingerprint(dispatch.weight_version.clone())
|
.maybe_system_fingerprint(dispatch.weight_version.as_deref())
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
let chunk_json = serde_json::to_string(&chunk)
|
let chunk_json = serde_json::to_string(&chunk)
|
||||||
@@ -525,7 +525,7 @@ impl HarmonyStreamingProcessor {
|
|||||||
ChatCompletionStreamResponse::builder(&dispatch.request_id, &original_request.model)
|
ChatCompletionStreamResponse::builder(&dispatch.request_id, &original_request.model)
|
||||||
.created(dispatch.created)
|
.created(dispatch.created)
|
||||||
.add_choice_finish_reason(index, finish_reason, matched_stop.cloned())
|
.add_choice_finish_reason(index, finish_reason, matched_stop.cloned())
|
||||||
.maybe_system_fingerprint(dispatch.weight_version.clone())
|
.maybe_system_fingerprint(dispatch.weight_version.as_deref())
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
let chunk_json = serde_json::to_string(&chunk)
|
let chunk_json = serde_json::to_string(&chunk)
|
||||||
@@ -555,7 +555,7 @@ impl HarmonyStreamingProcessor {
|
|||||||
total_tokens: prompt_tokens + completion_tokens,
|
total_tokens: prompt_tokens + completion_tokens,
|
||||||
completion_tokens_details: None,
|
completion_tokens_details: None,
|
||||||
})
|
})
|
||||||
.maybe_system_fingerprint(dispatch.weight_version.clone())
|
.maybe_system_fingerprint(dispatch.weight_version.as_deref())
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
let chunk_json = serde_json::to_string(&usage_chunk)
|
let chunk_json = serde_json::to_string(&usage_chunk)
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ impl ResponseProcessor {
|
|||||||
if original_request.separate_reasoning && reasoning_parser_available {
|
if original_request.separate_reasoning && reasoning_parser_available {
|
||||||
let pooled_parser = utils::get_reasoning_parser(
|
let pooled_parser = utils::get_reasoning_parser(
|
||||||
&self.reasoning_parser_factory,
|
&self.reasoning_parser_factory,
|
||||||
self.configured_reasoning_parser.as_ref(),
|
self.configured_reasoning_parser.as_deref(),
|
||||||
&original_request.model,
|
&original_request.model,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -227,7 +227,7 @@ impl ResponseProcessor {
|
|||||||
let reasoning_parser_available = chat_request.separate_reasoning
|
let reasoning_parser_available = chat_request.separate_reasoning
|
||||||
&& utils::check_reasoning_parser_availability(
|
&& utils::check_reasoning_parser_availability(
|
||||||
&self.reasoning_parser_factory,
|
&self.reasoning_parser_factory,
|
||||||
self.configured_reasoning_parser.as_ref(),
|
self.configured_reasoning_parser.as_deref(),
|
||||||
&chat_request.model,
|
&chat_request.model,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -240,7 +240,7 @@ impl ResponseProcessor {
|
|||||||
&& chat_request.tools.is_some()
|
&& chat_request.tools.is_some()
|
||||||
&& utils::check_tool_parser_availability(
|
&& utils::check_tool_parser_availability(
|
||||||
&self.tool_parser_factory,
|
&self.tool_parser_factory,
|
||||||
self.configured_tool_parser.as_ref(),
|
self.configured_tool_parser.as_deref(),
|
||||||
&chat_request.model,
|
&chat_request.model,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -308,7 +308,7 @@ impl ResponseProcessor {
|
|||||||
// Get pooled parser for this model
|
// Get pooled parser for this model
|
||||||
let pooled_parser = utils::get_tool_parser(
|
let pooled_parser = utils::get_tool_parser(
|
||||||
&self.tool_parser_factory,
|
&self.tool_parser_factory,
|
||||||
self.configured_tool_parser.as_ref(),
|
self.configured_tool_parser.as_deref(),
|
||||||
model,
|
model,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -204,7 +204,7 @@ impl StreamingProcessor {
|
|||||||
let reasoning_parser_available = separate_reasoning
|
let reasoning_parser_available = separate_reasoning
|
||||||
&& utils::check_reasoning_parser_availability(
|
&& utils::check_reasoning_parser_availability(
|
||||||
&self.reasoning_parser_factory,
|
&self.reasoning_parser_factory,
|
||||||
self.configured_reasoning_parser.as_ref(),
|
self.configured_reasoning_parser.as_deref(),
|
||||||
model,
|
model,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -222,7 +222,7 @@ impl StreamingProcessor {
|
|||||||
let tool_parser_available = tools.is_some()
|
let tool_parser_available = tools.is_some()
|
||||||
&& utils::check_tool_parser_availability(
|
&& utils::check_tool_parser_availability(
|
||||||
&self.tool_parser_factory,
|
&self.tool_parser_factory,
|
||||||
self.configured_tool_parser.as_ref(),
|
self.configured_tool_parser.as_deref(),
|
||||||
model,
|
model,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -300,7 +300,7 @@ impl StreamingProcessor {
|
|||||||
let first_chunk = ChatCompletionStreamResponse::builder(request_id, model)
|
let first_chunk = ChatCompletionStreamResponse::builder(request_id, model)
|
||||||
.created(created)
|
.created(created)
|
||||||
.add_choice_role(index, "assistant")
|
.add_choice_role(index, "assistant")
|
||||||
.maybe_system_fingerprint(system_fingerprint.map(|s| s.to_string()))
|
.maybe_system_fingerprint(system_fingerprint)
|
||||||
.build();
|
.build();
|
||||||
Self::format_sse_chunk_into(&mut sse_buffer, &first_chunk);
|
Self::format_sse_chunk_into(&mut sse_buffer, &first_chunk);
|
||||||
tx.send(Ok(Bytes::from(sse_buffer.clone())))
|
tx.send(Ok(Bytes::from(sse_buffer.clone())))
|
||||||
@@ -419,9 +419,7 @@ impl StreamingProcessor {
|
|||||||
ChatCompletionStreamResponse::builder(request_id, model)
|
ChatCompletionStreamResponse::builder(request_id, model)
|
||||||
.created(created)
|
.created(created)
|
||||||
.add_choice_content(index, "assistant", text)
|
.add_choice_content(index, "assistant", text)
|
||||||
.maybe_system_fingerprint(
|
.maybe_system_fingerprint(system_fingerprint)
|
||||||
system_fingerprint.map(|s| s.to_string()),
|
|
||||||
)
|
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
let sse_chunk =
|
let sse_chunk =
|
||||||
@@ -489,7 +487,7 @@ impl StreamingProcessor {
|
|||||||
let tool_chunk = ChatCompletionStreamResponse::builder(request_id, model)
|
let tool_chunk = ChatCompletionStreamResponse::builder(request_id, model)
|
||||||
.created(created)
|
.created(created)
|
||||||
.add_choice_tool_call_delta(*index, tool_call_delta)
|
.add_choice_tool_call_delta(*index, tool_call_delta)
|
||||||
.maybe_system_fingerprint(system_fingerprint.map(|s| s.to_string()))
|
.maybe_system_fingerprint(system_fingerprint)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
let sse_chunk = serde_json::to_string(&tool_chunk)
|
let sse_chunk = serde_json::to_string(&tool_chunk)
|
||||||
@@ -514,7 +512,7 @@ impl StreamingProcessor {
|
|||||||
let finish_chunk = ChatCompletionStreamResponse::builder(request_id, model)
|
let finish_chunk = ChatCompletionStreamResponse::builder(request_id, model)
|
||||||
.created(created)
|
.created(created)
|
||||||
.add_choice_finish_reason(*index, final_finish_reason, matched_stop_value)
|
.add_choice_finish_reason(*index, final_finish_reason, matched_stop_value)
|
||||||
.maybe_system_fingerprint(system_fingerprint.map(|s| s.to_string()))
|
.maybe_system_fingerprint(system_fingerprint)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
let sse_chunk = serde_json::to_string(&finish_chunk)
|
let sse_chunk = serde_json::to_string(&finish_chunk)
|
||||||
@@ -537,7 +535,7 @@ impl StreamingProcessor {
|
|||||||
total_tokens: total_prompt + total_completion,
|
total_tokens: total_prompt + total_completion,
|
||||||
completion_tokens_details: None,
|
completion_tokens_details: None,
|
||||||
})
|
})
|
||||||
.maybe_system_fingerprint(system_fingerprint.map(|s| s.to_string()))
|
.maybe_system_fingerprint(system_fingerprint)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
let sse_chunk = serde_json::to_string(&usage_chunk)
|
let sse_chunk = serde_json::to_string(&usage_chunk)
|
||||||
@@ -1023,7 +1021,7 @@ impl StreamingProcessor {
|
|||||||
reasoning_parsers.entry(index).or_insert_with(|| {
|
reasoning_parsers.entry(index).or_insert_with(|| {
|
||||||
let parser = utils::create_reasoning_parser(
|
let parser = utils::create_reasoning_parser(
|
||||||
&self.reasoning_parser_factory,
|
&self.reasoning_parser_factory,
|
||||||
self.configured_reasoning_parser.as_ref(),
|
self.configured_reasoning_parser.as_deref(),
|
||||||
model,
|
model,
|
||||||
)
|
)
|
||||||
.expect("Parser should be available - checked upfront");
|
.expect("Parser should be available - checked upfront");
|
||||||
@@ -1048,7 +1046,7 @@ impl StreamingProcessor {
|
|||||||
ChatCompletionStreamResponse::builder(request_id, model)
|
ChatCompletionStreamResponse::builder(request_id, model)
|
||||||
.created(created)
|
.created(created)
|
||||||
.add_choice_reasoning(index, reasoning_text)
|
.add_choice_reasoning(index, reasoning_text)
|
||||||
.maybe_system_fingerprint(system_fingerprint.map(|s| s.to_string()))
|
.maybe_system_fingerprint(system_fingerprint)
|
||||||
.build(),
|
.build(),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
@@ -1098,7 +1096,7 @@ impl StreamingProcessor {
|
|||||||
ChatCompletionStreamResponse::builder(request_id, model)
|
ChatCompletionStreamResponse::builder(request_id, model)
|
||||||
.created(created)
|
.created(created)
|
||||||
.add_choice_tool_name(index, tool_call_id, function.name.clone())
|
.add_choice_tool_name(index, tool_call_id, function.name.clone())
|
||||||
.maybe_system_fingerprint(system_fingerprint.map(|s| s.to_string()))
|
.maybe_system_fingerprint(system_fingerprint)
|
||||||
.build(),
|
.build(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1109,7 +1107,7 @@ impl StreamingProcessor {
|
|||||||
ChatCompletionStreamResponse::builder(request_id, model)
|
ChatCompletionStreamResponse::builder(request_id, model)
|
||||||
.created(created)
|
.created(created)
|
||||||
.add_choice_tool_args(index, delta.to_string())
|
.add_choice_tool_args(index, delta.to_string())
|
||||||
.maybe_system_fingerprint(system_fingerprint.map(|s| s.to_string()))
|
.maybe_system_fingerprint(system_fingerprint)
|
||||||
.build(),
|
.build(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1139,16 +1137,12 @@ impl StreamingProcessor {
|
|||||||
// Create fresh parser for this index (not pooled, to avoid state pollution)
|
// Create fresh parser for this index (not pooled, to avoid state pollution)
|
||||||
tool_parsers.entry(index).or_insert_with(|| {
|
tool_parsers.entry(index).or_insert_with(|| {
|
||||||
let parser = if use_json_parser {
|
let parser = if use_json_parser {
|
||||||
utils::create_tool_parser(
|
utils::create_tool_parser(&self.tool_parser_factory, Some("json"), model)
|
||||||
&self.tool_parser_factory,
|
.expect("JSON parser should be available")
|
||||||
Some(&"json".to_string()),
|
|
||||||
model,
|
|
||||||
)
|
|
||||||
.expect("JSON parser should be available")
|
|
||||||
} else {
|
} else {
|
||||||
utils::create_tool_parser(
|
utils::create_tool_parser(
|
||||||
&self.tool_parser_factory,
|
&self.tool_parser_factory,
|
||||||
self.configured_tool_parser.as_ref(),
|
self.configured_tool_parser.as_deref(),
|
||||||
model,
|
model,
|
||||||
)
|
)
|
||||||
.expect("Parser should be available - checked upfront")
|
.expect("Parser should be available - checked upfront")
|
||||||
@@ -1167,7 +1161,7 @@ impl StreamingProcessor {
|
|||||||
ChatCompletionStreamResponse::builder(request_id, model)
|
ChatCompletionStreamResponse::builder(request_id, model)
|
||||||
.created(created)
|
.created(created)
|
||||||
.add_choice_content(index, "assistant", normal_text)
|
.add_choice_content(index, "assistant", normal_text)
|
||||||
.maybe_system_fingerprint(system_fingerprint.map(|s| s.to_string()))
|
.maybe_system_fingerprint(system_fingerprint)
|
||||||
.build(),
|
.build(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1209,7 +1203,7 @@ impl StreamingProcessor {
|
|||||||
ChatCompletionStreamResponse::builder(request_id, model)
|
ChatCompletionStreamResponse::builder(request_id, model)
|
||||||
.created(created)
|
.created(created)
|
||||||
.add_choice_tool_call_delta(index, tool_call_delta)
|
.add_choice_tool_call_delta(index, tool_call_delta)
|
||||||
.maybe_system_fingerprint(system_fingerprint.map(|s| s.to_string()))
|
.maybe_system_fingerprint(system_fingerprint)
|
||||||
.build(),
|
.build(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -197,7 +197,7 @@ pub fn generate_tool_constraints(
|
|||||||
// Return the tool's parameters schema directly (not wrapped in array)
|
// Return the tool's parameters schema directly (not wrapped in array)
|
||||||
let params_schema = serde_json::to_string(&tool.function.parameters)
|
let params_schema = serde_json::to_string(&tool.function.parameters)
|
||||||
.map_err(|e| format!("Failed to serialize tool parameters: {}", e))?;
|
.map_err(|e| format!("Failed to serialize tool parameters: {}", e))?;
|
||||||
Ok(Some(("json_schema".to_string(), params_schema)))
|
Ok(Some((String::from("json_schema"), params_schema)))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Required: Array of tool calls with minItems: 1
|
// Required: Array of tool calls with minItems: 1
|
||||||
@@ -673,7 +673,13 @@ pub fn generate_tool_call_id(
|
|||||||
tool_index: usize,
|
tool_index: usize,
|
||||||
history_count: usize,
|
history_count: usize,
|
||||||
) -> String {
|
) -> String {
|
||||||
if model.to_lowercase().contains("kimi") {
|
// Case-insensitive check without allocation (search for "kimi" substring)
|
||||||
|
let is_kimi = model
|
||||||
|
.as_bytes()
|
||||||
|
.windows(4) // "kimi".len()
|
||||||
|
.any(|window| window.eq_ignore_ascii_case(b"kimi"));
|
||||||
|
|
||||||
|
if is_kimi {
|
||||||
// KimiK2 format: functions.{name}:{global_index}
|
// KimiK2 format: functions.{name}:{global_index}
|
||||||
format!("functions.{}:{}", tool_name, history_count + tool_index)
|
format!("functions.{}:{}", tool_name, history_count + tool_index)
|
||||||
} else {
|
} else {
|
||||||
@@ -685,7 +691,7 @@ pub fn generate_tool_call_id(
|
|||||||
/// Check if a reasoning parser is available for the given model
|
/// Check if a reasoning parser is available for the given model
|
||||||
pub fn check_reasoning_parser_availability(
|
pub fn check_reasoning_parser_availability(
|
||||||
reasoning_parser_factory: &ReasoningParserFactory,
|
reasoning_parser_factory: &ReasoningParserFactory,
|
||||||
configured_parser: Option<&String>,
|
configured_parser: Option<&str>,
|
||||||
model: &str,
|
model: &str,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
if let Some(parser_name) = configured_parser {
|
if let Some(parser_name) = configured_parser {
|
||||||
@@ -700,7 +706,7 @@ pub fn check_reasoning_parser_availability(
|
|||||||
/// Check if a tool parser is available for the given model
|
/// Check if a tool parser is available for the given model
|
||||||
pub fn check_tool_parser_availability(
|
pub fn check_tool_parser_availability(
|
||||||
tool_parser_factory: &ToolParserFactory,
|
tool_parser_factory: &ToolParserFactory,
|
||||||
configured_parser: Option<&String>,
|
configured_parser: Option<&str>,
|
||||||
model: &str,
|
model: &str,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
if let Some(parser_name) = configured_parser {
|
if let Some(parser_name) = configured_parser {
|
||||||
@@ -717,7 +723,7 @@ pub fn check_tool_parser_availability(
|
|||||||
/// Get a pooled reasoning parser (for non-streaming where state doesn't matter)
|
/// Get a pooled reasoning parser (for non-streaming where state doesn't matter)
|
||||||
pub fn get_reasoning_parser(
|
pub fn get_reasoning_parser(
|
||||||
reasoning_parser_factory: &ReasoningParserFactory,
|
reasoning_parser_factory: &ReasoningParserFactory,
|
||||||
configured_parser: Option<&String>,
|
configured_parser: Option<&str>,
|
||||||
model: &str,
|
model: &str,
|
||||||
) -> ReasoningPooledParser {
|
) -> ReasoningPooledParser {
|
||||||
if let Some(parser_name) = configured_parser {
|
if let Some(parser_name) = configured_parser {
|
||||||
@@ -741,7 +747,7 @@ pub fn get_reasoning_parser(
|
|||||||
/// Create a fresh reasoning parser instance (for streaming where state isolation is needed)
|
/// Create a fresh reasoning parser instance (for streaming where state isolation is needed)
|
||||||
pub fn create_reasoning_parser(
|
pub fn create_reasoning_parser(
|
||||||
reasoning_parser_factory: &ReasoningParserFactory,
|
reasoning_parser_factory: &ReasoningParserFactory,
|
||||||
configured_parser: Option<&String>,
|
configured_parser: Option<&str>,
|
||||||
model: &str,
|
model: &str,
|
||||||
) -> Option<Box<dyn ReasoningParser>> {
|
) -> Option<Box<dyn ReasoningParser>> {
|
||||||
if let Some(parser_name) = configured_parser {
|
if let Some(parser_name) = configured_parser {
|
||||||
@@ -769,7 +775,7 @@ pub fn create_reasoning_parser(
|
|||||||
/// Get a pooled tool parser (for non-streaming where state doesn't matter)
|
/// Get a pooled tool parser (for non-streaming where state doesn't matter)
|
||||||
pub fn get_tool_parser(
|
pub fn get_tool_parser(
|
||||||
tool_parser_factory: &ToolParserFactory,
|
tool_parser_factory: &ToolParserFactory,
|
||||||
configured_parser: Option<&String>,
|
configured_parser: Option<&str>,
|
||||||
model: &str,
|
model: &str,
|
||||||
) -> ToolPooledParser {
|
) -> ToolPooledParser {
|
||||||
if let Some(parser_name) = configured_parser {
|
if let Some(parser_name) = configured_parser {
|
||||||
@@ -793,7 +799,7 @@ pub fn get_tool_parser(
|
|||||||
/// Create a fresh tool parser instance (for streaming where state isolation is needed)
|
/// Create a fresh tool parser instance (for streaming where state isolation is needed)
|
||||||
pub fn create_tool_parser(
|
pub fn create_tool_parser(
|
||||||
tool_parser_factory: &ToolParserFactory,
|
tool_parser_factory: &ToolParserFactory,
|
||||||
configured_parser: Option<&String>,
|
configured_parser: Option<&str>,
|
||||||
model: &str,
|
model: &str,
|
||||||
) -> Option<Box<dyn ToolParser>> {
|
) -> Option<Box<dyn ToolParser>> {
|
||||||
if let Some(parser_name) = configured_parser {
|
if let Some(parser_name) = configured_parser {
|
||||||
|
|||||||
Reference in New Issue
Block a user