providers/claude.ps1
<#
Esta função é usada como base para invocar a a API da Anthropic! #> function InvokeClaude { [CmdletBinding()] param( $endpoint ,$body ,$method = 'POST' ,$StreamCallback = $null ,$Token = $null ) $Provider = Get-AiCurrentProvider $TokenRequired = GetCurrentProviderData RequireToken; if(!$Token){ $TokenEnvName = GetCurrentProviderData TokenEnvName; if($TokenEnvName){ write-verbose "Trying get token from environment var: $($TokenEnvName)" $Token = (get-item "Env:$TokenEnvName" -ErrorAction SilentlyContinue).Value } } if($TokenRequired -and !$Token){ $Token = GetCurrentProviderData Token; if(!$token){ throw "POWERSHAI_CLAUDE_NOTOKEN: No token was defined and is required! Provider = $($Provider.name)"; } } $headers = @{ "anthropic-version" = "2023-06-01" } if($TokenRequired){ $headers["x-api-key"] = "$token" } if($endpoint -match '^https?://'){ $url = $endpoint } else { $BaseUrl = GetCurrentProviderData BaseUrl $url = "$BaseUrl/$endpoint" } if($StreamCallback){ $body.stream = $true; } $JsonParams = @{Depth = 10} write-verbose "InvokeOpenai: Converting body to json (depth: $($JsonParams.Depth))..." $ReqBodyPrint = $body | ConvertTo-Json @JsonParams write-verbose "ReqBody:`n$($ReqBodyPrint|out-string)" $ReqBody = $body | ConvertTo-Json @JsonParams -Compress $ReqParams = @{ data = $ReqBody url = $url method = $method Headers = $headers } $StreamData = @{ #Todas as respostas enviadas! answers = @() fullContent = "" FinishMessage = $null #Todas as functions calls calls = @{ all = @() funcs = @{} } CurrentCall = $null CurrentEvent = $null MessageMeta = @{ stop_reason = $null model = $null id = $null usage = @{ input_tokens = 0 output_tokens = 0 } } } if($StreamCallback){ $CurrentEvent = $null; $ReqParams['SseCallBack'] = { param($data) $line = $data.line; $StreamData.lines += $line; if(!$line){ return; } if($line -match "event: (.+)"){ $StreamData.CurrentEvent = $matches[1]; return; } $CurrentEvent = $StreamData.CurrentEvent if($CurrentEvent -eq "message_stop"){ return $false; } if($CurrentEvent -eq "content_block_delta"){ $RawJson = $line.replace("data: ",""); $Answer = $RawJson | ConvertFrom-Json; $DeltaResp = $Answer.delta $StreamData.fullContent += $DeltaResp.text; $StreamData.answers += $Answer $StdAnswer = @{ choices = @( @{ delta = @{content = $DeltaResp.text} } ) } & $StreamCallback $StdAnswer } if($CurrentEvent -eq "message_start"){ $RawJson = $line.replace("data: ",""); $Answer = $RawJson | ConvertFrom-Json; $Message = $Answer.message; $StreamData.MessageMeta.usage.input_tokens = $Message.usage.input_tokens; $StreamData.MessageMeta.usage.output_tokens += $Message.usage.output_tokens; $StreamData.MessageMeta.model = $Message.model } if($CurrentEvent -eq "message_delta"){ $RawJson = $line.replace("data: ",""); $Answer = $RawJson | ConvertFrom-Json; $Usage = $Answer.usage; $StreamData.MessageMeta.usage.output_tokens += $Usage.output_tokens } } } write-verbose "ReqParams:`n$($ReqParams|out-string)" $RawResp = InvokeHttp @ReqParams write-verbose "RawResp: `n$($RawResp|out-string)" if($RawResp.stream){ #Isso imita a mensagem de resposta, para ficar igual ao resultado quando está sem Stream! $MessageResponse = @{ role = "assistant" content = $StreamData.fullContent } if($StreamData.calls.all){ $MessageResponse.tool_calls = $StreamData.calls.all; } $Usage = @{ completion_tokens = $StreamData.MessageMeta.usage.output_tokens prompt_tokens = $StreamData.MessageMeta.usage.input_tokens total_tokens = $StreamData.MessageMeta.usage.output_tokens + $StreamData.MessageMeta.usage.input_tokens } return @{ stream = @{ RawResp = $RawResp answers = $StreamData.answers tools = $StreamData.calls.all meta = $StreamData.MessageMeta } message = $MessageResponse finish_reason = $StreamData.MessageMeta.stop_reason usage = $Usage model = $StreamData.MessageMeta.model } } return $RawResp.text | ConvertFrom-Json } # Define o token a ser usado nas chamadas da OpenAI! # Faz um testes antes para certificar de que é acessível! function Set-ClaudeToken { [CmdletBinding()] param() $ErrorActionPreference = "Stop"; write-host "Forneça o token no campo senha na tela em que se abrir"; $Provider = Get-AiCurrentProvider $ProviderName = $Provider.name.toUpper(); $creds = Get-Credential "$ProviderName API TOKEN"; $TempToken = $creds.GetNetworkCredential().Password; SetCurrentProviderData Token $TempToken; return; } function Get-ClaudeMessages { [CmdletBinding()] param( $messages ,$model ,$max_tokens = 200 ,$temperature = 0.5 ,$StreamCallback = $null ) $Provider = Get-AiCurrentProvider #Note que reaproveitamos o convert to openai message do provider! [object[]]$messages = @(ConvertTo-OpenaiMessage $messages) #Separa a mensagens de system! $NewMessages = @() $SystemMessage = ""; $PrevMessage = $null foreach($m in $messages){ if($m.role -eq "system"){ $SystemMessage += $m.content; } else { $NewMessage = @{ role = $m.role; content = $m.content }; if($PrevMessage.role -eq $NewMessage.role){ $PrevMessage.content += $NewMessage.content; } else { $NewMessages += $NewMessage; } $PrevMessage = $NewMessage; } } [object[]]$FinalMessages = @( $NewMessages ) $data = @{ messages = $FinalMessages max_tokens = $max_tokens temperature = $temperature model = $model system = $SystemMessage } InvokeClaude "messages" -method POST -body $data -StreamCallback $StreamCallback; } function claude_Chat { param( $prompt ,$temperature = 0.6 ,$model = $null ,$MaxTokens = 200 ,$ResponseFormat = $null ,#Function list, as acceptable by Openai #OpenAuxFunc2Tool can be used to convert from powershell to this format! $Functions = @() #Add raw params directly to api! #overwite previous ,$RawParams = @{} ,$StreamCallback = $null ) $Params = @{ messages = $prompt temperature = $temperature model = $model max_tokens = $MaxTokens StreamCallback = $StreamCallback } if($RawParams){ foreach($Key in @($RawParams.keys)){ $Params[$key] = $RawParams[$Key] } } $result = Get-ClaudeMessages @Params if($result.stream){ return $result; } $ResultText = $result.content.text; #$ResultText = $result.answer; #if($ResponseFormat -eq "json_object"){ # if($ResultText -match '(?s)```json(.*?)```'){ # $ResultText = $matches[1]; # } else { # write-warning "Maritalk nao conseguiu retornar o JSON corretamente. A resposta será ajustada mas pode não ser no formato esperado!" # $ResultText = @{PowerShaiJsonResult = $ResultText} | ConvertTo-Json -Compress; # } # #} if($ResponseFormat){ throw "POWERSHAI_CLAUDE_FORMAT_NOTSUPPORTED: ResponseFormat not suppoted yet!" } #converte para o mesmo formato da OpenAI! return @{ choices = @( @{ finish_reason = $res.stop_reason index = 0 logprobs = $null message = @{ role = "assistant" content = $ResultText } } ) usage = @{ prompt_tokens = $result.usage.input_tokens completion_tokens = $result.usage.output_tokens total_tokens = $result.usage.input_tokens + $result.usage.output_tokens } model = $result.model created = [int](((Get-Date) - (Get-Date "1970-01-01T00:00:00Z")).TotalSeconds) object = "chat.completion" id = $result.id system_fingerprint = $null } } return @{ RequireToken = $true BaseUrl = "https://api.anthropic.com/v1" DefaultModel = "claude-3-haiku-20240307" TokenEnvName = "CLAUDE_API_KEY" info = @{ desc = "Anthropic Claude" url = "https://docs.anthropic.com/" } } |