Public/New-ChatGPTConversation.ps1
function New-ChatGPTConversation { <# .SYNOPSIS Create a new ChatGPT conversation or get a Chat Completion result if you specify the prompt parameter directly. .DESCRIPTION Create a new ChatGPT conversation, You can chat with the OpenAI service just like chat with a human. You can also get the chat completion result if you specify the prompt parameter. .PARAMETER api_key The API key to access OpenAI service, if not specified, the API key will be read from environment variable OPENAI_API_KEY. You can also use "token" or "access_token" or "accesstoken" as the alias. .PARAMETER model The model to use for this request, you can also set it in environment variable OPENAI_API_MODEL. If you are using Azure OpenAI Service, the model should be the deployment name you created in portal. .PARAMETER endpoint The endpoint to use for this request, you can also set it in environment variable OPENAI_API_ENDPOINT. You can also use some special value to specify the endpoint, like "ollama", "local", "kimi", "zhipu". .PARAMETER system The system prompt, this is a string, you can use it to define the role you want it be, for example, "You are a chatbot, please answer the user's question according to the user's language." If you provide a file path to this parameter, we will read the file as the system prompt. You can also specify a url to this parameter, we will read the url as the system prompt. You can read the prompt from a library (https://github.com/code365opensource/promptlibrary), by use "lib:xxxxx" as the prompt, for example, "lib:fitness". .PARAMETER prompt If you want to get result immediately, you can use this parameter to define the prompt. It will not start the chat conversation. If you provide a file path to this parameter, we will read the file as the prompt. You can also specify a url to this parameter, we will read the url as the prompt. You can read the prompt from a library (https://github.com/code365opensource/promptlibrary), by use "lib:xxxxx" as the prompt, for example, "lib:fitness". .PARAMETER config The dynamic settings for the API call, it can meet all the requirement for each model. please pass a custom object to this parameter, like @{temperature=1;max_tokens=1024}. .PARAMETER outFile If you want to save the result to a file, you can use this parameter to set the file path. You can also use "out" as the alias. .PARAMETER context If you want to pass some dymamic value to the prompt, you can use the context parameter here. It can be anything, you just specify a custom powershell object here. You define the variables in the system prompt or user prompt by using {{you_variable_name}} syntext, and then pass the data to the context parameter, like @{you_variable_name="your value"}. if there are multiple variables, you can use @{variable1="value1";variable2="value2"}. .PARAMETER headers If you want to pass some custom headers to the API call, you can use this parameter. You can pass a custom hashtable to this parameter, like @{header1="value1";header2="value2"}. .PARAMETER json Send the response in json format. .EXAMPLE New-ChatGPTConversation Use OpenAI Service with all the default settings, will read the API key from environment variable (OPENAI_API_KEY), enter the chat mode. .EXAMPLE New-ChatGPTConversation -api_key "your api key" -model "gpt-3.5-turbo" Use OpenAI Service with the specified api key and model, enter the chat mode. .EXAMPLE chat -system "You help me to translate the text to Chinese." Use OpenAI Service to translate text (system prompt specified), will read the API key from environment variable (OPENAI_API_KEY), enter the chat mode. .EXAMPLE chat -endpoint "ollama" -model "llama3" Use OpenAI Service with the local model, enter the chat mode. .EXAMPLE chat -endpoint $endpoint $env:OPENAI_API_ENDPOINT_AZURE -model $env:OPENAI_API_MODEL_AZURE -api_key $env:OPENAI_API_KEY_AZURE Use Azure OpenAI Service with the specified api key and model, enter the chat mode. .EXAMPLE gpt -system "Translate the text to Chinese." -prompt "Hello, how are you?" Use OpenAI Service to translate text (system prompt specified), will read the API key from environment variable (OPENAI_API_KEY), model from OPENAI_API_MODEL (if present) or use "gpt-3.5-turbo" as default, get the chat completion result directly. .EXAMPLE "Hello, how are you?" | gpt -system "Translate the text to Chinese." Use OpenAI Service to translate text (system prompt specified, user prompt will pass from pipeline), will read the API key from environment variable (OPENAI_API_KEY), model from OPENAI_API_MODEL (if present) or use "gpt-3.5-turbo" as default, get the chat completion result directly. .OUTPUTS System.String, the completion result. .LINK https://github.com/chenxizhang/openai-powershell #> [CmdletBinding(DefaultParameterSetName = "default")] [Alias("chatgpt")][Alias("chat")][Alias("gpt")] param( [Alias("token", "access_token", "accesstoken", "key", "apikey")] [string]$api_key, [Alias("engine", "deployment")] [string]$model, [string]$endpoint, [string]$system = "You are a chatbot, please answer the user's question according to the user's language.", [Parameter(ValueFromPipeline = $true, Position = 0)] [string]$prompt = "", [Alias("settings")] [PSCustomObject]$config, [Alias("out")] [string]$outFile, [switch]$json, [Alias("variables")] [PSCustomObject]$context, [PSCustomObject]$headers ) BEGIN { Write-Verbose ($resources.verbose_parameters_received -f ($PSBoundParameters | Out-String)) Write-Verbose ($resources.verbose_environment_received -f (Get-ChildItem Env:OPENAI_API_* | Out-String)) $api_key = ($api_key, [System.Environment]::GetEnvironmentVariable("OPENAI_API_KEY") | Where-Object { $_.Length -gt 0 } | Select-Object -First 1) $model = ($model, [System.Environment]::GetEnvironmentVariable("OPENAI_API_MODEL"), "gpt-3.5-turbo" | Where-Object { $_.Length -gt 0 } | Select-Object -First 1) $endpoint = ($endpoint, [System.Environment]::GetEnvironmentVariable("OPENAI_API_ENDPOINT"), "https://api.openai.com/v1/chat/completions" | Where-Object { $_.Length -gt 0 } | Select-Object -First 1) $endpoint = switch ($endpoint) { { $_ -in ("ollama", "local") } { "http://localhost:11434/v1/chat/completions" } "kimi" { "https://api.moonshot.cn/v1/chat/completions" } "zhipu" { "https://open.bigmodel.cn/api/paas/v4/chat/completions" } default { $endpoint } } Write-Verbose ($resources.verbose_parameters_parsed -f $api_key, $model, $endpoint) $hasError = $false if (!$api_key) { Write-Error $resources.error_missing_api_key $hasError = $true } if (!$model) { Write-Error $resources.error_missing_engine $hasError = $true } if (!$endpoint) { Write-Error $resources.error_missing_endpoint $hasError = $true } } PROCESS { if ($hasError) { return } # if endpoint contains ".openai.azure.com", then people wants to use azure openai service, try to concat the endpoint with the model if ($endpoint.EndsWith("openai.azure.com/")) { $version = Get-AzureAPIVersion $endpoint += "openai/deployments/$model/chat/completions?api-version=$version" } $telemetries = @{ type = switch ($endpoint) { { $_ -match "openai.azure.com" } { "azure" } { $_ -match "localhost" } { "local" } { $_ -match "databricks-dbrx" } { "dbrx" } { $_ -match "api.openai.com" } { "openai" } { $_ -match "platform.moonshot.cn" } { "kimi" } { $_ -match "open.bigmodel.cn" } { "zhipu" } default { $endpoint } } } # if prompt is not empty and it is a file, then read the file as the prompt $parsedprompt = Get-PromptContent($prompt) $prompt = $parsedprompt.content # if user provide the context, inject the data into the prompt by replace the context key with the context value if ($context) { Write-Verbose ($resources.verbose_context_received -f ($context | ConvertTo-Json -Depth 10)) foreach ($key in $context.keys) { $prompt = $prompt -replace "{{$key}}", $context[$key] } Write-Verbose ($resources.verbose_prompt_context_injected -f $prompt) } $telemetries.Add("promptType", $parsedprompt.type) $telemetries.Add("promptLib", $parsedprompt.lib) # if system is not empty and it is a file, then read the file as the system prompt $parsedsystem = Get-PromptContent($system) $system = $parsedsystem.content # if user provide the context, inject the data into the system prompt by replace the context key with the context value if ($context) { Write-Verbose ($resources.verbose_context_received -f ($context | ConvertTo-Json -Depth 10)) foreach ($key in $context.keys) { $system = $system -replace "{{$key}}", $context[$key] } Write-Verbose ($resources.verbose_prompt_context_injected -f $system) } $telemetries.Add("systemPromptType", $parsedsystem.type) $telemetries.Add("systemPromptLib", $parsedsystem.lib) # collect the telemetry data Submit-Telemetry -cmdletName $MyInvocation.MyCommand.Name -innovationName $MyInvocation.InvocationName -props $telemetries # add databricks support, it will use the basic authorization method, not the bearer token $azure = $endpoint.Contains("openai.azure.com") $header = if ($azure) { # if the apikey is a jwt, then use the bearer token in authorization header if ($api_key -match "^ey[a-zA-Z0-9-_]+\.[a-zA-Z0-9-_]+\.[a-zA-Z0-9-_]+$") { @{"Authorization" = "Bearer $api_key" } } else { @{"api-key" = "$api_key" } } } else { # dbrx instruct use the basic authorization method @{"Authorization" = "$(if($endpoint.Contains("databricks-dbrx-instruct")){"Basic"}else{"Bearer"}) $api_key" } } # if user provide the headers, merge the headers to the default headers if ($headers) { Merge-Hashtable -table1 $header -table2 $headers } if ($PSBoundParameters.Keys.Contains("prompt")) { Write-Verbose ($resources.verbose_prompt_mode -f $prompt) $messages = @( @{ role = "system" content = $system }, @{ role = "user" content = $prompt } ) $params = @{ Uri = $endpoint Method = "POST" Body = @{model = "$model"; messages = $messages } Headers = $header ContentType = "application/json;charset=utf-8" } if ($json) { $params.Body.Add("response_format" , @{type = "json_object" } ) } if ($config) { Merge-Hashtable -table1 $params.Body -table2 $config } $params.Body = ($params.Body | ConvertTo-Json -Depth 10) Write-Verbose ($resources.verbose_prepare_params -f ($params | ConvertTo-Json -Depth 10)) $response = Invoke-RestMethod @params if ($PSVersionTable['PSVersion'].Major -eq 5) { Write-Verbose ($resources.verbose_powershell_5_utf8) $dstEncoding = [System.Text.Encoding]::GetEncoding('iso-8859-1') $srcEncoding = [System.Text.Encoding]::UTF8 $response.choices | ForEach-Object { $_.message.content = $srcEncoding.GetString([System.Text.Encoding]::Convert($srcEncoding, $dstEncoding, $srcEncoding.GetBytes($_.message.content))) } } Write-Verbose ($resources.verbose_response_utf8 -f ($response | ConvertTo-Json -Depth 10)) $result = $response.choices[0].message.content Write-Verbose ($resources.verbose_response_plain_text -f $result) #if user specify the outfile, write the response to the file if ($outFile) { Write-Verbose ($resources.verbose_outfile_specified -f $outFile) $result | Out-File -FilePath $outFile -Encoding utf8 } # support passthru, even though user specify the outfile, we still return the result to the pipeline Write-Output $result } else { Write-Verbose ($resources.verbose_chat_mode) $stream = ($PSVersionTable['PSVersion'].Major -gt 5) $index = 1; $welcome = "`n{0}`n{1}" -f ($resources.welcome_chatgpt -f $(if ($azure) { " $($resources.azure_version) " } else { "" }), $model), $resources.shortcuts Write-Host $welcome -ForegroundColor Yellow Write-Host $system -ForegroundColor Cyan $messages = @() $systemPrompt = @( [PSCustomObject]@{ role = "system" content = $system } ) Write-Verbose "Prepare the system prompt: $($systemPrompt|ConvertTo-Json -Depth 10)" while ($true) { Write-Verbose ($resources.verbose_chat_let_chat) $current = $index++ $prompt = Read-Host -Prompt "`n[$current] $($resources.prompt)" Write-Verbose "Prompt received: $prompt" if ($prompt -in ("q", "bye")) { Write-Verbose ($resources.verbose_chat_q_message -f $prompt) break } if ($prompt -eq "m") { $os = [System.Environment]::OSVersion.Platform if ($os -notin @([System.PlatformID]::Win32NT, [System.PlatformID]::Win32Windows, [System.PlatformID]::Win32S)) { Write-Host ($resources.verbose_chat_m_message_not_supported) continue } Write-Verbose ($resources.verbose_chat_m_message) $prompt = Read-MultiLineInputBoxDialog -Message $resources.multi_line_prompt -WindowTitle $resources.multi_line_prompt -DefaultText "" Write-Verbose ($resources.verbose_prompt_received -f $prompt) if ($null -eq $prompt) { Write-Host $resources.cancel_button_message continue } else { Write-Host "$($resources.multi_line_message)`n$prompt" } } if ($prompt -eq "f") { $os = [System.Environment]::OSVersion.Platform if ($os -notin @([System.PlatformID]::Win32NT, [System.PlatformID]::Win32Windows, [System.PlatformID]::Win32S)) { Write-Host ($resources.verbose_chat_f_message_not_supported) continue } Write-Verbose ($resources.verbose_chat_f_message) $file = Read-OpenFileDialog -WindowTitle $resources.file_prompt Write-Verbose ($resources.verbose_chat_file_read -f $file) if (!($file)) { Write-Host $resources.cancel_button_message continue } else { $prompt = Get-Content $file -Encoding utf8 Write-Host "$($resources.multi_line_message)`n$prompt" } } $messages += [PSCustomObject]@{ role = "user" content = $prompt } Write-Verbose ($resources.verbose_prepare_messages -f ($messages | ConvertTo-Json -Depth 10)) $params = @{ Uri = $endpoint Method = "POST" Body = @{model = "$model"; messages = ($systemPrompt + $messages[-5..-1]); stream = $stream } Headers = $header ContentType = "application/json;charset=utf-8" } if ($json) { $params.Body.Add("response_format" , @{type = "json_object" } ) } if ($config) { Merge-Hashtable -table1 $params.Body -table2 $config } $params.Body = ($params.Body | ConvertTo-Json -Depth 10) Write-Verbose ($resources.verbose_prepare_params -f ($params | ConvertTo-Json -Depth 10)) try { if ($stream) { Write-Verbose ($resources.verbose_chat_stream_mode) $client = New-Object System.Net.Http.HttpClient $body = $params.Body Write-Verbose "body: $body" $request = [System.Net.Http.HttpRequestMessage]::new() $request.Method = "POST" $request.RequestUri = $params.Uri $request.Headers.Clear() $request.Content = [System.Net.Http.StringContent]::new(($body), [System.Text.Encoding]::UTF8) $request.Content.Headers.Clear() $request.Content.Headers.Add("Content-Type", "application/json;charset=utf-8") foreach ($k in $header.Keys) { $request.Headers.Add($k, $header[$k]) } $task = $client.Send($request) $response = $task.Content.ReadAsStream() $reader = [System.IO.StreamReader]::new($response) $result = "" # message from the api Write-Host -ForegroundColor Red "`n[$current] " -NoNewline while ($true) { $line = $reader.ReadLine() if (($line -eq $null) -or ($line -eq "data: [DONE]")) { break } $chunk = ($line -replace "data: ", "" | ConvertFrom-Json).choices.delta.content Write-Host $chunk -NoNewline -ForegroundColor Green # Write-Verbose ($resources.verbose_chat_stream_chunk_received -f $chunk) $result += $chunk Start-Sleep -Milliseconds 50 } Write-Host "" $reader.Close() $reader.Dispose() $messages += [PSCustomObject]@{ role = "assistant" content = $result } Write-Verbose ($resources.verbose_chat_message_combined -f ($messages | ConvertTo-Json -Depth 10)) Write-Host "" } else { Write-Verbose ($resources.verbose_chat_not_stream_mode) $stopwatch = [System.Diagnostics.Stopwatch]::StartNew() $response = Invoke-RestMethod @params Write-Verbose ($resources.verbose_chat_response_received -f ($response | ConvertTo-Json -Depth 10)) $stopwatch.Stop() $result = $response.choices[0].message.content $total_tokens = $response.usage.total_tokens $prompt_tokens = $response.usage.prompt_tokens $completion_tokens = $response.usage.completion_tokens Write-Verbose ($resources.verbose_chat_response_summary -f $result, $total_tokens, $prompt_tokens, $completion_tokens) if ($PSVersionTable['PSVersion'].Major -le 5) { Write-Verbose ($resources.verbose_powershell_5_utf8) $dstEncoding = [System.Text.Encoding]::GetEncoding('iso-8859-1') $srcEncoding = [System.Text.Encoding]::UTF8 $result = $srcEncoding.GetString([System.Text.Encoding]::Convert($srcEncoding, $dstEncoding, $srcEncoding.GetBytes($result))) Write-Verbose ($resouces.verbose_response_utf8 -f $result) } $messages += [PSCustomObject]@{ role = "assistant" content = $result } Write-Verbose ($resources.verbose_chat_message_combined -f ($messages | ConvertTo-Json -Depth 10)) Write-Host -ForegroundColor Red ("`n[$current] $($resources.response)" -f $total_tokens, $prompt_tokens, $completion_tokens ) Write-Host $result -ForegroundColor Green } } catch { Write-Error $_ } } } } } # SIG # Begin signature block # MIIc/wYJKoZIhvcNAQcCoIIc8DCCHOwCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCCYeNtCC2Nmt6Hx # qbKw1VuQqsO8jo++sCF4ZM9F1aEaq6CCAyowggMmMIICDqADAgECAhBcsg5m3zM9 # kUZxmeNzIQNjMA0GCSqGSIb3DQEBCwUAMCoxKDAmBgNVBAMMH0NIRU5YSVpIQU5H # IC0gQ29kZSBTaWduaW5nIENlcnQwIBcNMjQwMTA4MTMwMjA0WhgPMjA5OTEyMzEx # NjAwMDBaMCoxKDAmBgNVBAMMH0NIRU5YSVpIQU5HIC0gQ29kZSBTaWduaW5nIENl # cnQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDKDY3QG81JOKZG9jTb # QriDMDhq6gy93Pmoqgav9wErj+CgVvXKk+lGpUu74MWVyLUrJx8/ACb4b287wsXx # mQj8zQ3SqGn5CCjPKoAPsSbry0LOSl8bsFpwBr3YBJVL6cibhus2KLCbNu/u7sND # wyivKXYA1Iy1uTQPNVPcBx36krZTZyyE4CmngO75YbTMEzvHEjM3BIXdKtEt673t # iNOVSP6doh0zRwWEh2Y/eoOpv+FUokORwhKonxMtmIIET+ZPx7Ex+9aqHrliEabx # FsN4ETnuVT3rST++7Q2fquWFnl5scDnisFhU8JL8k+OGUzpLlo/nOpiRZkbKCEkZ # FCLhAgMBAAGjRjBEMA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcD # AzAdBgNVHQ4EFgQUwcR3UUOZ6TxpBp9MxnBygyIMhUQwDQYJKoZIhvcNAQELBQAD # ggEBADwiE9nowKxUNN84BTk9an1ZkdU95ouj+q6MRbafH08u4XV7CxXpkPR8Za/c # BJWTOqCuz9pMPo0TylqWPm+++Tqy1OJ7Qewvy1+DXPuFGkTqY721uZ+YsHY3CueC # VSRZRNsWSYE9UxXXFRsjDu/M3+EvyaNDE4xQkwrP8obFJoHq7WaOCCD2wMbKjLb5 # bS/VgtOK7Yn9pU/ghrW+Em+zHOX87wNRh/I5jd+LsnY8bR6REzgdmogIyvD4dsJD # /IZLxRtbm2BHOn/aGBdu+GpEaYEEb6VkWcJhrQnpiNjjlu43CbRz5Bw14XPWGUDH # +EkUqkWS4h8zsRiyvR9Pnwklg6UxghkrMIIZJwIBATA+MCoxKDAmBgNVBAMMH0NI # RU5YSVpIQU5HIC0gQ29kZSBTaWduaW5nIENlcnQCEFyyDmbfMz2RRnGZ43MhA2Mw # DQYJYIZIAWUDBAIBBQCgfDAQBgorBgEEAYI3AgEMMQIwADAZBgkqhkiG9w0BCQMx # DAYKKwYBBAGCNwIBBDAcBgorBgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkq # hkiG9w0BCQQxIgQgsuYERlVFSBFwexsPj5m8W3Fuxu0vz7K1TgRlpwGesEgwDQYJ # KoZIhvcNAQEBBQAEggEAwBO7kY6X3h1pFttreg/6ojjdBTa9LBlKYfXpYdCc2K41 # ePdffIrNtRpm7Ak1BhFWGqxgyYeslNbm873KCJLZbFO0lQCcBbsCWHTdaex4nhRe # 8iiI0reqyWrpfwtruubiw++zPOB6kRpSHTbli1Uc1esP/peF2ZozP+bSSQiX9x9t # cL7ulK6SGb0b7XsgH48OXu5/QGcl8PxStuFIMzNx5FO81afn6WfB3HYH0TTob2SX # IOdp/yUMRd7GEOmW7MsgX/AX94yDh0b9gP8FueB4KAjhi9ySB20KeoZra++YuroE # 0BDaNKwVqTcWc/o2jxtleqSXlgiF2fq+XTiRyy1LT6GCF0Awghc8BgorBgEEAYI3 # AwMBMYIXLDCCFygGCSqGSIb3DQEHAqCCFxkwghcVAgEDMQ8wDQYJYIZIAWUDBAIB # BQAweAYLKoZIhvcNAQkQAQSgaQRnMGUCAQEGCWCGSAGG/WwHATAxMA0GCWCGSAFl # AwQCAQUABCC3MytyrGS5FhKjvA+JPG9OCkABVfGarOAIS4mzaTgCnAIRAMh4j9QK # 8BnXo51LkbI8G3sYDzIwMjQwNTAyMDg1OTIyWqCCEwkwggbCMIIEqqADAgECAhAF # RK/zlJ0IOaa/2z9f5WEWMA0GCSqGSIb3DQEBCwUAMGMxCzAJBgNVBAYTAlVTMRcw # FQYDVQQKEw5EaWdpQ2VydCwgSW5jLjE7MDkGA1UEAxMyRGlnaUNlcnQgVHJ1c3Rl # ZCBHNCBSU0E0MDk2IFNIQTI1NiBUaW1lU3RhbXBpbmcgQ0EwHhcNMjMwNzE0MDAw # MDAwWhcNMzQxMDEzMjM1OTU5WjBIMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGln # aUNlcnQsIEluYy4xIDAeBgNVBAMTF0RpZ2lDZXJ0IFRpbWVzdGFtcCAyMDIzMIIC # IjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAo1NFhx2DjlusPlSzI+DPn9fl # 0uddoQ4J3C9Io5d6OyqcZ9xiFVjBqZMRp82qsmrdECmKHmJjadNYnDVxvzqX65RQ # jxwg6seaOy+WZuNp52n+W8PWKyAcwZeUtKVQgfLPywemMGjKg0La/H8JJJSkghra # arrYO8pd3hkYhftF6g1hbJ3+cV7EBpo88MUueQ8bZlLjyNY+X9pD04T10Mf2SC1e # RXWWdf7dEKEbg8G45lKVtUfXeCk5a+B4WZfjRCtK1ZXO7wgX6oJkTf8j48qG7rSk # IWRw69XloNpjsy7pBe6q9iT1HbybHLK3X9/w7nZ9MZllR1WdSiQvrCuXvp/k/Xtz # PjLuUjT71Lvr1KAsNJvj3m5kGQc3AZEPHLVRzapMZoOIaGK7vEEbeBlt5NkP4FhB # +9ixLOFRr7StFQYU6mIIE9NpHnxkTZ0P387RXoyqq1AVybPKvNfEO2hEo6U7Qv1z # fe7dCv95NBB+plwKWEwAPoVpdceDZNZ1zY8SdlalJPrXxGshuugfNJgvOuprAbD3 # +yqG7HtSOKmYCaFxsmxxrz64b5bV4RAT/mFHCoz+8LbH1cfebCTwv0KCyqBxPZyS # kwS0aXAnDU+3tTbRyV8IpHCj7ArxES5k4MsiK8rxKBMhSVF+BmbTO77665E42FEH # ypS34lCh8zrTioPLQHsCAwEAAaOCAYswggGHMA4GA1UdDwEB/wQEAwIHgDAMBgNV # HRMBAf8EAjAAMBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMIMCAGA1UdIAQZMBcwCAYG # Z4EMAQQCMAsGCWCGSAGG/WwHATAfBgNVHSMEGDAWgBS6FtltTYUvcyl2mi91jGog # j57IbzAdBgNVHQ4EFgQUpbbvE+fvzdBkodVWqWUxo97V40kwWgYDVR0fBFMwUTBP # oE2gS4ZJaHR0cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VHJ1c3RlZEc0 # UlNBNDA5NlNIQTI1NlRpbWVTdGFtcGluZ0NBLmNybDCBkAYIKwYBBQUHAQEEgYMw # gYAwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBYBggrBgEF # BQcwAoZMaHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VHJ1c3Rl # ZEc0UlNBNDA5NlNIQTI1NlRpbWVTdGFtcGluZ0NBLmNydDANBgkqhkiG9w0BAQsF # AAOCAgEAgRrW3qCptZgXvHCNT4o8aJzYJf/LLOTN6l0ikuyMIgKpuM+AqNnn48Xt # JoKKcS8Y3U623mzX4WCcK+3tPUiOuGu6fF29wmE3aEl3o+uQqhLXJ4Xzjh6S2sJA # OJ9dyKAuJXglnSoFeoQpmLZXeY/bJlYrsPOnvTcM2Jh2T1a5UsK2nTipgedtQVyM # adG5K8TGe8+c+njikxp2oml101DkRBK+IA2eqUTQ+OVJdwhaIcW0z5iVGlS6ubzB # aRm6zxbygzc0brBBJt3eWpdPM43UjXd9dUWhpVgmagNF3tlQtVCMr1a9TMXhRsUo # 063nQwBw3syYnhmJA+rUkTfvTVLzyWAhxFZH7doRS4wyw4jmWOK22z75X7BC1o/j # F5HRqsBV44a/rCcsQdCaM0qoNtS5cpZ+l3k4SF/Kwtw9Mt911jZnWon49qfH5U81 # PAC9vpwqbHkB3NpE5jreODsHXjlY9HxzMVWggBHLFAx+rrz+pOt5Zapo1iLKO+ua # gjVXKBbLafIymrLS2Dq4sUaGa7oX/cR3bBVsrquvczroSUa31X/MtjjA2Owc9bah # uEMs305MfR5ocMB3CtQC4Fxguyj/OOVSWtasFyIjTvTs0xf7UGv/B3cfcZdEQcm4 # RtNsMnxYL2dHZeUbc7aZ+WssBkbvQR7w8F/g29mtkIBEr4AQQYowggauMIIElqAD # AgECAhAHNje3JFR82Ees/ShmKl5bMA0GCSqGSIb3DQEBCwUAMGIxCzAJBgNVBAYT # AlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2Vy # dC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0IFRydXN0ZWQgUm9vdCBHNDAeFw0yMjAz # MjMwMDAwMDBaFw0zNzAzMjIyMzU5NTlaMGMxCzAJBgNVBAYTAlVTMRcwFQYDVQQK # Ew5EaWdpQ2VydCwgSW5jLjE7MDkGA1UEAxMyRGlnaUNlcnQgVHJ1c3RlZCBHNCBS # U0E0MDk2IFNIQTI1NiBUaW1lU3RhbXBpbmcgQ0EwggIiMA0GCSqGSIb3DQEBAQUA # A4ICDwAwggIKAoICAQDGhjUGSbPBPXJJUVXHJQPE8pE3qZdRodbSg9GeTKJtoLDM # g/la9hGhRBVCX6SI82j6ffOciQt/nR+eDzMfUBMLJnOWbfhXqAJ9/UO0hNoR8XOx # s+4rgISKIhjf69o9xBd/qxkrPkLcZ47qUT3w1lbU5ygt69OxtXXnHwZljZQp09ns # ad/ZkIdGAHvbREGJ3HxqV3rwN3mfXazL6IRktFLydkf3YYMZ3V+0VAshaG43IbtA # rF+y3kp9zvU5EmfvDqVjbOSmxR3NNg1c1eYbqMFkdECnwHLFuk4fsbVYTXn+149z # k6wsOeKlSNbwsDETqVcplicu9Yemj052FVUmcJgmf6AaRyBD40NjgHt1biclkJg6 # OBGz9vae5jtb7IHeIhTZgirHkr+g3uM+onP65x9abJTyUpURK1h0QCirc0PO30qh # HGs4xSnzyqqWc0Jon7ZGs506o9UD4L/wojzKQtwYSH8UNM/STKvvmz3+DrhkKvp1 # KCRB7UK/BZxmSVJQ9FHzNklNiyDSLFc1eSuo80VgvCONWPfcYd6T/jnA+bIwpUzX # 6ZhKWD7TA4j+s4/TXkt2ElGTyYwMO1uKIqjBJgj5FBASA31fI7tk42PgpuE+9sJ0 # sj8eCXbsq11GdeJgo1gJASgADoRU7s7pXcheMBK9Rp6103a50g5rmQzSM7TNsQID # AQABo4IBXTCCAVkwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUuhbZbU2F # L3MpdpovdYxqII+eyG8wHwYDVR0jBBgwFoAU7NfjgtJxXWRM3y5nP+e6mK4cD08w # DgYDVR0PAQH/BAQDAgGGMBMGA1UdJQQMMAoGCCsGAQUFBwMIMHcGCCsGAQUFBwEB # BGswaTAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tMEEGCCsG # AQUFBzAChjVodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVz # dGVkUm9vdEc0LmNydDBDBgNVHR8EPDA6MDigNqA0hjJodHRwOi8vY3JsMy5kaWdp # Y2VydC5jb20vRGlnaUNlcnRUcnVzdGVkUm9vdEc0LmNybDAgBgNVHSAEGTAXMAgG # BmeBDAEEAjALBglghkgBhv1sBwEwDQYJKoZIhvcNAQELBQADggIBAH1ZjsCTtm+Y # qUQiAX5m1tghQuGwGC4QTRPPMFPOvxj7x1Bd4ksp+3CKDaopafxpwc8dB+k+YMjY # C+VcW9dth/qEICU0MWfNthKWb8RQTGIdDAiCqBa9qVbPFXONASIlzpVpP0d3+3J0 # FNf/q0+KLHqrhc1DX+1gtqpPkWaeLJ7giqzl/Yy8ZCaHbJK9nXzQcAp876i8dU+6 # WvepELJd6f8oVInw1YpxdmXazPByoyP6wCeCRK6ZJxurJB4mwbfeKuv2nrF5mYGj # VoarCkXJ38SNoOeY+/umnXKvxMfBwWpx2cYTgAnEtp/Nh4cku0+jSbl3ZpHxcpzp # SwJSpzd+k1OsOx0ISQ+UzTl63f8lY5knLD0/a6fxZsNBzU+2QJshIUDQtxMkzdwd # eDrknq3lNHGS1yZr5Dhzq6YBT70/O3itTK37xJV77QpfMzmHQXh6OOmc4d0j/R0o # 08f56PGYX/sr2H7yRp11LB4nLCbbbxV7HhmLNriT1ObyF5lZynDwN7+YAN8gFk8n # +2BnFqFmut1VwDophrCYoCvtlUG3OtUVmDG0YgkPCr2B2RP+v6TR81fZvAT6gt4y # 3wSJ8ADNXcL50CN/AAvkdgIm2fBldkKmKYcJRyvmfxqkhQ/8mJb2VVQrH4D6wPIO # K+XW+6kvRBVK5xMOHds3OBqhK/bt1nz8MIIFjTCCBHWgAwIBAgIQDpsYjvnQLefv # 21DiCEAYWjANBgkqhkiG9w0BAQwFADBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMM # RGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQD # ExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0EwHhcNMjIwODAxMDAwMDAwWhcN # MzExMTA5MjM1OTU5WjBiMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQg # SW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2Vy # dCBUcnVzdGVkIFJvb3QgRzQwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC # AQC/5pBzaN675F1KPDAiMGkz7MKnJS7JIT3yithZwuEppz1Yq3aaza57G4QNxDAf # 8xukOBbrVsaXbR2rsnnyyhHS5F/WBTxSD1Ifxp4VpX6+n6lXFllVcq9ok3DCsrp1 # mWpzMpTREEQQLt+C8weE5nQ7bXHiLQwb7iDVySAdYyktzuxeTsiT+CFhmzTrBcZe # 7FsavOvJz82sNEBfsXpm7nfISKhmV1efVFiODCu3T6cw2Vbuyntd463JT17lNecx # y9qTXtyOj4DatpGYQJB5w3jHtrHEtWoYOAMQjdjUN6QuBX2I9YI+EJFwq1WCQTLX # 2wRzKm6RAXwhTNS8rhsDdV14Ztk6MUSaM0C/CNdaSaTC5qmgZ92kJ7yhTzm1EVgX # 9yRcRo9k98FpiHaYdj1ZXUJ2h4mXaXpI8OCiEhtmmnTK3kse5w5jrubU75KSOp49 # 3ADkRSWJtppEGSt+wJS00mFt6zPZxd9LBADMfRyVw4/3IbKyEbe7f/LVjHAsQWCq # sWMYRJUadmJ+9oCw++hkpjPRiQfhvbfmQ6QYuKZ3AeEPlAwhHbJUKSWJbOUOUlFH # dL4mrLZBdd56rF+NP8m800ERElvlEFDrMcXKchYiCd98THU/Y+whX8QgUWtvsauG # i0/C1kVfnSD8oR7FwI+isX4KJpn15GkvmB0t9dmpsh3lGwIDAQABo4IBOjCCATYw # DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU7NfjgtJxXWRM3y5nP+e6mK4cD08w # HwYDVR0jBBgwFoAUReuir/SSy4IxLVGLp6chnfNtyA8wDgYDVR0PAQH/BAQDAgGG # MHkGCCsGAQUFBwEBBG0wazAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNl # cnQuY29tMEMGCCsGAQUFBzAChjdodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20v # RGlnaUNlcnRBc3N1cmVkSURSb290Q0EuY3J0MEUGA1UdHwQ+MDwwOqA4oDaGNGh0 # dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5j # cmwwEQYDVR0gBAowCDAGBgRVHSAAMA0GCSqGSIb3DQEBDAUAA4IBAQBwoL9DXFXn # OF+go3QbPbYW1/e/Vwe9mqyhhyzshV6pGrsi+IcaaVQi7aSId229GhT0E0p6Ly23 # OO/0/4C5+KH38nLeJLxSA8hO0Cre+i1Wz/n096wwepqLsl7Uz9FDRJtDIeuWcqFI # tJnLnU+nBgMTdydE1Od/6Fmo8L8vC6bp8jQ87PcDx4eo0kxAGTVGamlUsLihVo7s # pNU96LHc/RzY9HdaXFSMb++hUD38dglohJ9vytsgjTVgHAIDyyCwrFigDkBjxZgi # wbJZ9VVrzyerbHbObyMt9H5xaiNrIv8SuFQtJ37YOtnwtoeW/VvRXKwYw02fc7cB # qZ9Xql4o4rmUMYIDdjCCA3ICAQEwdzBjMQswCQYDVQQGEwJVUzEXMBUGA1UEChMO # RGlnaUNlcnQsIEluYy4xOzA5BgNVBAMTMkRpZ2lDZXJ0IFRydXN0ZWQgRzQgUlNB # NDA5NiBTSEEyNTYgVGltZVN0YW1waW5nIENBAhAFRK/zlJ0IOaa/2z9f5WEWMA0G # CWCGSAFlAwQCAQUAoIHRMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAcBgkq # hkiG9w0BCQUxDxcNMjQwNTAyMDg1OTIyWjArBgsqhkiG9w0BCRACDDEcMBowGDAW # BBRm8CsywsLJD4JdzqqKycZPGZzPQDAvBgkqhkiG9w0BCQQxIgQg37vvNx4lQ8L9 # vVJew6xSeKpktEz9mDZh27U8Wp496LIwNwYLKoZIhvcNAQkQAi8xKDAmMCQwIgQg # 0vbkbe10IszR1EBXaEE2b4KK2lWarjMWr00amtQMeCgwDQYJKoZIhvcNAQEBBQAE # ggIAkpYTDs5BDhM4hKcC7j5CR0aKu16fTaxMFmQ5HEecx2hLdiCi5ufXssf8rXmu # S/dJgjc8+7Zzf00JCd/2/11P+4HZkWPC2U8+mQWvuj3q3j8KibYUx8dkNYWq5q5N # GyznxfV63NkahoYr252AcEvQ3HOrYDAllkiNinp+IDj4kQXQ9K+rh6Hj0vf6fOhA # h5QY/QlI1JacNs9+zuaZlnu4mnpixryjJh5kuiXL16D51uUhcHrR+SXZufGTaWrq # Mzh4NOEWXoDhG5bge9dDmW8Ypm2X7rMQ485WTmz3wRCu/AtFHuZCRMf8OVSH4Faz # hStl7oTMvSCnGJQxJJiHgGel4qHH/wNdEFaaT5dly8qZ4FY8YDtncSOPSF6xg69c # a18iepLfnynwuBFfRGtR11+SCva+tzQe1ezb1euUsKIEZyflUIyaqpntJayW2QcJ # l8VoFqwO3OgZhR594+fS7Y8y87/OLR1Eb13j4iihUPjfeAvyjA181vyiWBb2O/XN # WhZlMV0ctl+UIwJ3ojV4mlSKNRm3BrdA1YLZX1klkhqeBn+Yzf5TxIOsOzcMZqYI # OhordJqZL9zwpPWN4n/VQOC4FgXZnZnR3FgQFZoj1HSyhb50DcSSL7uAvpo0Fq/R # 9HxzPHMS0jf76NnpuoLcRadvmyInm7lTiRBgLd+ixOW2E0M= # SIG # End signature block |