diff --git a/arch/Dockerfile b/arch/Dockerfile index 2c3c8744..98a113ad 100644 --- a/arch/Dockerfile +++ b/arch/Dockerfile @@ -28,4 +28,5 @@ COPY arch/arch_config_schema.yaml . RUN pip install requests RUN touch /var/log/envoy.log +# ENTRYPOINT ["sh","-c", "python config_generator.py && envsubst < /etc/envoy/envoy.yaml > /etc/envoy.env_sub.yaml && envoy -c /etc/envoy.env_sub.yaml --log-level trace 2>&1 | tee /var/log/envoy.log"] ENTRYPOINT ["sh","-c", "python config_generator.py && envsubst < /etc/envoy/envoy.yaml > /etc/envoy.env_sub.yaml && envoy -c /etc/envoy.env_sub.yaml --component-log-level wasm:debug 2>&1 | tee /var/log/envoy.log"] diff --git a/arch/arch_config_schema.yaml b/arch/arch_config_schema.yaml index cab5cf17..59276589 100644 --- a/arch/arch_config_schema.yaml +++ b/arch/arch_config_schema.yaml @@ -93,7 +93,6 @@ properties: additionalProperties: false required: - name - - model overrides: type: object properties: @@ -101,6 +100,8 @@ properties: type: number optimize_context_window: type: boolean + use_agent_orchestrator: + type: boolean system_prompt: type: string prompt_targets: diff --git a/arch/envoy.template.yaml b/arch/envoy.template.yaml index ca722b7c..cac17187 100644 --- a/arch/envoy.template.yaml +++ b/arch/envoy.template.yaml @@ -142,6 +142,19 @@ static_resources: cluster: {{ llm_cluster_name }} timeout: 60s {% endfor %} + + {% if agent_orchestrator %} + - match: + prefix: "/" + headers: + - name: "x-arch-llm-provider" + string_match: + exact: {{ agent_orchestrator }} + route: + auto_host_rewrite: true + cluster: {{ agent_orchestrator }} + timeout: 60s + {% endif %} http_filters: - name: envoy.filters.http.compressor typed_config: diff --git a/arch/tools/cli/config_generator.py b/arch/tools/cli/config_generator.py index f0aae28a..44ceda27 100644 --- a/arch/tools/cli/config_generator.py +++ b/arch/tools/cli/config_generator.py @@ -48,7 +48,7 @@ def validate_and_render_schema(): arch_config_schema = file.read() config_yaml = yaml.safe_load(arch_config) - config_schema_yaml = yaml.safe_load(arch_config_schema) + _ = yaml.safe_load(arch_config_schema) inferred_clusters = {} endpoints = config_yaml.get("endpoints", {}) @@ -150,6 +150,26 @@ def validate_and_render_schema(): if llm_gateway_listener.get("timeout") == None: llm_gateway_listener["timeout"] = "10s" + use_agent_orchestrator = config_yaml.get("overrides", {}).get( + "use_agent_orchestrator", False + ) + + agent_orchestrator = None + if use_agent_orchestrator: + print("Using agent orchestrator") + + if len(endpoints) == 0: + raise Exception( + "Please provide agent orchestrator in the endpoints section in your arch_config.yaml file" + ) + elif len(endpoints) > 1: + raise Exception( + "Please provide single agent orchestrator in the endpoints section in your arch_config.yaml file" + ) + else: + agent_orchestrator = list(endpoints.keys())[0] + + print("agent_orchestrator: ", agent_orchestrator) data = { "prompt_gateway_listener": prompt_gateway_listener, "llm_gateway_listener": llm_gateway_listener, @@ -159,6 +179,7 @@ def validate_and_render_schema(): "arch_llm_providers": config_yaml["llm_providers"], "arch_tracing": arch_tracing, "local_llms": llms_with_endpoint, + "agent_orchestrator": agent_orchestrator, } rendered = template.render(data) diff --git a/crates/common/src/api/open_ai.rs b/crates/common/src/api/open_ai.rs index f318ebc4..d71b0d58 100644 --- a/crates/common/src/api/open_ai.rs +++ b/crates/common/src/api/open_ai.rs @@ -189,7 +189,7 @@ pub struct ToolCall { #[derive(Debug, Clone, Serialize, Deserialize)] pub struct FunctionCallDetail { pub name: String, - pub arguments: HashMap, + pub arguments: Option>, } #[derive(Debug, Deserialize, Serialize)] diff --git a/crates/common/src/configuration.rs b/crates/common/src/configuration.rs index 1b954c3d..2065b1aa 100644 --- a/crates/common/src/configuration.rs +++ b/crates/common/src/configuration.rs @@ -25,6 +25,7 @@ pub struct Configuration { pub struct Overrides { pub prompt_target_intent_matching_threshold: Option, pub optimize_context_window: Option, + pub use_agent_orchestrator: Option, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] @@ -159,7 +160,7 @@ pub struct LlmProvider { pub name: String, pub provider_interface: LlmProviderType, pub access_key: Option, - pub model: String, + pub model: Option, pub default: Option, pub stream: Option, pub endpoint: Option, diff --git a/crates/llm_gateway/src/filter_context.rs b/crates/llm_gateway/src/filter_context.rs index 0edba456..fc31355a 100644 --- a/crates/llm_gateway/src/filter_context.rs +++ b/crates/llm_gateway/src/filter_context.rs @@ -1,6 +1,7 @@ use crate::metrics::Metrics; use crate::stream_context::StreamContext; use common::configuration::Configuration; +use common::configuration::Overrides; use common::consts::OTEL_COLLECTOR_HTTP; use common::consts::OTEL_POST_PATH; use common::http::CallArgs; @@ -31,6 +32,7 @@ pub struct FilterContext { callouts: RefCell>, llm_providers: Option>, traces_queue: Arc>>, + overrides: Rc>, } impl FilterContext { @@ -40,6 +42,7 @@ impl FilterContext { metrics: Rc::new(Metrics::new()), llm_providers: None, traces_queue: Arc::new(Mutex::new(VecDeque::new())), + overrides: Rc::new(None), } } } @@ -69,6 +72,7 @@ impl RootContext for FilterContext { }; ratelimit::ratelimits(Some(config.ratelimits.unwrap_or_default())); + self.overrides = Rc::new(config.overrides); match config.llm_providers.try_into() { Ok(llm_providers) => self.llm_providers = Some(Rc::new(llm_providers)), @@ -93,6 +97,7 @@ impl RootContext for FilterContext { .expect("LLM Providers must exist when Streams are being created"), ), Arc::clone(&self.traces_queue), + Rc::clone(&self.overrides), ))) } diff --git a/crates/llm_gateway/src/stream_context.rs b/crates/llm_gateway/src/stream_context.rs index 8e879eda..b4a05575 100644 --- a/crates/llm_gateway/src/stream_context.rs +++ b/crates/llm_gateway/src/stream_context.rs @@ -3,7 +3,7 @@ use common::api::open_ai::{ ChatCompletionStreamResponseServerEvents, ChatCompletionsRequest, ChatCompletionsResponse, Message, StreamOptions, }; -use common::configuration::LlmProvider; +use common::configuration::{LlmProvider, LlmProviderType, Overrides}; use common::consts::{ ARCH_PROVIDER_HINT_HEADER, ARCH_ROUTING_HEADER, CHAT_COMPLETIONS_PATH, HEALTHZ_PATH, RATELIMIT_SELECTOR_HEADER_KEY, REQUEST_ID_HEADER, TRACE_PARENT_HEADER, @@ -42,6 +42,7 @@ pub struct StreamContext { request_body_sent_time: Option, user_message: Option, traces_queue: Arc>>, + overrides: Rc>, } impl StreamContext { @@ -50,10 +51,12 @@ impl StreamContext { metrics: Rc, llm_providers: Rc, traces_queue: Arc>>, + overrides: Rc>, ) -> Self { StreamContext { context_id, metrics, + overrides, ratelimit_selector: None, streaming_response: false, response_tokens: 0, @@ -91,7 +94,12 @@ impl StreamContext { self.get_http_request_header(ARCH_PROVIDER_HINT_HEADER) .unwrap_or_default(), self.llm_provider.as_ref().unwrap().name, - self.llm_provider.as_ref().unwrap().model + self.llm_provider + .as_ref() + .unwrap() + .model + .as_ref() + .unwrap_or(&String::new()) ); } @@ -151,11 +159,11 @@ impl StreamContext { // Tokenize and record token count. let token_count = tokenizer::token_count(model, json_string).unwrap_or(0); + trace!("Recorded input token count: {}", token_count); // Record the token count to metrics. self.metrics .input_sequence_length .record(token_count as u64); - trace!("Recorded input token count: {}", token_count); // Check if rate limiting needs to be applied. if let Some(selector) = self.ratelimit_selector.take() { @@ -184,24 +192,41 @@ impl HttpContext for StreamContext { return Action::Continue; } - self.select_llm_provider(); + let routing_header_value = self.get_http_request_header(ARCH_ROUTING_HEADER); - // if endpoint is not set then use provider name as routing header so envoy can resolve the cluster name - if self.llm_provider().endpoint.is_none() { + let use_agent_orchestrator = match self.overrides.as_ref() { + Some(overrides) => overrides.use_agent_orchestrator.unwrap_or_default(), + None => false, + }; + + if let Some(routing_header_value) = routing_header_value.as_ref() { + debug!("routing header already set: {}", routing_header_value); + self.llm_provider = Some(Rc::new(LlmProvider { + name: routing_header_value.to_string(), + provider_interface: LlmProviderType::OpenAI, + access_key: None, + endpoint: None, + model: None, + default: None, + stream: None, + port: None, + rate_limits: None, + })); + } else { + self.select_llm_provider(); self.add_http_request_header( ARCH_ROUTING_HEADER, &self.llm_provider().provider_interface.to_string(), ); - } else { - self.add_http_request_header(ARCH_ROUTING_HEADER, &self.llm_provider().name); - } - - if let Err(error) = self.modify_auth_headers() { - // ensure that the provider has an endpoint if the access key is missing else return a bad request - if self.llm_provider.as_ref().unwrap().endpoint.is_none() { - self.send_server_error(error, Some(StatusCode::BAD_REQUEST)); + if let Err(error) = self.modify_auth_headers() { + // ensure that the provider has an endpoint if the access key is missing else return a bad request + if self.llm_provider.as_ref().unwrap().endpoint.is_none() && !use_agent_orchestrator + { + self.send_server_error(error, Some(StatusCode::BAD_REQUEST)); + } } } + self.delete_content_length_header(); self.save_ratelimit_header(); @@ -230,34 +255,38 @@ impl HttpContext for StreamContext { return Action::Continue; } + let body_bytes = match self.get_http_request_body(0, body_size) { + Some(body_bytes) => body_bytes, + None => { + self.send_server_error( + ServerError::LogicError(format!( + "Failed to obtain body bytes even though body_size is {}", + body_size + )), + None, + ); + return Action::Pause; + } + }; + // Deserialize body into spec. // Currently OpenAI API. let mut deserialized_body: ChatCompletionsRequest = - match self.get_http_request_body(0, body_size) { - Some(body_bytes) => match serde_json::from_slice(&body_bytes) { - Ok(deserialized) => deserialized, - Err(e) => { - self.send_server_error( - ServerError::Deserialization(e), - Some(StatusCode::BAD_REQUEST), - ); - return Action::Pause; - } - }, - None => { + match serde_json::from_slice(&body_bytes) { + Ok(deserialized) => deserialized, + Err(e) => { + debug!("body str: {}", String::from_utf8_lossy(&body_bytes)); self.send_server_error( - ServerError::LogicError(format!( - "Failed to obtain body bytes even though body_size is {}", - body_size - )), - None, + ServerError::Deserialization(e), + Some(StatusCode::BAD_REQUEST), ); return Action::Pause; } }; // remove metadata from the request body - deserialized_body.metadata = None; + //TODO: move this to prompt gateway + // deserialized_body.metadata = None; // delete model key from message array for message in deserialized_body.messages.iter_mut() { message.model = None; @@ -270,10 +299,16 @@ impl HttpContext for StreamContext { .last() .cloned(); - // override model name from the llm provider - deserialized_body - .model - .clone_from(&self.llm_provider.as_ref().unwrap().model); + let model_name = match self.llm_provider.as_ref() { + Some(llm_provider) => match llm_provider.model.as_ref() { + Some(model) => model, + None => "--", + }, + None => "--", + }; + + deserialized_body.model = model_name.to_string(); + let chat_completion_request_str = serde_json::to_string(&deserialized_body).unwrap(); trace!( @@ -469,6 +504,10 @@ impl HttpContext for StreamContext { }; if self.streaming_response { + if body_utf8 == "data: [DONE]\n" { + return Action::Continue; + } + let chat_completions_chunk_response_events = match ChatCompletionStreamResponseServerEvents::try_from(body_utf8.as_str()) { Ok(response) => response, @@ -482,7 +521,10 @@ impl HttpContext for StreamContext { }; if chat_completions_chunk_response_events.events.is_empty() { - debug!("empty streaming response"); + debug!( + "cound't parse any streaming events: body str: {}", + body_utf8 + ); return Action::Continue; } diff --git a/crates/llm_gateway/tests/integration.rs b/crates/llm_gateway/tests/integration.rs index b18a0d29..71ec2e16 100644 --- a/crates/llm_gateway/tests/integration.rs +++ b/crates/llm_gateway/tests/integration.rs @@ -20,6 +20,11 @@ fn request_headers_expectations(module: &mut Tester, http_context: i32) { .call_proxy_on_request_headers(http_context, 0, false) .expect_get_header_map_value(Some(MapType::HttpRequestHeaders), Some(":path")) .returning(Some("/v1/chat/completions")) + .expect_get_header_map_value( + Some(MapType::HttpRequestHeaders), + Some("x-arch-llm-provider"), + ) + .returning(None) .expect_get_header_map_value( Some(MapType::HttpRequestHeaders), Some("x-arch-llm-provider-hint"), @@ -36,6 +41,7 @@ fn request_headers_expectations(module: &mut Tester, http_context: i32) { Some("Authorization"), Some("Bearer secret_key"), ) + .expect_remove_header_map_value(Some(MapType::HttpRequestHeaders), Some("content-length")) .expect_get_header_map_value( Some(MapType::HttpRequestHeaders), Some("x-arch-llm-provider-hint"), @@ -48,8 +54,6 @@ fn request_headers_expectations(module: &mut Tester, http_context: i32) { .returning(Some("selector-key")) .expect_get_header_map_value(Some(MapType::HttpRequestHeaders), Some("selector-key")) .returning(Some("selector-value")) - .expect_get_header_map_pairs(Some(MapType::HttpRequestHeaders)) - .returning(None) .expect_get_header_map_value(Some(MapType::HttpRequestHeaders), Some(":path")) .returning(Some("/v1/chat/completions")) .expect_get_header_map_value(Some(MapType::HttpRequestHeaders), Some("x-request-id")) @@ -223,8 +227,8 @@ fn llm_gateway_successful_request_to_open_ai_chat_completions() { .returning(Some(chat_completions_request_body)) .expect_log(Some(LogLevel::Trace), None) .expect_log(Some(LogLevel::Trace), None) - .expect_metric_record("input_sequence_length", 21) .expect_log(Some(LogLevel::Trace), None) + .expect_metric_record("input_sequence_length", 21) .expect_log(Some(LogLevel::Debug), None) .expect_log(Some(LogLevel::Debug), None) .expect_set_buffer_bytes(Some(BufferType::HttpRequestBody), None) @@ -266,7 +270,7 @@ fn llm_gateway_bad_request_to_open_ai_chat_completions() { {\ \"messages\": [\ {\ - \"role\": \"system\",\ + \"role\": \"system\"\ },\ {\ \"role\": \"user\",\ @@ -283,14 +287,19 @@ fn llm_gateway_bad_request_to_open_ai_chat_completions() { ) .expect_get_buffer_bytes(Some(BufferType::HttpRequestBody)) .returning(Some(incomplete_chat_completions_request_body)) - .expect_log(Some(LogLevel::Debug), None) + .expect_log(Some(LogLevel::Trace), None) .expect_send_local_response( Some(StatusCode::BAD_REQUEST.as_u16().into()), None, None, None, ) - .execute_and_expect(ReturnType::Action(Action::Pause)) + .expect_log(Some(LogLevel::Trace), None) + .expect_log(Some(LogLevel::Trace), None) + .expect_metric_record("input_sequence_length", 14) + .expect_log(Some(LogLevel::Debug), None) + .expect_log(Some(LogLevel::Debug), None) + .execute_and_expect(ReturnType::Action(Action::Continue)) .unwrap(); } diff --git a/crates/prompt_gateway/src/filter_context.rs b/crates/prompt_gateway/src/filter_context.rs index f782dea2..d5368686 100644 --- a/crates/prompt_gateway/src/filter_context.rs +++ b/crates/prompt_gateway/src/filter_context.rs @@ -1,6 +1,8 @@ use crate::metrics::Metrics; use crate::stream_context::StreamContext; -use common::configuration::{Configuration, Overrides, PromptGuards, PromptTarget, Tracing}; +use common::configuration::{ + Configuration, Endpoint, Overrides, PromptGuards, PromptTarget, Tracing, +}; use common::http::Client; use common::stats::Gauge; use log::trace; @@ -21,6 +23,7 @@ pub struct FilterContext { overrides: Rc>, system_prompt: Rc>, prompt_targets: Rc>, + endpoints: Rc>>, prompt_guards: Rc, tracing: Rc>, } @@ -34,6 +37,7 @@ impl FilterContext { prompt_targets: Rc::new(HashMap::new()), overrides: Rc::new(None), prompt_guards: Rc::new(PromptGuards::default()), + endpoints: Rc::new(None), tracing: Rc::new(None), } } @@ -73,6 +77,7 @@ impl RootContext for FilterContext { } self.system_prompt = Rc::new(config.system_prompt); self.prompt_targets = Rc::new(prompt_targets); + self.endpoints = Rc::new(config.endpoints); if let Some(prompt_guards) = config.prompt_guards { self.prompt_guards = Rc::new(prompt_guards) @@ -94,6 +99,7 @@ impl RootContext for FilterContext { Rc::clone(&self.metrics), Rc::clone(&self.system_prompt), Rc::clone(&self.prompt_targets), + Rc::clone(&self.endpoints), Rc::clone(&self.overrides), Rc::clone(&self.tracing), ))) diff --git a/crates/prompt_gateway/src/http_context.rs b/crates/prompt_gateway/src/http_context.rs index 53a2d25b..b8eb2797 100644 --- a/crates/prompt_gateway/src/http_context.rs +++ b/crates/prompt_gateway/src/http_context.rs @@ -4,7 +4,7 @@ use common::{ self, ArchState, ChatCompletionStreamResponse, ChatCompletionTool, ChatCompletionsRequest, }, consts::{ - ARCH_FC_MODEL_NAME, ARCH_INTERNAL_CLUSTER_NAME, ARCH_STATE_HEADER, + ARCH_FC_MODEL_NAME, ARCH_INTERNAL_CLUSTER_NAME, ARCH_ROUTING_HEADER, ARCH_STATE_HEADER, ARCH_UPSTREAM_HOST_HEADER, ASSISTANT_ROLE, CHAT_COMPLETIONS_PATH, HEALTHZ_PATH, MODEL_SERVER_NAME, MODEL_SERVER_REQUEST_TIMEOUT_MS, REQUEST_ID_HEADER, TOOL_ROLE, TRACE_PARENT_HEADER, USER_ROLE, @@ -33,6 +33,27 @@ impl HttpContext for StreamContext { // manipulate the body in benign ways e.g., compression. self.set_http_request_header("content-length", None); + if let Some(overrides) = self.overrides.as_ref() { + if overrides.use_agent_orchestrator.unwrap_or_default() { + // get endpoint that has agent_orchestrator set to true + if let Some(endpoints) = self.endpoints.as_ref() { + if endpoints.len() == 1 { + let (name, _) = endpoints.iter().next().unwrap(); + debug!("Setting ARCH_PROVIDER_HINT_HEADER to {}", name); + self.set_http_request_header(ARCH_ROUTING_HEADER, Some(name)); + } else { + warn!("Need single endpoint when use_agent_orchestrator is set"); + self.send_server_error( + ServerError::LogicError( + "Need single endpoint when use_agent_orchestrator is set".to_string(), + ), + None, + ); + } + } + } + } + let request_path = self.get_http_request_header(":path").unwrap_or_default(); if request_path == HEALTHZ_PATH { self.send_http_response(200, vec![], None); @@ -49,6 +70,7 @@ impl HttpContext for StreamContext { self.request_id = self.get_http_request_header(REQUEST_ID_HEADER); self.traceparent = self.get_http_request_header(TRACE_PARENT_HEADER); + Action::Continue } @@ -152,6 +174,18 @@ impl HttpContext for StreamContext { } } + if let Some(overrides) = self.overrides.as_ref() { + if overrides.use_agent_orchestrator.unwrap_or_default() { + if metadata.is_none() { + metadata = Some(HashMap::new()); + } + metadata + .as_mut() + .unwrap() + .insert("use_agent_orchestrator".to_string(), "true".to_string()); + } + } + let arch_fc_chat_completion_request = ChatCompletionsRequest { messages: deserialized_body.messages.clone(), metadata, diff --git a/crates/prompt_gateway/src/stream_context.rs b/crates/prompt_gateway/src/stream_context.rs index 87cf3ee9..fddb3b20 100644 --- a/crates/prompt_gateway/src/stream_context.rs +++ b/crates/prompt_gateway/src/stream_context.rs @@ -4,7 +4,7 @@ use common::api::open_ai::{ to_server_events, ArchState, ChatCompletionStreamResponse, ChatCompletionsRequest, ChatCompletionsResponse, Message, ToolCall, }; -use common::configuration::{Overrides, PromptTarget, Tracing}; +use common::configuration::{Endpoint, Overrides, PromptTarget, Tracing}; use common::consts::{ API_REQUEST_TIMEOUT_MS, ARCH_FC_MODEL_NAME, ARCH_INTERNAL_CLUSTER_NAME, ARCH_UPSTREAM_HOST_HEADER, ASSISTANT_ROLE, DEFAULT_TARGET_REQUEST_TIMEOUT_MS, MESSAGES_KEY, @@ -46,6 +46,7 @@ pub struct StreamCallContext { pub struct StreamContext { system_prompt: Rc>, pub prompt_targets: Rc>, + pub endpoints: Rc>>, pub overrides: Rc>, pub metrics: Rc, pub callouts: RefCell>, @@ -72,6 +73,7 @@ impl StreamContext { metrics: Rc, system_prompt: Rc>, prompt_targets: Rc>, + endpoints: Rc>>, overrides: Rc>, tracing: Rc>, ) -> Self { @@ -80,6 +82,7 @@ impl StreamContext { metrics, system_prompt, prompt_targets, + endpoints, callouts: RefCell::new(HashMap::new()), chat_completions_request: None, tool_calls: None, @@ -312,12 +315,59 @@ impl StreamContext { callout_context.prompt_target_name = Some(self.tool_calls.as_ref().unwrap()[0].function.name.clone()); + if let Some(overrides) = self.overrides.as_ref() { + if overrides.use_agent_orchestrator.unwrap_or_default() { + let mut metadata = HashMap::new(); + metadata.insert("use_agent_orchestrator".to_string(), "true".to_string()); + + metadata.insert( + "agent-name".to_string(), + callout_context + .prompt_target_name + .as_ref() + .unwrap() + .to_string(), + ); + + if let Some(overrides) = self.overrides.as_ref() { + if overrides.optimize_context_window.unwrap_or_default() { + metadata.insert("optimize_context_window".to_string(), "true".to_string()); + } + } + + if let Some(overrides) = self.overrides.as_ref() { + if overrides.use_agent_orchestrator.unwrap_or_default() { + metadata.insert("use_agent_orchestrator".to_string(), "true".to_string()); + } + } + + let messages = self.construct_llm_messages(&callout_context); + + let chat_completion_request = ChatCompletionsRequest { + model: callout_context.request_body.model.clone(), + messages, + tools: None, + stream: callout_context.request_body.stream, + stream_options: callout_context.request_body.stream_options.clone(), + metadata: Some(metadata), + }; + + let body_str = serde_json::to_string(&chat_completion_request).unwrap(); + debug!("sending request to llm agent: {}", body_str); + self.set_http_request_body(0, self.request_body_size, body_str.as_bytes()); + self.resume_http_request(); + return; + } + } + self.schedule_api_call_request(callout_context); } fn schedule_api_call_request(&mut self, mut callout_context: StreamCallContext) { + // Construct messages early to avoid mutable borrow conflicts + let tools_call_name = self.tool_calls.as_ref().unwrap()[0].function.name.clone(); - let prompt_target = self.prompt_targets.get(&tools_call_name).unwrap(); + let prompt_target = self.prompt_targets.get(&tools_call_name).unwrap().clone(); let tool_params = &self.tool_calls.as_ref().unwrap()[0].function.arguments; let endpoint_details = prompt_target.endpoint.as_ref().unwrap(); let endpoint_path: String = endpoint_details @@ -329,7 +379,7 @@ impl StreamContext { let http_method = endpoint_details.method.clone().unwrap_or_default(); let prompt_target_params = prompt_target.parameters.clone().unwrap_or_default(); - let (path, body) = match compute_request_path_body( + let (path, api_call_body) = match compute_request_path_body( &endpoint_path, tool_params, &prompt_target_params, @@ -346,6 +396,8 @@ impl StreamContext { } }; + debug!("api call body {:?}", api_call_body); + let timeout_str = API_REQUEST_TIMEOUT_MS.to_string(); let http_method_str = http_method.to_string(); @@ -375,11 +427,12 @@ impl StreamContext { headers.insert(key.as_str(), value.as_str()); } + let call_args = CallArgs::new( ARCH_INTERNAL_CLUSTER_NAME, &path, headers.into_iter().collect(), - body.as_deref().map(|s| s.as_bytes()), + api_call_body.as_deref().map(|s| s.as_bytes()), vec![], Duration::from_secs(5), ); @@ -406,6 +459,11 @@ impl StreamContext { "developer api call response received: status code: {}", http_status ); + let prompt_target = self + .prompt_targets + .get(callout_context.prompt_target_name.as_ref().unwrap()) + .unwrap() + .clone(); if http_status != StatusCode::OK.as_str() { warn!( "api server responded with non 2xx status code: {}", @@ -441,6 +499,40 @@ impl StreamContext { } }; + if !prompt_target + .auto_llm_dispatch_on_response + .unwrap_or(true) + { + let tool_call_response = self.tool_call_response.as_ref().unwrap().clone(); + + let direct_response_str = if self.streaming_response { + let chunks = vec![ + ChatCompletionStreamResponse::new( + None, + Some(ASSISTANT_ROLE.to_string()), + Some(ARCH_FC_MODEL_NAME.to_owned()), + None, + ), + ChatCompletionStreamResponse::new( + Some(tool_call_response.clone()), + None, + Some(ARCH_FC_MODEL_NAME.to_owned()), + None, + ), + ]; + + to_server_events(chunks) + } else { + tool_call_response + }; + + return self.send_http_response( + StatusCode::OK.as_u16().into(), + vec![], + Some(direct_response_str.as_bytes()), + ); + } + let final_prompt = format!( "{}\ncontext: {}", user_message.content.unwrap(), @@ -565,7 +657,7 @@ impl StreamContext { // check if the default target should be dispatched to the LLM provider if !prompt_target .auto_llm_dispatch_on_response - .unwrap_or_default() + .unwrap_or(true) { let default_target_response_str = if self.streaming_response { let chat_completion_response = diff --git a/crates/prompt_gateway/src/tools.rs b/crates/prompt_gateway/src/tools.rs index b3b3d07e..c909a2dd 100644 --- a/crates/prompt_gateway/src/tools.rs +++ b/crates/prompt_gateway/src/tools.rs @@ -4,8 +4,13 @@ use std::collections::HashMap; use serde_yaml::Value; // only add params that are of string, number and bool type -pub fn filter_tool_params(tool_params: &HashMap) -> HashMap { +pub fn filter_tool_params(tool_params: &Option>) -> HashMap { + if tool_params.is_none() { + return HashMap::new(); + } tool_params + .as_ref() + .unwrap() .iter() .filter(|(_, value)| value.is_number() || value.is_string() || value.is_bool()) .map(|(key, value)| match value { @@ -22,7 +27,7 @@ pub fn filter_tool_params(tool_params: &HashMap) -> HashMap, + tool_params: &Option>, prompt_target_params: &[Parameter], http_method: &HttpMethod, ) -> Result<(String, Option), String> { diff --git a/crates/prompt_gateway/tests/integration.rs b/crates/prompt_gateway/tests/integration.rs index c5d23297..1673ac16 100644 --- a/crates/prompt_gateway/tests/integration.rs +++ b/crates/prompt_gateway/tests/integration.rs @@ -352,10 +352,10 @@ fn prompt_gateway_request_to_llm_gateway() { tool_type: ToolType::Function, function: FunctionCallDetail { name: String::from("weather_forecast"), - arguments: HashMap::from([( + arguments: Some(HashMap::from([( String::from("city"), Value::String(String::from("seattle")), - )]), + )])), }, }]), model: None, @@ -381,8 +381,8 @@ fn prompt_gateway_request_to_llm_gateway() { .expect_log(Some(LogLevel::Debug), None) .expect_log(Some(LogLevel::Trace), None) .expect_log(Some(LogLevel::Debug), None) - .expect_log(Some(LogLevel::Trace), None) .expect_log(Some(LogLevel::Debug), None) + .expect_log(Some(LogLevel::Trace), None) .expect_http_call( Some("arch_internal"), Some(vec![ @@ -400,6 +400,7 @@ fn prompt_gateway_request_to_llm_gateway() { ) .returning(Some(2)) .expect_metric_increment("active_http_calls", 1) + .expect_log(Some(LogLevel::Trace), None) .execute_and_expect(ReturnType::None) .unwrap(); @@ -499,10 +500,10 @@ fn prompt_gateway_request_no_intent_match() { tool_type: ToolType::Function, function: FunctionCallDetail { name: String::from("weather_forecast"), - arguments: HashMap::from([( + arguments: Some(HashMap::from([( String::from("city"), Value::String(String::from("seattle")), - )]), + )])), }, }]), model: None, @@ -655,10 +656,10 @@ fn prompt_gateway_request_no_intent_match_default_target() { tool_type: ToolType::Function, function: FunctionCallDetail { name: String::from("weather_forecast"), - arguments: HashMap::from([( + arguments: Some(HashMap::from([( String::from("city"), Value::String(String::from("seattle")), - )]), + )])), }, }]), model: None, diff --git a/demos/samples_python/weather_forecast/hurl_tests/simple.hurl b/demos/samples_python/weather_forecast/hurl_tests/simple.hurl new file mode 100644 index 00000000..d1243d20 --- /dev/null +++ b/demos/samples_python/weather_forecast/hurl_tests/simple.hurl @@ -0,0 +1,19 @@ +POST http://localhost:10000/v1/chat/completions +Content-Type: application/json + +{ + "messages": [ + { + "role": "user", + "content": "how is the weather in seattle for next 5 days" + } + ] +} +HTTP 200 +[Asserts] +header "content-type" == "application/json" +jsonpath "$.model" matches /^gpt-4o/ +jsonpath "$.metadata.x-arch-state" != null +jsonpath "$.usage" != null +jsonpath "$.choices[0].message.content" matches /Seattle/ +jsonpath "$.choices[0].message.role" == "assistant" diff --git a/demos/samples_python/weather_forecast/hurl_tests/simple_stream.hurl b/demos/samples_python/weather_forecast/hurl_tests/simple_stream.hurl new file mode 100644 index 00000000..51844c2d --- /dev/null +++ b/demos/samples_python/weather_forecast/hurl_tests/simple_stream.hurl @@ -0,0 +1,17 @@ +POST http://localhost:10000/v1/chat/completions +Content-Type: application/json + +{ + "messages": [ + { + "role": "user", + "content": "how is the weather in seattle for next 5 days" + } + ], + "stream": true +} +HTTP 200 +[Asserts] +header "content-type" matches /text\/event-stream/ +body matches "(?s).*\"name\":\"get_current_weather\".*" +body matches "(?s).*\"model\":\"gpt-4o-mini.*" diff --git a/demos/use_cases/orchestrating_agents/Dockerfile b/demos/use_cases/orchestrating_agents/Dockerfile new file mode 100644 index 00000000..b53cb719 --- /dev/null +++ b/demos/use_cases/orchestrating_agents/Dockerfile @@ -0,0 +1,41 @@ +# took inspiration from https://medium.com/@albertazzir/blazing-fast-python-docker-builds-with-poetry-a78a66f5aed0 + +# The builder image, used to build the virtual environment +FROM python:3.10 as builder + +RUN pip install poetry==1.8.3 + +ENV POETRY_NO_INTERACTION=1 \ + POETRY_VIRTUALENVS_IN_PROJECT=1 \ + POETRY_VIRTUALENVS_CREATE=1 \ + POETRY_CACHE_DIR=/tmp/poetry_cache + +WORKDIR /code + +COPY pyproject.toml poetry.lock ./ +RUN touch README.md + +RUN poetry install --no-root && rm -rf $POETRY_CACHE_DIR + +# The runtime image, used to just run the code provided its virtual environment +FROM python:3.10-slim as runtime + +RUN apt-get update && apt-get install -y curl + +WORKDIR /code + +ENV VIRTUAL_ENV=/code/.venv \ + PATH="/code/.venv/bin:$PATH" + +COPY --from=builder ${VIRTUAL_ENV} ${VIRTUAL_ENV} + +COPY main.py ./ + +HEALTHCHECK \ + --interval=5s \ + --timeout=1s \ + --start-period=1s \ + --retries=3 \ + CMD curl http://localhost:80/healthz + +CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "80", "--log-level", "debug"] diff --git a/demos/use_cases/orchestrating_agents/arch_config.yaml b/demos/use_cases/orchestrating_agents/arch_config.yaml new file mode 100644 index 00000000..7cffa101 --- /dev/null +++ b/demos/use_cases/orchestrating_agents/arch_config.yaml @@ -0,0 +1,46 @@ +version: "0.1-beta" + +listeners: + ingress_traffic: + address: 0.0.0.0 + port: 10000 + message_format: openai + timeout: 30s + + egress_traffic: + address: 0.0.0.0 + port: 12000 + message_format: openai + timeout: 30s + +overrides: + use_agent_orchestrator: true + +endpoints: + agent_gateway: + endpoint: host.docker.internal:18083 + connect_timeout: 0.005s + +llm_providers: + - name: gpt-4o-mini + access_key: $OPENAI_API_KEY + provider_interface: openai + model: gpt-4o-mini + default: true + +system_prompt: | + You are a helpful assistant. + +prompt_targets: + - name: sales_agent + description: handles queries related to sales and purchases + + - name: issues_and_repairs + description: handles issues, repairs, or refunds + + - name: escalate_to_human + description: escalates to human agent + +tracing: + random_sampling: 100 + trace_arch_internal: true diff --git a/demos/use_cases/orchestrating_agents/docker-compose.yaml b/demos/use_cases/orchestrating_agents/docker-compose.yaml new file mode 100644 index 00000000..288ecf30 --- /dev/null +++ b/demos/use_cases/orchestrating_agents/docker-compose.yaml @@ -0,0 +1,29 @@ +services: + triage_service: + build: + context: ./ + environment: + - OLTP_HOST=http://jaeger:4317 + extra_hosts: + - "host.docker.internal:host-gateway" + ports: + - "18083:80" + + chatbot_ui: + build: + context: ../../shared/chatbot_ui + ports: + - "18080:8080" + environment: + # this is only because we are running the sample app in the same docker container environemtn as archgw + - CHAT_COMPLETION_ENDPOINT=http://host.docker.internal:10000/v1 + extra_hosts: + - "host.docker.internal:host-gateway" + + jaeger: + build: + context: ../../shared/jaeger + ports: + - "16686:16686" + - "4317:4317" + - "4318:4318" diff --git a/demos/use_cases/orchestrating_agents/hurl_tests/simple.hurl b/demos/use_cases/orchestrating_agents/hurl_tests/simple.hurl new file mode 100644 index 00000000..4db2c67c --- /dev/null +++ b/demos/use_cases/orchestrating_agents/hurl_tests/simple.hurl @@ -0,0 +1,19 @@ +POST http://localhost:10000/v1/chat/completions +Content-Type: application/json + +{ + "messages": [ + { + "role": "user", + "content": "I want to sell red shoes" + } + ] +} +HTTP 200 +[Asserts] +header "content-type" == "application/json" +jsonpath "$.model" matches /^gpt-4o-mini/ +jsonpath "$.metadata.x-arch-state" != null +jsonpath "$.usage" != null +jsonpath "$.choices[0].message.content" != null +jsonpath "$.choices[0].message.role" == "assistant" diff --git a/demos/use_cases/orchestrating_agents/hurl_tests/simple_stream.hurl b/demos/use_cases/orchestrating_agents/hurl_tests/simple_stream.hurl new file mode 100644 index 00000000..f060fed0 --- /dev/null +++ b/demos/use_cases/orchestrating_agents/hurl_tests/simple_stream.hurl @@ -0,0 +1,16 @@ +POST http://localhost:10000/v1/chat/completions +Content-Type: application/json + +{ + "messages": [ + { + "role": "user", + "content": "I want to sell red shoes" + } + ], + "stream": true +} +HTTP 200 +[Asserts] +header "content-type" matches /text\/event-stream/ +body matches /^data: .*?sales_agent.*?\n/ diff --git a/demos/use_cases/orchestrating_agents/main.py b/demos/use_cases/orchestrating_agents/main.py new file mode 100644 index 00000000..27a9598a --- /dev/null +++ b/demos/use_cases/orchestrating_agents/main.py @@ -0,0 +1,105 @@ +import logging +import json +from typing import List, Dict, Any +from fastapi import FastAPI, Request +from fastapi.responses import StreamingResponse +from pydantic import BaseModel +import openai + +# Setup logging +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger("uvicorn.error") + +app = FastAPI() + + +class Message(BaseModel): + role: str + content: str + + +class ChatCompletionsRequest(BaseModel): + messages: List[Message] + model: str + metadata: Dict[str, Any] = {} + stream: bool = False + + +openai_client = openai.OpenAI( + api_key="None", # archgw picks the API key from the config file + base_url="http://host.docker.internal:12000/v1", +) + + +def call_openai(messages: List[Dict[str, str]], stream: bool): + completion = openai_client.chat.completions.create( + model="None", # archgw picks the default LLM configured in the config file + messages=messages, + stream=stream, + ) + + if stream: + + def stream(): + for line in completion: + if line.choices and len(line.choices) > 0 and line.choices[0].delta: + chunk_response_str = json.dumps(line.model_dump()) + yield "data: " + chunk_response_str + "\n\n" + yield "data: [DONE]" + "\n\n" + + return StreamingResponse(stream(), media_type="text/event-stream") + else: + return completion + + +class Agent: + def __init__(self, role: str, instructions: str): + self.system_prompt = f"You are a {role}.\n{instructions}" + + def handle(self, req: ChatCompletionsRequest): + messages = [{"role": "system", "content": self.get_system_prompt()}] + [ + message.model_dump() for message in req.messages + ] + return call_openai(messages, req.stream) + + def get_system_prompt(self) -> str: + return self.system_prompt + + +# Define your agents +AGENTS = { + "sales_agent": Agent( + role="sales agent", + instructions=( + "Always answer in a sentence or less.\n" + "Follow the following routine with the user:\n" + "1. Engage\n" + "2. Quote ridiculous price\n" + "3. Reveal caveat if user agrees." + ), + ), + "issues_and_repairs": Agent( + role="issues and repairs agent", + instructions="Propose a solution, offer refund if necessary.", + ), + "escalate_to_human": Agent( + role="human escalation agent", instructions="Escalate issues to a human." + ), + "unknown_agent": Agent( + role="general assistant", instructions="Assist the user in general queries." + ), +} + + +@app.post("/v1/chat/completions") +def completion_api(req: ChatCompletionsRequest, request: Request): + agent_name = req.metadata.get("agent-name", "unknown_agent") + agent = AGENTS.get(agent_name) + logger.info(f"Routing to agent: {agent_name}") + + return agent.handle(req) + + +@app.get("/healthz") +async def healthz(): + return {"status": "ok"} diff --git a/demos/use_cases/orchestrating_agents/poetry.lock b/demos/use_cases/orchestrating_agents/poetry.lock new file mode 100644 index 00000000..929a5c86 --- /dev/null +++ b/demos/use_cases/orchestrating_agents/poetry.lock @@ -0,0 +1,573 @@ +# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. + +[[package]] +name = "annotated-types" +version = "0.7.0" +description = "Reusable constraint types to use with typing.Annotated" +optional = false +python-versions = ">=3.8" +files = [ + {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, + {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, +] + +[[package]] +name = "anyio" +version = "4.9.0" +description = "High level compatibility layer for multiple asynchronous event loop implementations" +optional = false +python-versions = ">=3.9" +files = [ + {file = "anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c"}, + {file = "anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028"}, +] + +[package.dependencies] +exceptiongroup = {version = ">=1.0.2", markers = "python_version < \"3.11\""} +idna = ">=2.8" +sniffio = ">=1.1" +typing_extensions = {version = ">=4.5", markers = "python_version < \"3.13\""} + +[package.extras] +doc = ["Sphinx (>=8.2,<9.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx_rtd_theme"] +test = ["anyio[trio]", "blockbuster (>=1.5.23)", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "trustme", "truststore (>=0.9.1)", "uvloop (>=0.21)"] +trio = ["trio (>=0.26.1)"] + +[[package]] +name = "certifi" +version = "2025.1.31" +description = "Python package for providing Mozilla's CA Bundle." +optional = false +python-versions = ">=3.6" +files = [ + {file = "certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe"}, + {file = "certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651"}, +] + +[[package]] +name = "click" +version = "8.1.8" +description = "Composable command line interface toolkit" +optional = false +python-versions = ">=3.7" +files = [ + {file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"}, + {file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "distro" +version = "1.9.0" +description = "Distro - an OS platform information API" +optional = false +python-versions = ">=3.6" +files = [ + {file = "distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2"}, + {file = "distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed"}, +] + +[[package]] +name = "exceptiongroup" +version = "1.2.2" +description = "Backport of PEP 654 (exception groups)" +optional = false +python-versions = ">=3.7" +files = [ + {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, + {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, +] + +[package.extras] +test = ["pytest (>=6)"] + +[[package]] +name = "fastapi" +version = "0.115.11" +description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" +optional = false +python-versions = ">=3.8" +files = [ + {file = "fastapi-0.115.11-py3-none-any.whl", hash = "sha256:32e1541b7b74602e4ef4a0260ecaf3aadf9d4f19590bba3e1bf2ac4666aa2c64"}, + {file = "fastapi-0.115.11.tar.gz", hash = "sha256:cc81f03f688678b92600a65a5e618b93592c65005db37157147204d8924bf94f"}, +] + +[package.dependencies] +pydantic = ">=1.7.4,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0 || >2.0.0,<2.0.1 || >2.0.1,<2.1.0 || >2.1.0,<3.0.0" +starlette = ">=0.40.0,<0.47.0" +typing-extensions = ">=4.8.0" + +[package.extras] +all = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.5)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=3.1.5)", "orjson (>=3.2.1)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.18)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"] +standard = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.5)", "httpx (>=0.23.0)", "jinja2 (>=3.1.5)", "python-multipart (>=0.0.18)", "uvicorn[standard] (>=0.12.0)"] + +[[package]] +name = "h11" +version = "0.14.0" +description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" +optional = false +python-versions = ">=3.7" +files = [ + {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, + {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, +] + +[[package]] +name = "httpcore" +version = "1.0.7" +description = "A minimal low-level HTTP client." +optional = false +python-versions = ">=3.8" +files = [ + {file = "httpcore-1.0.7-py3-none-any.whl", hash = "sha256:a3fff8f43dc260d5bd363d9f9cf1830fa3a458b332856f34282de498ed420edd"}, + {file = "httpcore-1.0.7.tar.gz", hash = "sha256:8551cb62a169ec7162ac7be8d4817d561f60e08eaa485234898414bb5a8a0b4c"}, +] + +[package.dependencies] +certifi = "*" +h11 = ">=0.13,<0.15" + +[package.extras] +asyncio = ["anyio (>=4.0,<5.0)"] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (==1.*)"] +trio = ["trio (>=0.22.0,<1.0)"] + +[[package]] +name = "httpx" +version = "0.28.1" +description = "The next generation HTTP client." +optional = false +python-versions = ">=3.8" +files = [ + {file = "httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad"}, + {file = "httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc"}, +] + +[package.dependencies] +anyio = "*" +certifi = "*" +httpcore = "==1.*" +idna = "*" + +[package.extras] +brotli = ["brotli", "brotlicffi"] +cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (==1.*)"] +zstd = ["zstandard (>=0.18.0)"] + +[[package]] +name = "idna" +version = "3.10" +description = "Internationalized Domain Names in Applications (IDNA)" +optional = false +python-versions = ">=3.6" +files = [ + {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, + {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, +] + +[package.extras] +all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] + +[[package]] +name = "jiter" +version = "0.9.0" +description = "Fast iterable JSON parser." +optional = false +python-versions = ">=3.8" +files = [ + {file = "jiter-0.9.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:816ec9b60fdfd1fec87da1d7ed46c66c44ffec37ab2ef7de5b147b2fce3fd5ad"}, + {file = "jiter-0.9.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9b1d3086f8a3ee0194ecf2008cf81286a5c3e540d977fa038ff23576c023c0ea"}, + {file = "jiter-0.9.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1339f839b91ae30b37c409bf16ccd3dc453e8b8c3ed4bd1d6a567193651a4a51"}, + {file = "jiter-0.9.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ffba79584b3b670fefae66ceb3a28822365d25b7bf811e030609a3d5b876f538"}, + {file = "jiter-0.9.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cfc7d0a8e899089d11f065e289cb5b2daf3d82fbe028f49b20d7b809193958d"}, + {file = "jiter-0.9.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e00a1a2bbfaaf237e13c3d1592356eab3e9015d7efd59359ac8b51eb56390a12"}, + {file = "jiter-0.9.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d1d9870561eb26b11448854dce0ff27a9a27cb616b632468cafc938de25e9e51"}, + {file = "jiter-0.9.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9872aeff3f21e437651df378cb75aeb7043e5297261222b6441a620218b58708"}, + {file = "jiter-0.9.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:1fd19112d1049bdd47f17bfbb44a2c0001061312dcf0e72765bfa8abd4aa30e5"}, + {file = "jiter-0.9.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6ef5da104664e526836070e4a23b5f68dec1cc673b60bf1edb1bfbe8a55d0678"}, + {file = "jiter-0.9.0-cp310-cp310-win32.whl", hash = "sha256:cb12e6d65ebbefe5518de819f3eda53b73187b7089040b2d17f5b39001ff31c4"}, + {file = "jiter-0.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:c43ca669493626d8672be3b645dbb406ef25af3f4b6384cfd306da7eb2e70322"}, + {file = "jiter-0.9.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:6c4d99c71508912a7e556d631768dcdef43648a93660670986916b297f1c54af"}, + {file = "jiter-0.9.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8f60fb8ce7df529812bf6c625635a19d27f30806885139e367af93f6e734ef58"}, + {file = "jiter-0.9.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:51c4e1a4f8ea84d98b7b98912aa4290ac3d1eabfde8e3c34541fae30e9d1f08b"}, + {file = "jiter-0.9.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f4c677c424dc76684fea3e7285a7a2a7493424bea89ac441045e6a1fb1d7b3b"}, + {file = "jiter-0.9.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2221176dfec87f3470b21e6abca056e6b04ce9bff72315cb0b243ca9e835a4b5"}, + {file = "jiter-0.9.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3c7adb66f899ffa25e3c92bfcb593391ee1947dbdd6a9a970e0d7e713237d572"}, + {file = "jiter-0.9.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c98d27330fdfb77913c1097a7aab07f38ff2259048949f499c9901700789ac15"}, + {file = "jiter-0.9.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:eda3f8cc74df66892b1d06b5d41a71670c22d95a1ca2cbab73654745ce9d0419"}, + {file = "jiter-0.9.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:dd5ab5ddc11418dce28343123644a100f487eaccf1de27a459ab36d6cca31043"}, + {file = "jiter-0.9.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:42f8a68a69f047b310319ef8e2f52fdb2e7976fb3313ef27df495cf77bcad965"}, + {file = "jiter-0.9.0-cp311-cp311-win32.whl", hash = "sha256:a25519efb78a42254d59326ee417d6f5161b06f5da827d94cf521fed961b1ff2"}, + {file = "jiter-0.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:923b54afdd697dfd00d368b7ccad008cccfeb1efb4e621f32860c75e9f25edbd"}, + {file = "jiter-0.9.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:7b46249cfd6c48da28f89eb0be3f52d6fdb40ab88e2c66804f546674e539ec11"}, + {file = "jiter-0.9.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:609cf3c78852f1189894383cf0b0b977665f54cb38788e3e6b941fa6d982c00e"}, + {file = "jiter-0.9.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d726a3890a54561e55a9c5faea1f7655eda7f105bd165067575ace6e65f80bb2"}, + {file = "jiter-0.9.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2e89dc075c1fef8fa9be219e249f14040270dbc507df4215c324a1839522ea75"}, + {file = "jiter-0.9.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:04e8ffa3c353b1bc4134f96f167a2082494351e42888dfcf06e944f2729cbe1d"}, + {file = "jiter-0.9.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:203f28a72a05ae0e129b3ed1f75f56bc419d5f91dfacd057519a8bd137b00c42"}, + {file = "jiter-0.9.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fca1a02ad60ec30bb230f65bc01f611c8608b02d269f998bc29cca8619a919dc"}, + {file = "jiter-0.9.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:237e5cee4d5d2659aaf91bbf8ec45052cc217d9446070699441a91b386ae27dc"}, + {file = "jiter-0.9.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:528b6b71745e7326eed73c53d4aa57e2a522242320b6f7d65b9c5af83cf49b6e"}, + {file = "jiter-0.9.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9f48e86b57bc711eb5acdfd12b6cb580a59cc9a993f6e7dcb6d8b50522dcd50d"}, + {file = "jiter-0.9.0-cp312-cp312-win32.whl", hash = "sha256:699edfde481e191d81f9cf6d2211debbfe4bd92f06410e7637dffb8dd5dfde06"}, + {file = "jiter-0.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:099500d07b43f61d8bd780466d429c45a7b25411b334c60ca875fa775f68ccb0"}, + {file = "jiter-0.9.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:2764891d3f3e8b18dce2cff24949153ee30c9239da7c00f032511091ba688ff7"}, + {file = "jiter-0.9.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:387b22fbfd7a62418d5212b4638026d01723761c75c1c8232a8b8c37c2f1003b"}, + {file = "jiter-0.9.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40d8da8629ccae3606c61d9184970423655fb4e33d03330bcdfe52d234d32f69"}, + {file = "jiter-0.9.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a1be73d8982bdc278b7b9377426a4b44ceb5c7952073dd7488e4ae96b88e1103"}, + {file = "jiter-0.9.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2228eaaaa111ec54b9e89f7481bffb3972e9059301a878d085b2b449fbbde635"}, + {file = "jiter-0.9.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:11509bfecbc319459647d4ac3fd391d26fdf530dad00c13c4dadabf5b81f01a4"}, + {file = "jiter-0.9.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f22238da568be8bbd8e0650e12feeb2cfea15eda4f9fc271d3b362a4fa0604d"}, + {file = "jiter-0.9.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:17f5d55eb856597607562257c8e36c42bc87f16bef52ef7129b7da11afc779f3"}, + {file = "jiter-0.9.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:6a99bed9fbb02f5bed416d137944419a69aa4c423e44189bc49718859ea83bc5"}, + {file = "jiter-0.9.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:e057adb0cd1bd39606100be0eafe742de2de88c79df632955b9ab53a086b3c8d"}, + {file = "jiter-0.9.0-cp313-cp313-win32.whl", hash = "sha256:f7e6850991f3940f62d387ccfa54d1a92bd4bb9f89690b53aea36b4364bcab53"}, + {file = "jiter-0.9.0-cp313-cp313-win_amd64.whl", hash = "sha256:c8ae3bf27cd1ac5e6e8b7a27487bf3ab5f82318211ec2e1346a5b058756361f7"}, + {file = "jiter-0.9.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f0b2827fb88dda2cbecbbc3e596ef08d69bda06c6f57930aec8e79505dc17001"}, + {file = "jiter-0.9.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:062b756ceb1d40b0b28f326cba26cfd575a4918415b036464a52f08632731e5a"}, + {file = "jiter-0.9.0-cp313-cp313t-win_amd64.whl", hash = "sha256:6f7838bc467ab7e8ef9f387bd6de195c43bad82a569c1699cb822f6609dd4cdf"}, + {file = "jiter-0.9.0-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:4a2d16360d0642cd68236f931b85fe50288834c383492e4279d9f1792e309571"}, + {file = "jiter-0.9.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e84ed1c9c9ec10bbb8c37f450077cbe3c0d4e8c2b19f0a49a60ac7ace73c7452"}, + {file = "jiter-0.9.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9f3c848209ccd1bfa344a1240763975ca917de753c7875c77ec3034f4151d06c"}, + {file = "jiter-0.9.0-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7825f46e50646bee937e0f849d14ef3a417910966136f59cd1eb848b8b5bb3e4"}, + {file = "jiter-0.9.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d82a811928b26d1a6311a886b2566f68ccf2b23cf3bfed042e18686f1f22c2d7"}, + {file = "jiter-0.9.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0c058ecb51763a67f019ae423b1cbe3fa90f7ee6280c31a1baa6ccc0c0e2d06e"}, + {file = "jiter-0.9.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9897115ad716c48f0120c1f0c4efae348ec47037319a6c63b2d7838bb53aaef4"}, + {file = "jiter-0.9.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:351f4c90a24c4fb8c87c6a73af2944c440494ed2bea2094feecacb75c50398ae"}, + {file = "jiter-0.9.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:d45807b0f236c485e1e525e2ce3a854807dfe28ccf0d013dd4a563395e28008a"}, + {file = "jiter-0.9.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:1537a890724ba00fdba21787010ac6f24dad47f763410e9e1093277913592784"}, + {file = "jiter-0.9.0-cp38-cp38-win32.whl", hash = "sha256:e3630ec20cbeaddd4b65513fa3857e1b7c4190d4481ef07fb63d0fad59033321"}, + {file = "jiter-0.9.0-cp38-cp38-win_amd64.whl", hash = "sha256:2685f44bf80e95f8910553bf2d33b9c87bf25fceae6e9f0c1355f75d2922b0ee"}, + {file = "jiter-0.9.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:9ef340fae98065071ccd5805fe81c99c8f80484e820e40043689cf97fb66b3e2"}, + {file = "jiter-0.9.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:efb767d92c63b2cd9ec9f24feeb48f49574a713870ec87e9ba0c2c6e9329c3e2"}, + {file = "jiter-0.9.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:113f30f87fb1f412510c6d7ed13e91422cfd329436364a690c34c8b8bd880c42"}, + {file = "jiter-0.9.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8793b6df019b988526f5a633fdc7456ea75e4a79bd8396a3373c371fc59f5c9b"}, + {file = "jiter-0.9.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7a9aaa5102dba4e079bb728076fadd5a2dca94c05c04ce68004cfd96f128ea34"}, + {file = "jiter-0.9.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d838650f6ebaf4ccadfb04522463e74a4c378d7e667e0eb1865cfe3990bfac49"}, + {file = "jiter-0.9.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0194f813efdf4b8865ad5f5c5f50f8566df7d770a82c51ef593d09e0b347020"}, + {file = "jiter-0.9.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a7954a401d0a8a0b8bc669199db78af435aae1e3569187c2939c477c53cb6a0a"}, + {file = "jiter-0.9.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4feafe787eb8a8d98168ab15637ca2577f6ddf77ac6c8c66242c2d028aa5420e"}, + {file = "jiter-0.9.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:27cd1f2e8bb377f31d3190b34e4328d280325ad7ef55c6ac9abde72f79e84d2e"}, + {file = "jiter-0.9.0-cp39-cp39-win32.whl", hash = "sha256:161d461dcbe658cf0bd0aa375b30a968b087cdddc624fc585f3867c63c6eca95"}, + {file = "jiter-0.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:e8b36d8a16a61993be33e75126ad3d8aa29cf450b09576f3c427d27647fcb4aa"}, + {file = "jiter-0.9.0.tar.gz", hash = "sha256:aadba0964deb424daa24492abc3d229c60c4a31bfee205aedbf1acc7639d7893"}, +] + +[[package]] +name = "openai" +version = "1.66.5" +description = "The official Python library for the openai API" +optional = false +python-versions = ">=3.8" +files = [ + {file = "openai-1.66.5-py3-none-any.whl", hash = "sha256:74be528175f8389f67675830c51a15bd51e874425c86d3de6153bf70ed6c2884"}, + {file = "openai-1.66.5.tar.gz", hash = "sha256:f61b8fac29490ca8fdc6d996aa6926c18dbe5639536f8c40219c40db05511b11"}, +] + +[package.dependencies] +anyio = ">=3.5.0,<5" +distro = ">=1.7.0,<2" +httpx = ">=0.23.0,<1" +jiter = ">=0.4.0,<1" +pydantic = ">=1.9.0,<3" +sniffio = "*" +tqdm = ">4" +typing-extensions = ">=4.11,<5" + +[package.extras] +datalib = ["numpy (>=1)", "pandas (>=1.2.3)", "pandas-stubs (>=1.1.0.11)"] +realtime = ["websockets (>=13,<15)"] + +[[package]] +name = "pydantic" +version = "2.10.6" +description = "Data validation using Python type hints" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pydantic-2.10.6-py3-none-any.whl", hash = "sha256:427d664bf0b8a2b34ff5dd0f5a18df00591adcee7198fbd71981054cef37b584"}, + {file = "pydantic-2.10.6.tar.gz", hash = "sha256:ca5daa827cce33de7a42be142548b0096bf05a7e7b365aebfa5f8eeec7128236"}, +] + +[package.dependencies] +annotated-types = ">=0.6.0" +pydantic-core = "2.27.2" +typing-extensions = ">=4.12.2" + +[package.extras] +email = ["email-validator (>=2.0.0)"] +timezone = ["tzdata"] + +[[package]] +name = "pydantic-core" +version = "2.27.2" +description = "Core functionality for Pydantic validation and serialization" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pydantic_core-2.27.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2d367ca20b2f14095a8f4fa1210f5a7b78b8a20009ecced6b12818f455b1e9fa"}, + {file = "pydantic_core-2.27.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:491a2b73db93fab69731eaee494f320faa4e093dbed776be1a829c2eb222c34c"}, + {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7969e133a6f183be60e9f6f56bfae753585680f3b7307a8e555a948d443cc05a"}, + {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3de9961f2a346257caf0aa508a4da705467f53778e9ef6fe744c038119737ef5"}, + {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e2bb4d3e5873c37bb3dd58714d4cd0b0e6238cebc4177ac8fe878f8b3aa8e74c"}, + {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:280d219beebb0752699480fe8f1dc61ab6615c2046d76b7ab7ee38858de0a4e7"}, + {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47956ae78b6422cbd46f772f1746799cbb862de838fd8d1fbd34a82e05b0983a"}, + {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:14d4a5c49d2f009d62a2a7140d3064f686d17a5d1a268bc641954ba181880236"}, + {file = "pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:337b443af21d488716f8d0b6164de833e788aa6bd7e3a39c005febc1284f4962"}, + {file = "pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:03d0f86ea3184a12f41a2d23f7ccb79cdb5a18e06993f8a45baa8dfec746f0e9"}, + {file = "pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7041c36f5680c6e0f08d922aed302e98b3745d97fe1589db0a3eebf6624523af"}, + {file = "pydantic_core-2.27.2-cp310-cp310-win32.whl", hash = "sha256:50a68f3e3819077be2c98110c1f9dcb3817e93f267ba80a2c05bb4f8799e2ff4"}, + {file = "pydantic_core-2.27.2-cp310-cp310-win_amd64.whl", hash = "sha256:e0fd26b16394ead34a424eecf8a31a1f5137094cabe84a1bcb10fa6ba39d3d31"}, + {file = "pydantic_core-2.27.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:8e10c99ef58cfdf2a66fc15d66b16c4a04f62bca39db589ae8cba08bc55331bc"}, + {file = "pydantic_core-2.27.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:26f32e0adf166a84d0cb63be85c562ca8a6fa8de28e5f0d92250c6b7e9e2aff7"}, + {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c19d1ea0673cd13cc2f872f6c9ab42acc4e4f492a7ca9d3795ce2b112dd7e15"}, + {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5e68c4446fe0810e959cdff46ab0a41ce2f2c86d227d96dc3847af0ba7def306"}, + {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d9640b0059ff4f14d1f37321b94061c6db164fbe49b334b31643e0528d100d99"}, + {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:40d02e7d45c9f8af700f3452f329ead92da4c5f4317ca9b896de7ce7199ea459"}, + {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c1fd185014191700554795c99b347d64f2bb637966c4cfc16998a0ca700d048"}, + {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d81d2068e1c1228a565af076598f9e7451712700b673de8f502f0334f281387d"}, + {file = "pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1a4207639fb02ec2dbb76227d7c751a20b1a6b4bc52850568e52260cae64ca3b"}, + {file = "pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:3de3ce3c9ddc8bbd88f6e0e304dea0e66d843ec9de1b0042b0911c1663ffd474"}, + {file = "pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:30c5f68ded0c36466acede341551106821043e9afaad516adfb6e8fa80a4e6a6"}, + {file = "pydantic_core-2.27.2-cp311-cp311-win32.whl", hash = "sha256:c70c26d2c99f78b125a3459f8afe1aed4d9687c24fd677c6a4436bc042e50d6c"}, + {file = "pydantic_core-2.27.2-cp311-cp311-win_amd64.whl", hash = "sha256:08e125dbdc505fa69ca7d9c499639ab6407cfa909214d500897d02afb816e7cc"}, + {file = "pydantic_core-2.27.2-cp311-cp311-win_arm64.whl", hash = "sha256:26f0d68d4b235a2bae0c3fc585c585b4ecc51382db0e3ba402a22cbc440915e4"}, + {file = "pydantic_core-2.27.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9e0c8cfefa0ef83b4da9588448b6d8d2a2bf1a53c3f1ae5fca39eb3061e2f0b0"}, + {file = "pydantic_core-2.27.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:83097677b8e3bd7eaa6775720ec8e0405f1575015a463285a92bfdfe254529ef"}, + {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:172fce187655fece0c90d90a678424b013f8fbb0ca8b036ac266749c09438cb7"}, + {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:519f29f5213271eeeeb3093f662ba2fd512b91c5f188f3bb7b27bc5973816934"}, + {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05e3a55d124407fffba0dd6b0c0cd056d10e983ceb4e5dbd10dda135c31071d6"}, + {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c3ed807c7b91de05e63930188f19e921d1fe90de6b4f5cd43ee7fcc3525cb8c"}, + {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fb4aadc0b9a0c063206846d603b92030eb6f03069151a625667f982887153e2"}, + {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:28ccb213807e037460326424ceb8b5245acb88f32f3d2777427476e1b32c48c4"}, + {file = "pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:de3cd1899e2c279b140adde9357c4495ed9d47131b4a4eaff9052f23398076b3"}, + {file = "pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:220f892729375e2d736b97d0e51466252ad84c51857d4d15f5e9692f9ef12be4"}, + {file = "pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a0fcd29cd6b4e74fe8ddd2c90330fd8edf2e30cb52acda47f06dd615ae72da57"}, + {file = "pydantic_core-2.27.2-cp312-cp312-win32.whl", hash = "sha256:1e2cb691ed9834cd6a8be61228471d0a503731abfb42f82458ff27be7b2186fc"}, + {file = "pydantic_core-2.27.2-cp312-cp312-win_amd64.whl", hash = "sha256:cc3f1a99a4f4f9dd1de4fe0312c114e740b5ddead65bb4102884b384c15d8bc9"}, + {file = "pydantic_core-2.27.2-cp312-cp312-win_arm64.whl", hash = "sha256:3911ac9284cd8a1792d3cb26a2da18f3ca26c6908cc434a18f730dc0db7bfa3b"}, + {file = "pydantic_core-2.27.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7d14bd329640e63852364c306f4d23eb744e0f8193148d4044dd3dacdaacbd8b"}, + {file = "pydantic_core-2.27.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:82f91663004eb8ed30ff478d77c4d1179b3563df6cdb15c0817cd1cdaf34d154"}, + {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71b24c7d61131bb83df10cc7e687433609963a944ccf45190cfc21e0887b08c9"}, + {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fa8e459d4954f608fa26116118bb67f56b93b209c39b008277ace29937453dc9"}, + {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce8918cbebc8da707ba805b7fd0b382816858728ae7fe19a942080c24e5b7cd1"}, + {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eda3f5c2a021bbc5d976107bb302e0131351c2ba54343f8a496dc8783d3d3a6a"}, + {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd8086fa684c4775c27f03f062cbb9eaa6e17f064307e86b21b9e0abc9c0f02e"}, + {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8d9b3388db186ba0c099a6d20f0604a44eabdeef1777ddd94786cdae158729e4"}, + {file = "pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7a66efda2387de898c8f38c0cf7f14fca0b51a8ef0b24bfea5849f1b3c95af27"}, + {file = "pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:18a101c168e4e092ab40dbc2503bdc0f62010e95d292b27827871dc85450d7ee"}, + {file = "pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ba5dd002f88b78a4215ed2f8ddbdf85e8513382820ba15ad5ad8955ce0ca19a1"}, + {file = "pydantic_core-2.27.2-cp313-cp313-win32.whl", hash = "sha256:1ebaf1d0481914d004a573394f4be3a7616334be70261007e47c2a6fe7e50130"}, + {file = "pydantic_core-2.27.2-cp313-cp313-win_amd64.whl", hash = "sha256:953101387ecf2f5652883208769a79e48db18c6df442568a0b5ccd8c2723abee"}, + {file = "pydantic_core-2.27.2-cp313-cp313-win_arm64.whl", hash = "sha256:ac4dbfd1691affb8f48c2c13241a2e3b60ff23247cbcf981759c768b6633cf8b"}, + {file = "pydantic_core-2.27.2-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:d3e8d504bdd3f10835468f29008d72fc8359d95c9c415ce6e767203db6127506"}, + {file = "pydantic_core-2.27.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:521eb9b7f036c9b6187f0b47318ab0d7ca14bd87f776240b90b21c1f4f149320"}, + {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:85210c4d99a0114f5a9481b44560d7d1e35e32cc5634c656bc48e590b669b145"}, + {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d716e2e30c6f140d7560ef1538953a5cd1a87264c737643d481f2779fc247fe1"}, + {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f66d89ba397d92f840f8654756196d93804278457b5fbede59598a1f9f90b228"}, + {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:669e193c1c576a58f132e3158f9dfa9662969edb1a250c54d8fa52590045f046"}, + {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdbe7629b996647b99c01b37f11170a57ae675375b14b8c13b8518b8320ced5"}, + {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d262606bf386a5ba0b0af3b97f37c83d7011439e3dc1a9298f21efb292e42f1a"}, + {file = "pydantic_core-2.27.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:cabb9bcb7e0d97f74df8646f34fc76fbf793b7f6dc2438517d7a9e50eee4f14d"}, + {file = "pydantic_core-2.27.2-cp38-cp38-musllinux_1_1_armv7l.whl", hash = "sha256:d2d63f1215638d28221f664596b1ccb3944f6e25dd18cd3b86b0a4c408d5ebb9"}, + {file = "pydantic_core-2.27.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:bca101c00bff0adb45a833f8451b9105d9df18accb8743b08107d7ada14bd7da"}, + {file = "pydantic_core-2.27.2-cp38-cp38-win32.whl", hash = "sha256:f6f8e111843bbb0dee4cb6594cdc73e79b3329b526037ec242a3e49012495b3b"}, + {file = "pydantic_core-2.27.2-cp38-cp38-win_amd64.whl", hash = "sha256:fd1aea04935a508f62e0d0ef1f5ae968774a32afc306fb8545e06f5ff5cdf3ad"}, + {file = "pydantic_core-2.27.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:c10eb4f1659290b523af58fa7cffb452a61ad6ae5613404519aee4bfbf1df993"}, + {file = "pydantic_core-2.27.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ef592d4bad47296fb11f96cd7dc898b92e795032b4894dfb4076cfccd43a9308"}, + {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c61709a844acc6bf0b7dce7daae75195a10aac96a596ea1b776996414791ede4"}, + {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42c5f762659e47fdb7b16956c71598292f60a03aa92f8b6351504359dbdba6cf"}, + {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4c9775e339e42e79ec99c441d9730fccf07414af63eac2f0e48e08fd38a64d76"}, + {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:57762139821c31847cfb2df63c12f725788bd9f04bc2fb392790959b8f70f118"}, + {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d1e85068e818c73e048fe28cfc769040bb1f475524f4745a5dc621f75ac7630"}, + {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:097830ed52fd9e427942ff3b9bc17fab52913b2f50f2880dc4a5611446606a54"}, + {file = "pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:044a50963a614ecfae59bb1eaf7ea7efc4bc62f49ed594e18fa1e5d953c40e9f"}, + {file = "pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:4e0b4220ba5b40d727c7f879eac379b822eee5d8fff418e9d3381ee45b3b0362"}, + {file = "pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5e4f4bb20d75e9325cc9696c6802657b58bc1dbbe3022f32cc2b2b632c3fbb96"}, + {file = "pydantic_core-2.27.2-cp39-cp39-win32.whl", hash = "sha256:cca63613e90d001b9f2f9a9ceb276c308bfa2a43fafb75c8031c4f66039e8c6e"}, + {file = "pydantic_core-2.27.2-cp39-cp39-win_amd64.whl", hash = "sha256:77d1bca19b0f7021b3a982e6f903dcd5b2b06076def36a652e3907f596e29f67"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:2bf14caea37e91198329b828eae1618c068dfb8ef17bb33287a7ad4b61ac314e"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:b0cb791f5b45307caae8810c2023a184c74605ec3bcbb67d13846c28ff731ff8"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:688d3fd9fcb71f41c4c015c023d12a79d1c4c0732ec9eb35d96e3388a120dcf3"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d591580c34f4d731592f0e9fe40f9cc1b430d297eecc70b962e93c5c668f15f"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:82f986faf4e644ffc189a7f1aafc86e46ef70372bb153e7001e8afccc6e54133"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:bec317a27290e2537f922639cafd54990551725fc844249e64c523301d0822fc"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:0296abcb83a797db256b773f45773da397da75a08f5fcaef41f2044adec05f50"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:0d75070718e369e452075a6017fbf187f788e17ed67a3abd47fa934d001863d9"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:7e17b560be3c98a8e3aa66ce828bdebb9e9ac6ad5466fba92eb74c4c95cb1151"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c33939a82924da9ed65dab5a65d427205a73181d8098e79b6b426bdf8ad4e656"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:00bad2484fa6bda1e216e7345a798bd37c68fb2d97558edd584942aa41b7d278"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c817e2b40aba42bac6f457498dacabc568c3b7a986fc9ba7c8d9d260b71485fb"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:251136cdad0cb722e93732cb45ca5299fb56e1344a833640bf93b2803f8d1bfd"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d2088237af596f0a524d3afc39ab3b036e8adb054ee57cbb1dcf8e09da5b29cc"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:d4041c0b966a84b4ae7a09832eb691a35aec90910cd2dbe7a208de59be77965b"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:8083d4e875ebe0b864ffef72a4304827015cff328a1be6e22cc850753bfb122b"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f141ee28a0ad2123b6611b6ceff018039df17f32ada8b534e6aa039545a3efb2"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7d0c8399fcc1848491f00e0314bd59fb34a9c008761bcb422a057670c3f65e35"}, + {file = "pydantic_core-2.27.2.tar.gz", hash = "sha256:eb026e5a4c1fee05726072337ff51d1efb6f59090b7da90d30ea58625b1ffb39"}, +] + +[package.dependencies] +typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" + +[[package]] +name = "pyyaml" +version = "6.0.2" +description = "YAML parser and emitter for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, + {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"}, + {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"}, + {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"}, + {file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"}, + {file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"}, + {file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"}, + {file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"}, + {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"}, + {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"}, + {file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"}, + {file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"}, + {file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"}, + {file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"}, + {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"}, + {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"}, + {file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"}, + {file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"}, + {file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"}, + {file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"}, + {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"}, + {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"}, + {file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"}, + {file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"}, + {file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"}, + {file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"}, + {file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"}, + {file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"}, + {file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"}, + {file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"}, + {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"}, + {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"}, + {file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"}, + {file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"}, + {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, +] + +[[package]] +name = "sniffio" +version = "1.3.1" +description = "Sniff out which async library your code is running under" +optional = false +python-versions = ">=3.7" +files = [ + {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, + {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, +] + +[[package]] +name = "starlette" +version = "0.46.1" +description = "The little ASGI library that shines." +optional = false +python-versions = ">=3.9" +files = [ + {file = "starlette-0.46.1-py3-none-any.whl", hash = "sha256:77c74ed9d2720138b25875133f3a2dae6d854af2ec37dceb56aef370c1d8a227"}, + {file = "starlette-0.46.1.tar.gz", hash = "sha256:3c88d58ee4bd1bb807c0d1acb381838afc7752f9ddaec81bbe4383611d833230"}, +] + +[package.dependencies] +anyio = ">=3.6.2,<5" + +[package.extras] +full = ["httpx (>=0.27.0,<0.29.0)", "itsdangerous", "jinja2", "python-multipart (>=0.0.18)", "pyyaml"] + +[[package]] +name = "tqdm" +version = "4.67.1" +description = "Fast, Extensible Progress Meter" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2"}, + {file = "tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[package.extras] +dev = ["nbval", "pytest (>=6)", "pytest-asyncio (>=0.24)", "pytest-cov", "pytest-timeout"] +discord = ["requests"] +notebook = ["ipywidgets (>=6)"] +slack = ["slack-sdk"] +telegram = ["requests"] + +[[package]] +name = "typing-extensions" +version = "4.12.2" +description = "Backported and Experimental Type Hints for Python 3.8+" +optional = false +python-versions = ">=3.8" +files = [ + {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, + {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, +] + +[[package]] +name = "uvicorn" +version = "0.34.0" +description = "The lightning-fast ASGI server." +optional = false +python-versions = ">=3.9" +files = [ + {file = "uvicorn-0.34.0-py3-none-any.whl", hash = "sha256:023dc038422502fa28a09c7a30bf2b6991512da7dcdb8fd35fe57cfc154126f4"}, + {file = "uvicorn-0.34.0.tar.gz", hash = "sha256:404051050cd7e905de2c9a7e61790943440b3416f49cb409f965d9dcd0fa73e9"}, +] + +[package.dependencies] +click = ">=7.0" +h11 = ">=0.8" +typing-extensions = {version = ">=4.0", markers = "python_version < \"3.11\""} + +[package.extras] +standard = ["colorama (>=0.4)", "httptools (>=0.6.3)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.4)"] + +[metadata] +lock-version = "2.0" +python-versions = "^3.10" +content-hash = "d005d82268b6f8c2a68b26c454bced5c34bf3c971c0cbfefde3fc0c45c675f55" diff --git a/demos/use_cases/orchestrating_agents/pyproject.toml b/demos/use_cases/orchestrating_agents/pyproject.toml new file mode 100644 index 00000000..7b422438 --- /dev/null +++ b/demos/use_cases/orchestrating_agents/pyproject.toml @@ -0,0 +1,20 @@ +[tool.poetry] +name = "api-server" +version = "0.1.0" +description = "" +authors = ["Adil Hafeez "] +readme = "README.md" + +[tool.poetry.dependencies] +python = "^3.10" +fastapi = "^0.115.4" +pyyaml = "^6.0.2" +uvicorn = "^0.34.0" +openai = "^1.66.5" + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" + +[tool.poetry.scripts] +api-server = "api_server.main:app" diff --git a/demos/use_cases/orchestrating_agents/run_demo.sh b/demos/use_cases/orchestrating_agents/run_demo.sh new file mode 100644 index 00000000..eb47dce6 --- /dev/null +++ b/demos/use_cases/orchestrating_agents/run_demo.sh @@ -0,0 +1,47 @@ +#!/bin/bash +set -e + +# Function to start the demo +start_demo() { + # Step 1: Check if .env file exists + if [ -f ".env" ]; then + echo ".env file already exists. Skipping creation." + else + # Step 2: Create `.env` file and set OpenAI key + if [ -z "$OPENAI_API_KEY" ]; then + echo "Error: OPENAI_API_KEY environment variable is not set for the demo." + exit 1 + fi + + echo "Creating .env file..." + echo "OPENAI_API_KEY=$OPENAI_API_KEY" > .env + echo ".env file created with OPENAI_API_KEY." + fi + + # Step 3: Start Arch + echo "Starting Arch with arch_config.yaml..." + archgw up arch_config.yaml + + # Step 4: Start developer services + echo "Starting Network Agent using Docker Compose..." + docker compose up -d # Run in detached mode +} + +# Function to stop the demo +stop_demo() { + # Step 1: Stop Docker Compose services + echo "Stopping Network Agent using Docker Compose..." + docker compose down + + # Step 2: Stop Arch + echo "Stopping Arch..." + archgw down +} + +# Main script logic +if [ "$1" == "down" ]; then + stop_demo +else + # Default action is to bring the demo up + start_demo +fi diff --git a/model_server/src/commons/globals.py b/model_server/src/commons/globals.py index 2f533024..49dce5e7 100644 --- a/model_server/src/commons/globals.py +++ b/model_server/src/commons/globals.py @@ -3,6 +3,8 @@ from openai import OpenAI from src.commons.utils import get_model_server_logger from src.core.guardrails import get_guardrail_handler from src.core.function_calling import ( + ArchAgentConfig, + ArchAgentHandler, ArchIntentConfig, ArchIntentHandler, ArchFunctionConfig, @@ -18,10 +20,12 @@ logger = get_model_server_logger() ARCH_ENDPOINT = os.getenv("ARCH_ENDPOINT", "https://archfc.katanemo.dev/v1") ARCH_API_KEY = "EMPTY" ARCH_CLIENT = OpenAI(base_url=ARCH_ENDPOINT, api_key=ARCH_API_KEY) +ARCH_AGENT_CLIENT = ARCH_CLIENT # Define model names ARCH_INTENT_MODEL_ALIAS = "Arch-Intent" ARCH_FUNCTION_MODEL_ALIAS = "Arch-Function" +ARCH_AGENT_MODEL_ALIAS = ARCH_FUNCTION_MODEL_ALIAS ARCH_GUARD_MODEL_ALIAS = "katanemo/Arch-Guard" # Define model handlers @@ -32,5 +36,8 @@ handler_map = { "Arch-Function": ArchFunctionHandler( ARCH_CLIENT, ARCH_FUNCTION_MODEL_ALIAS, ArchFunctionConfig ), + "Arch-Agent": ArchAgentHandler( + ARCH_AGENT_CLIENT, ARCH_AGENT_MODEL_ALIAS, ArchAgentConfig + ), "Arch-Guard": get_guardrail_handler(ARCH_GUARD_MODEL_ALIAS), } diff --git a/model_server/src/core/function_calling.py b/model_server/src/core/function_calling.py index e1cc1b94..8c01493a 100644 --- a/model_server/src/core/function_calling.py +++ b/model_server/src/core/function_calling.py @@ -1,4 +1,5 @@ import ast +import copy import json import random import builtins @@ -221,6 +222,15 @@ class ArchFunctionConfig: SUPPORT_DATA_TYPES = ["int", "float", "bool", "str", "list", "tuple", "set", "dict"] +class ArchAgentConfig(ArchFunctionConfig): + GENERATION_PARAMS = { + "temperature": 0.01, + "stop_token_ids": [151645], + "logprobs": True, + "top_logprobs": 10, + } + + class ArchFunctionHandler(ArchBaseHandler): def __init__( self, @@ -358,16 +368,17 @@ class ArchFunctionHandler(ArchBaseHandler): is_valid, error_message = False, e break - tool_calls.append( - { - "id": f"call_{random.randint(1000, 10000)}", - "type": "function", - "function": { - "name": tool_content["name"], - "arguments": tool_content["arguments"], - }, - } - ) + tool = { + "id": f"call_{random.randint(1000, 10000)}", + "type": "function", + "function": { + "name": tool_content["name"], + }, + } + if "arguments" in tool_content: + tool["function"]["arguments"] = tool_content["arguments"] + + tool_calls.append(tool) flag = False @@ -415,7 +426,9 @@ class ArchFunctionHandler(ArchBaseHandler): break func_name = tool_call["function"]["name"] - func_args = tool_call["function"]["arguments"] + func_args = tool_call["function"].get("arguments") + if not func_args: + func_args = {} # Check whether the function is available or not if func_name not in functions: @@ -430,12 +443,14 @@ class ArchFunctionHandler(ArchBaseHandler): if required_param not in func_args: is_valid = False invalid_tool_call = tool_call - error_message = f"`{required_param}` is requiried by the function `{func_name}` but not found in the tool call!" + error_message = f"`{required_param}` is required by the function `{func_name}` but not found in the tool call!" break # Verify the data type of each parameter in the tool calls function_properties = functions[func_name]["properties"] + logger.info("== func_args ==") + logger.info(func_args) for param_name in func_args: if param_name not in function_properties: is_valid = False @@ -523,7 +538,9 @@ class ArchFunctionHandler(ArchBaseHandler): req.messages, req.tools, metadata=req.metadata ) - logger.info(f"[request to arch-fc]: {json.dumps(messages)}") + logger.info( + f"[request to arch-fc]: model: {self.model_name}, extra_body: {self.generation_params}, body: {json.dumps(messages)}" + ) # always enable `stream=True` to collect model responses response = self.client.chat.completions.create( @@ -533,42 +550,48 @@ class ArchFunctionHandler(ArchBaseHandler): extra_body=self.generation_params, ) - # initialize the hallucination handler, which is an iterator - self.hallucination_state = HallucinationState( - response_iterator=response, function=req.tools - ) - + use_agent_orchestrator = req.metadata.get("use_agent_orchestrator", False) model_response = "" + if use_agent_orchestrator: + for chunk in response: + if len(chunk.choices) > 0 and chunk.choices[0].delta.content: + model_response += chunk.choices[0].delta.content + logger.info(f"[Agent Orchestrator]: response received: {model_response}") + else: + # initialize the hallucination handler, which is an iterator + self.hallucination_state = HallucinationState( + response_iterator=response, function=req.tools + ) - has_tool_calls, has_hallucination = None, False - for _ in self.hallucination_state: - # check if the first token is - if len(self.hallucination_state.tokens) > 0 and has_tool_calls is None: - if self.hallucination_state.tokens[0] == "": - has_tool_calls = True - else: - has_tool_calls = False + has_tool_calls, has_hallucination = None, False + for _ in self.hallucination_state: + # check if the first token is + if len(self.hallucination_state.tokens) > 0 and has_tool_calls is None: + if self.hallucination_state.tokens[0] == "": + has_tool_calls = True + else: + has_tool_calls = False + break + + # if the model is hallucinating, start parameter gathering + if self.hallucination_state.hallucination is True: + has_hallucination = True break - # if the model is hallucinating, start parameter gathering - if self.hallucination_state.hallucination is True: - has_hallucination = True - break - - if has_tool_calls: - if has_hallucination: - # start prompt prefilling if hallcuination is found in tool calls - logger.info( - f"[Hallucination]: {self.hallucination_state.error_message}" - ) + if has_tool_calls: + if has_hallucination: + # start prompt prefilling if hallcuination is found in tool calls + logger.info( + f"[Hallucination]: {self.hallucination_state.error_message}" + ) + prefill_response = self._engage_parameter_gathering(messages) + model_response = prefill_response.choices[0].message.content + else: + model_response = "".join(self.hallucination_state.tokens) + else: + # start parameter gathering if the model is not generating tool calls prefill_response = self._engage_parameter_gathering(messages) model_response = prefill_response.choices[0].message.content - else: - model_response = "".join(self.hallucination_state.tokens) - else: - # start parameter gathering if the model is not generating tool calls - prefill_response = self._engage_parameter_gathering(messages) - model_response = prefill_response.choices[0].message.content # Extract tool calls from model response extracted = self._extract_tool_calls(model_response) @@ -576,9 +599,14 @@ class ArchFunctionHandler(ArchBaseHandler): if extracted["status"]: # Response with tool calls if len(extracted["result"]): - verified = self._verify_tool_calls( - tools=req.tools, tool_calls=extracted["result"] - ) + verified = {} + if use_agent_orchestrator: + # skip tool call verification if using agent orchestrator + verified = {"status": True, "message": ""} + else: + verified = self._verify_tool_calls( + tools=req.tools, tool_calls=extracted["result"] + ) if verified["status"]: logger.info( @@ -601,3 +629,35 @@ class ArchFunctionHandler(ArchBaseHandler): logger.info(f"[response]: {json.dumps(chat_completion_response.model_dump())}") return chat_completion_response + + +class ArchAgentHandler(ArchFunctionHandler): + def __init__(self, client: OpenAI, model_name: str, config: ArchAgentConfig): + super().__init__(client, model_name, config) + + @override + def _convert_tools(self, tools: List[Dict[str, Any]]) -> str: + """ + Converts a list of tools into JSON format. + + Args: + tools (List[Dict[str, Any]]): A list of tools represented as dictionaries. + + Returns: + str: A string representation of converted tools. + """ + + converted = [] + # delete parameters key if its empty in tool + for tool in tools: + if ( + "parameters" in tool["function"] + and "properties" in tool["function"]["parameters"] + and not tool["function"]["parameters"]["properties"] + ): + tool_copy = copy.deepcopy(tool) + del tool_copy["function"]["parameters"] + converted.append(json.dumps(tool_copy)) + else: + converted.append(json.dumps(tool)) + return "\n".join(converted) diff --git a/model_server/src/main.py b/model_server/src/main.py index 081ee241..1763b015 100644 --- a/model_server/src/main.py +++ b/model_server/src/main.py @@ -5,6 +5,7 @@ import logging import src.commons.utils as utils from src.commons.globals import ARCH_ENDPOINT, handler_map +from src.core.function_calling import ArchFunctionHandler from src.core.utils.model_utils import ( ChatMessage, ChatCompletionResponse, @@ -76,33 +77,51 @@ async def function_calling(req: ChatMessage, res: Response): error_messages = None try: - intent_start_time = time.perf_counter() - intent_response = await handler_map["Arch-Intent"].chat_completion(req) - intent_latency = time.perf_counter() - intent_start_time + intent_detected = False + use_agent_orchestrator = req.metadata.get("use_agent_orchestrator", False) + logger.info(f"Use agent orchestrator: {use_agent_orchestrator}") + if not use_agent_orchestrator: + intent_start_time = time.perf_counter() + intent_response = await handler_map["Arch-Intent"].chat_completion(req) + intent_latency = time.perf_counter() - intent_start_time + intent_detected = handler_map["Arch-Intent"].detect_intent(intent_response) - if handler_map["Arch-Intent"].detect_intent(intent_response): + if use_agent_orchestrator or intent_detected: # TODO: measure agreement between intent detection and function calling try: function_start_time = time.perf_counter() - final_response = await handler_map["Arch-Function"].chat_completion(req) + handler_name = ( + "Arch-Agent" if use_agent_orchestrator else "Arch-Function" + ) + function_calling_handler: ArchFunctionHandler = handler_map[ + handler_name + ] + final_response = await function_calling_handler.chat_completion(req) function_latency = time.perf_counter() - function_start_time final_response.metadata = { - "intent_latency": str(round(intent_latency * 1000, 3)), "function_latency": str(round(function_latency * 1000, 3)), - "hallucination": str( - handler_map["Arch-Function"].hallucination_state.hallucination - ), } + + if not use_agent_orchestrator: + final_response.metadata["intent_latency"] = str( + round(intent_latency * 1000, 3) + ) + final_response.metadata["hallucination"] = str( + function_calling_handler.hallucination_state.hallucination + ) except ValueError as e: res.statuscode = 503 - error_messages = f"[Arch-Function] - Error in tool call extraction: {e}" + error_messages = ( + f"[{handler_name}] - Error in tool call extraction: {e}" + ) except StopIteration as e: res.statuscode = 500 - error_messages = f"[Arch-Function] - Error in hallucination check: {e}" + error_messages = f"[{handler_name}] - Error in hallucination check: {e}" except Exception as e: res.status_code = 500 - error_messages = f"[Arch-Function] - Error in ChatCompletion: {e}" + error_messages = f"[{handler_name}] - Error in ChatCompletion: {e}" + raise else: # no intent matched intent_response.metadata = { @@ -113,6 +132,7 @@ async def function_calling(req: ChatMessage, res: Response): except Exception as e: res.status_code = 500 error_messages = f"[Arch-Intent] - Error in ChatCompletion: {e}" + raise if error_messages is not None: logger.error(error_messages) diff --git a/tests/hurl/llm_gateway_simple.hurl b/tests/hurl/llm_gateway_simple.hurl new file mode 100644 index 00000000..f16e0074 --- /dev/null +++ b/tests/hurl/llm_gateway_simple.hurl @@ -0,0 +1,17 @@ +POST http://localhost:12000/v1/chat/completions +Content-Type: application/json + +{ + "model": "--", + "messages": [ + { + "role": "system", + "content": "You are a helpful assistant.\n" + }, + { + "role": "user", + "content": "I want to sell red shoes" + } + ], + "stream": true +}