2024-12-20 13:25:01 -08:00
import json
2024-10-03 18:21:27 -07:00
import os
2025-10-14 14:01:11 -07:00
from cli . utils import convert_legacy_listeners
2024-10-03 18:21:27 -07:00
from jinja2 import Environment , FileSystemLoader
import yaml
from jsonschema import validate
2025-03-05 17:20:04 -08:00
from urllib . parse import urlparse
2025-10-14 14:01:11 -07:00
from copy import deepcopy
2024-10-03 18:21:27 -07:00
2025-07-11 16:42:16 -07:00
2025-10-29 17:08:07 -07:00
SUPPORTED_PROVIDERS_WITH_BASE_URL = [
" azure_openai " ,
" ollama " ,
" qwen " ,
" amazon_bedrock " ,
2025-11-22 12:55:00 -08:00
" arch " ,
2025-10-29 17:08:07 -07:00
]
SUPPORTED_PROVIDERS_WITHOUT_BASE_URL = [
2025-07-11 16:42:16 -07:00
" deepseek " ,
" groq " ,
" mistral " ,
" openai " ,
" gemini " ,
2025-09-10 07:40:30 -07:00
" anthropic " ,
2025-09-18 18:36:30 -07:00
" together_ai " ,
" xai " ,
2025-09-30 12:24:06 -07:00
" moonshotai " ,
" zhipu " ,
2025-07-11 16:42:16 -07:00
]
2024-10-09 11:25:07 -07:00
2025-10-29 17:08:07 -07:00
SUPPORTED_PROVIDERS = (
SUPPORTED_PROVIDERS_WITHOUT_BASE_URL + SUPPORTED_PROVIDERS_WITH_BASE_URL
)
2024-10-09 11:25:07 -07:00
2025-02-03 14:51:59 -08:00
def get_endpoint_and_port ( endpoint , protocol ) :
endpoint_tokens = endpoint . split ( " : " )
if len ( endpoint_tokens ) > 1 :
endpoint = endpoint_tokens [ 0 ]
port = int ( endpoint_tokens [ 1 ] )
return endpoint , port
else :
if protocol == " http " :
port = 80
else :
port = 443
return endpoint , port
2024-10-03 18:21:27 -07:00
def validate_and_render_schema ( ) :
2025-07-11 16:42:16 -07:00
ENVOY_CONFIG_TEMPLATE_FILE = os . getenv (
" ENVOY_CONFIG_TEMPLATE_FILE " , " envoy.template.yaml "
)
ARCH_CONFIG_FILE = os . getenv ( " ARCH_CONFIG_FILE " , " /app/arch_config.yaml " )
ARCH_CONFIG_FILE_RENDERED = os . getenv (
" ARCH_CONFIG_FILE_RENDERED " , " /app/arch_config_rendered.yaml "
)
ENVOY_CONFIG_FILE_RENDERED = os . getenv (
" ENVOY_CONFIG_FILE_RENDERED " , " /etc/envoy/envoy.yaml "
)
ARCH_CONFIG_SCHEMA_FILE = os . getenv (
" ARCH_CONFIG_SCHEMA_FILE " , " arch_config_schema.yaml "
)
env = Environment ( loader = FileSystemLoader ( os . getenv ( " TEMPLATE_ROOT " , " ./ " ) ) )
template = env . get_template ( ENVOY_CONFIG_TEMPLATE_FILE )
2024-10-03 18:21:27 -07:00
try :
validate_prompt_config ( ARCH_CONFIG_FILE , ARCH_CONFIG_SCHEMA_FILE )
except Exception as e :
2024-10-30 17:54:51 -07:00
print ( str ( e ) )
2024-10-09 11:25:07 -07:00
exit ( 1 ) # validate_prompt_config failed. Exit
2024-10-03 18:21:27 -07:00
2024-10-09 11:25:07 -07:00
with open ( ARCH_CONFIG_FILE , " r " ) as file :
2024-10-03 18:21:27 -07:00
arch_config = file . read ( )
2024-10-09 11:25:07 -07:00
with open ( ARCH_CONFIG_SCHEMA_FILE , " r " ) as file :
2024-10-03 18:21:27 -07:00
arch_config_schema = file . read ( )
config_yaml = yaml . safe_load ( arch_config )
2025-03-19 15:21:34 -07:00
_ = yaml . safe_load ( arch_config_schema )
2024-10-03 18:21:27 -07:00
inferred_clusters = { }
2025-10-14 14:01:11 -07:00
# Convert legacy llm_providers to model_providers
if " llm_providers " in config_yaml :
if " model_providers " in config_yaml :
raise Exception (
" Please provide either llm_providers or model_providers, not both. llm_providers is deprecated, please use model_providers instead "
)
config_yaml [ " model_providers " ] = config_yaml [ " llm_providers " ]
del config_yaml [ " llm_providers " ]
listeners , llm_gateway , prompt_gateway = convert_legacy_listeners (
config_yaml . get ( " listeners " ) , config_yaml . get ( " model_providers " )
)
config_yaml [ " listeners " ] = listeners
2024-12-20 13:25:01 -08:00
endpoints = config_yaml . get ( " endpoints " , { } )
2025-10-14 14:01:11 -07:00
# Process agents section and convert to endpoints
agents = config_yaml . get ( " agents " , [ ] )
2025-12-17 17:30:14 -08:00
filters = config_yaml . get ( " filters " , [ ] )
agents_combined = agents + filters
agent_id_keys = set ( )
for agent in agents_combined :
2025-10-14 14:01:11 -07:00
agent_id = agent . get ( " id " )
2025-12-17 17:30:14 -08:00
if agent_id in agent_id_keys :
raise Exception (
f " Duplicate agent id { agent_id } , please provide unique id for each agent "
)
agent_id_keys . add ( agent_id )
2025-10-14 14:01:11 -07:00
agent_endpoint = agent . get ( " url " )
if agent_id and agent_endpoint :
urlparse_result = urlparse ( agent_endpoint )
if urlparse_result . scheme and urlparse_result . hostname :
protocol = urlparse_result . scheme
port = urlparse_result . port
if port is None :
if protocol == " http " :
port = 80
else :
port = 443
endpoints [ agent_id ] = {
" endpoint " : urlparse_result . hostname ,
" port " : port ,
" protocol " : protocol ,
}
2024-12-20 13:25:01 -08:00
# override the inferred clusters with the ones defined in the config
for name , endpoint_details in endpoints . items ( ) :
inferred_clusters [ name ] = endpoint_details
2025-10-14 14:01:11 -07:00
# Only call get_endpoint_and_port for manually defined endpoints, not agent-derived ones
if " port " not in endpoint_details :
endpoint = inferred_clusters [ name ] [ " endpoint " ]
protocol = inferred_clusters [ name ] . get ( " protocol " , " http " )
(
inferred_clusters [ name ] [ " endpoint " ] ,
inferred_clusters [ name ] [ " port " ] ,
) = get_endpoint_and_port ( endpoint , protocol )
2024-12-20 13:25:01 -08:00
print ( " defined clusters from arch_config.yaml: " , json . dumps ( inferred_clusters ) )
2024-10-28 20:05:06 -04:00
if " prompt_targets " in config_yaml :
for prompt_target in config_yaml [ " prompt_targets " ] :
2024-12-06 14:37:33 -08:00
name = prompt_target . get ( " endpoint " , { } ) . get ( " name " , None )
if not name :
continue
2024-10-28 20:05:06 -04:00
if name not in inferred_clusters :
2024-12-20 13:25:01 -08:00
raise Exception (
f " Unknown endpoint { name } , please add it in endpoints section in your arch_config.yaml file "
)
2024-10-03 18:21:27 -07:00
2024-10-08 16:24:08 -07:00
arch_tracing = config_yaml . get ( " tracing " , { } )
2025-01-17 18:25:55 -08:00
llms_with_endpoint = [ ]
2025-10-27 17:01:59 -07:00
llms_with_endpoint_cluster_names = set ( )
2025-10-14 14:01:11 -07:00
updated_model_providers = [ ]
model_provider_name_set = set ( )
llms_with_usage = [ ]
2025-07-11 16:42:16 -07:00
model_name_keys = set ( )
model_usage_name_keys = set ( )
2025-10-14 14:01:11 -07:00
print ( " listeners: " , listeners )
2025-07-11 16:42:16 -07:00
2025-10-14 14:01:11 -07:00
for listener in listeners :
2025-10-01 21:57:58 -07:00
if (
2025-10-14 14:01:11 -07:00
listener . get ( " model_providers " ) is None
or listener . get ( " model_providers " ) == [ ]
) :
continue
print ( " Processing listener with model_providers: " , listener )
name = listener . get ( " name " , None )
for model_provider in listener . get ( " model_providers " , [ ] ) :
if model_provider . get ( " usage " , None ) :
llms_with_usage . append ( model_provider [ " name " ] )
if model_provider . get ( " name " ) in model_provider_name_set :
raise Exception (
f " Duplicate model_provider name { model_provider . get ( ' name ' ) } , please provide unique name for each model_provider "
)
2025-09-18 18:36:30 -07:00
2025-10-14 14:01:11 -07:00
model_name = model_provider . get ( " model " )
print ( " Processing model_provider: " , model_provider )
if model_name in model_name_keys :
2025-07-11 16:42:16 -07:00
raise Exception (
2025-10-14 14:01:11 -07:00
f " Duplicate model name { model_name } , please provide unique model name for each model_provider "
2025-07-11 16:42:16 -07:00
)
2025-10-14 14:01:11 -07:00
model_name_keys . add ( model_name )
if model_provider . get ( " name " ) is None :
model_provider [ " name " ] = model_name
2025-07-11 16:42:16 -07:00
2025-10-14 14:01:11 -07:00
model_provider_name_set . add ( model_provider . get ( " name " ) )
2025-07-11 16:42:16 -07:00
2025-10-14 14:01:11 -07:00
model_name_tokens = model_name . split ( " / " )
if len ( model_name_tokens ) < 2 :
raise Exception (
f " Invalid model name { model_name } . Please provide model name in the format <provider>/<model_id>. "
)
provider = model_name_tokens [ 0 ]
# Validate azure_openai and ollama provider requires base_url
2025-10-29 17:08:07 -07:00
if ( provider in SUPPORTED_PROVIDERS_WITH_BASE_URL ) and model_provider . get (
" base_url "
) is None :
2025-07-11 16:42:16 -07:00
raise Exception (
2025-10-14 14:01:11 -07:00
f " Provider ' { provider } ' requires ' base_url ' to be set for model { model_name } "
2025-07-11 16:42:16 -07:00
)
2025-10-14 14:01:11 -07:00
model_id = " / " . join ( model_name_tokens [ 1 : ] )
if provider not in SUPPORTED_PROVIDERS :
if (
model_provider . get ( " base_url " , None ) is None
or model_provider . get ( " provider_interface " , None ) is None
) :
raise Exception (
f " Must provide base_url and provider_interface for unsupported provider { provider } for model { model_name } . Supported providers are: { ' , ' . join ( SUPPORTED_PROVIDERS ) } "
)
provider = model_provider . get ( " provider_interface " , None )
elif model_provider . get ( " provider_interface " , None ) is not None :
raise Exception (
f " Please provide provider interface as part of model name { model_name } using the format <provider>/<model_id>. For example, use ' openai/gpt-3.5-turbo ' instead of ' gpt-3.5-turbo ' "
)
2025-01-17 18:25:55 -08:00
2025-10-14 14:01:11 -07:00
if model_id in model_name_keys :
2025-07-11 16:42:16 -07:00
raise Exception (
2025-10-14 14:01:11 -07:00
f " Duplicate model_id { model_id } , please provide unique model_id for each model_provider "
2025-07-11 16:42:16 -07:00
)
2025-10-14 14:01:11 -07:00
model_name_keys . add ( model_id )
for routing_preference in model_provider . get ( " routing_preferences " , [ ] ) :
if routing_preference . get ( " name " ) in model_usage_name_keys :
raise Exception (
f " Duplicate routing preference name \" { routing_preference . get ( ' name ' ) } \" , please provide unique name for each routing preference "
)
model_usage_name_keys . add ( routing_preference . get ( " name " ) )
model_provider [ " model " ] = model_id
model_provider [ " provider_interface " ] = provider
model_provider_name_set . add ( model_provider . get ( " name " ) )
if model_provider . get ( " provider " ) and model_provider . get (
" provider_interface "
) :
2025-03-05 17:20:04 -08:00
raise Exception (
2025-10-14 14:01:11 -07:00
" Please provide either provider or provider_interface, not both "
2025-03-05 17:20:04 -08:00
)
2025-10-14 14:01:11 -07:00
if model_provider . get ( " provider " ) :
provider = model_provider [ " provider " ]
model_provider [ " provider_interface " ] = provider
del model_provider [ " provider " ]
updated_model_providers . append ( model_provider )
if model_provider . get ( " base_url " , None ) :
base_url = model_provider [ " base_url " ]
urlparse_result = urlparse ( base_url )
2025-10-29 17:08:07 -07:00
base_url_path_prefix = urlparse_result . path
if base_url_path_prefix and base_url_path_prefix != " / " :
# we will now support base_url_path_prefix. This means that the user can provide base_url like http://example.com/path and we will extract /path as base_url_path_prefix
model_provider [ " base_url_path_prefix " ] = base_url_path_prefix
2025-10-14 14:01:11 -07:00
if urlparse_result . scheme == " " or urlparse_result . scheme not in [
" http " ,
" https " ,
] :
raise Exception (
" Please provide a valid URL with scheme (http/https) in base_url "
)
protocol = urlparse_result . scheme
port = urlparse_result . port
if port is None :
if protocol == " http " :
port = 80
else :
port = 443
endpoint = urlparse_result . hostname
model_provider [ " endpoint " ] = endpoint
model_provider [ " port " ] = port
model_provider [ " protocol " ] = protocol
2025-10-27 17:01:59 -07:00
cluster_name = (
2025-10-14 14:01:11 -07:00
provider + " _ " + endpoint
) # make name unique by appending endpoint
2025-10-27 17:01:59 -07:00
model_provider [ " cluster_name " ] = cluster_name
# Only add if cluster_name is not already present to avoid duplicates
if cluster_name not in llms_with_endpoint_cluster_names :
llms_with_endpoint . append ( model_provider )
llms_with_endpoint_cluster_names . add ( cluster_name )
2025-05-23 08:46:12 -07:00
2025-07-11 16:42:16 -07:00
if len ( model_usage_name_keys ) > 0 :
2025-10-14 14:01:11 -07:00
routing_model_provider = config_yaml . get ( " routing " , { } ) . get (
" model_provider " , None
)
if (
routing_model_provider
and routing_model_provider not in model_provider_name_set
) :
2025-07-08 00:33:40 -07:00
raise Exception (
2025-10-14 14:01:11 -07:00
f " Routing model_provider { routing_model_provider } is not defined in model_providers "
2025-07-08 00:33:40 -07:00
)
2025-10-14 14:01:11 -07:00
if (
routing_model_provider is None
and " arch-router " not in model_provider_name_set
) :
updated_model_providers . append (
2025-07-08 00:33:40 -07:00
{
" name " : " arch-router " ,
" provider_interface " : " arch " ,
" model " : config_yaml . get ( " routing " , { } ) . get ( " model " , " Arch-Router " ) ,
}
)
2025-01-17 18:25:55 -08:00
2025-11-22 12:55:00 -08:00
# Always add arch-function model provider if not already defined
if " arch-function " not in model_provider_name_set :
updated_model_providers . append (
{
" name " : " arch-function " ,
" provider_interface " : " arch " ,
" model " : " Arch-Function " ,
}
)
2025-12-22 18:05:49 -08:00
if " plano-orchestrator " not in model_provider_name_set :
updated_model_providers . append (
{
" name " : " plano-orchestrator " ,
" provider_interface " : " arch " ,
" model " : " Plano-Orchestrator " ,
}
)
2025-10-23 18:50:23 -07:00
config_yaml [ " model_providers " ] = deepcopy ( updated_model_providers )
listeners_with_provider = 0
2025-10-14 14:01:11 -07:00
for listener in listeners :
print ( " Processing listener: " , listener )
model_providers = listener . get ( " model_providers " , None )
2025-10-23 18:50:23 -07:00
if model_providers is not None :
listeners_with_provider + = 1
if listeners_with_provider > 1 :
2025-10-14 14:01:11 -07:00
raise Exception (
" Please provide model_providers either under listeners or at root level, not both. Currently we don ' t support multiple listeners with model_providers "
)
2025-01-17 18:25:55 -08:00
2025-09-16 11:12:08 -07:00
# Validate model aliases if present
if " model_aliases " in config_yaml :
model_aliases = config_yaml [ " model_aliases " ]
for alias_name , alias_config in model_aliases . items ( ) :
target = alias_config . get ( " target " )
if target not in model_name_keys :
raise Exception (
2025-10-14 14:01:11 -07:00
f " Model alias 2 - ' { alias_name } ' targets ' { target } ' which is not defined as a model. Available models: { ' , ' . join ( sorted ( model_name_keys ) ) } "
2025-09-16 11:12:08 -07:00
)
2024-10-03 18:21:27 -07:00
arch_config_string = yaml . dump ( config_yaml )
2024-10-09 15:47:32 -07:00
arch_llm_config_string = yaml . dump ( config_yaml )
2024-10-03 18:21:27 -07:00
2025-03-19 15:21:34 -07:00
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 )
2025-07-11 16:42:16 -07:00
2024-10-03 18:21:27 -07:00
data = {
2025-10-14 14:01:11 -07:00
" prompt_gateway_listener " : prompt_gateway ,
" llm_gateway_listener " : llm_gateway ,
2024-10-09 11:25:07 -07:00
" arch_config " : arch_config_string ,
2024-10-09 15:47:32 -07:00
" arch_llm_config " : arch_llm_config_string ,
2024-10-09 11:25:07 -07:00
" arch_clusters " : inferred_clusters ,
2025-10-14 14:01:11 -07:00
" arch_model_providers " : updated_model_providers ,
2024-10-09 11:25:07 -07:00
" arch_tracing " : arch_tracing ,
2025-01-17 18:25:55 -08:00
" local_llms " : llms_with_endpoint ,
2025-03-19 15:21:34 -07:00
" agent_orchestrator " : agent_orchestrator ,
2025-10-14 14:01:11 -07:00
" listeners " : listeners ,
2024-10-03 18:21:27 -07:00
}
rendered = template . render ( data )
print ( ENVOY_CONFIG_FILE_RENDERED )
2024-12-20 13:25:01 -08:00
print ( rendered )
2024-10-09 11:25:07 -07:00
with open ( ENVOY_CONFIG_FILE_RENDERED , " w " ) as file :
2024-10-03 18:21:27 -07:00
file . write ( rendered )
2025-07-11 16:42:16 -07:00
with open ( ARCH_CONFIG_FILE_RENDERED , " w " ) as file :
file . write ( arch_config_string )
2024-10-09 11:25:07 -07:00
2024-10-03 18:21:27 -07:00
def validate_prompt_config ( arch_config_file , arch_config_schema_file ) :
2024-10-09 11:25:07 -07:00
with open ( arch_config_file , " r " ) as file :
2024-10-03 18:21:27 -07:00
arch_config = file . read ( )
2024-10-09 11:25:07 -07:00
with open ( arch_config_schema_file , " r " ) as file :
2024-10-03 18:21:27 -07:00
arch_config_schema = file . read ( )
config_yaml = yaml . safe_load ( arch_config )
config_schema_yaml = yaml . safe_load ( arch_config_schema )
try :
validate ( config_yaml , config_schema_yaml )
except Exception as e :
2024-10-09 11:25:07 -07:00
print (
2025-07-11 16:42:16 -07:00
f " Error validating arch_config file: { arch_config_file } , schema file: { arch_config_schema_file } , error: { e } "
2024-10-09 11:25:07 -07:00
)
2024-10-03 18:21:27 -07:00
raise e
2024-10-09 11:25:07 -07:00
if __name__ == " __main__ " :
2024-10-03 18:21:27 -07:00
validate_and_render_schema ( )