providers/google.ps1
<#
Esta função é usada como base para invocar a a API da OpenAI! #> function InvokeGoogleApi { [CmdletBinding()] param( $endpoint ,$body ,$method = 'POST' ,$StreamCallback = $null ,$Token = $null ) $Provider = Get-AiCurrentProvider verbose "InvokeGoogleApi, current provider = $($Provider.name)" $TokenRequired = GetCurrentProviderData RequireToken; if(!$Token){ $TokenEnvName = GetCurrentProviderData TokenEnvName; if($TokenEnvName){ verbose "Trying get token from environment var: $($TokenEnvName)" $Token = (get-item "Env:$TokenEnvName" -ErrorAction SilentlyContinue).Value if($Token){ verbose " Token got from ENV VAR $TokenEnvName!"; } } } if($TokenRequired -and !$Token){ $Token = GetCurrentProviderData Token; if(!$token){ throw "POWERSHAI_GOOGLE_NOTOKEN: No token was defined and is required! Provider = $($Provider.name)"; } } $headers = @{} if($TokenRequired){ $headers["x-goog-api-key"] = "$token" } if($endpoint -match '^https?://'){ $url = $endpoint } else { $BaseUrl = GetCurrentProviderData BaseUrl $url = "$BaseUrl/$endpoint" } $JsonParams = @{Depth = 10} verbose "InvokeGoogleApi: Converting body to json (depth: $($JsonParams.Depth))... $($body|out-string)" $ReqBodyPrint = $body | ConvertTo-Json @JsonParams verbose "ReqBody:`n$($ReqBodyPrint|out-string)" $ReqBody = $null if($body){ $ReqBody = $body | ConvertTo-Json @JsonParams -Compress } $ReqParams = @{ data = $ReqBody url = $url method = $method Headers = $headers } if($StreamCallback){ $ReqParams['SseCallBack'] = $StreamCallback } verbose "ReqParams:`n$($ReqParams|out-string)" $RawResp = InvokeHttp @ReqParams verbose "RawResp: `n$($RawResp|out-string)" if($RawResp.stream){ return $RawResp; } return $RawResp.text | ConvertFrom-Json } <# .SYNOPSIS Define a API Key (o Token) da Api do Google. #> function Set-GoogleApiKey { [CmdletBinding()] param() $ErrorActionPreference = "Stop"; write-host "Forneça o token no campo senha na tela em que se abrir"; $Provider = Get-AiCurrentProvider $creds = Get-Credential "GOOGLE API KEY"; $TempToken = $creds.GetNetworkCredential().Password; write-host "Checando se o token é válido"; #try { # $result = InvokeOpenai 'models' -m 'GET' -token $TempToken #} catch [System.Net.WebException] { # $resp = $_.exception.Response; # # if($resp.StatusCode -eq 401){ # throw "INVALID_TOKEN: Token is not valid!" # } # # throw; #} #write-host -ForegroundColor green " TOKEN ALTERADO!"; SetCurrentProviderData Token $TempToken; return; } <# .SYNOPSIS Obtém a lista de modelos do Google. Endpoint: https://ai.google.dev/api/models #> function Get-GoogleModels(){ [CmdletBinding()] param() (InvokeGoogleApi -method GET "v1beta/models").models } function google_GetModels(){ param() $Models = Get-GoogleModels $Models | %{ $_.name = $_.name -replace '^models/','' } return $models; } <# .SYNOPSIS Endpoint: https://ai.google.dev/api/generate-content Stream: https://ai.google.dev/api/generate-content#method:-models.streamgeneratecontent #> function Invoke-GoogleGenerateContent { [CmdletBinding()] param( $messages = @() ,$model = "gemini-1.5-flash" ,$StreamCallback = $null ,$RawParams = @{} ,$Tools = $null ) #Note que reaproveitamos o convert to openai message do provider! $GoogleContent = @(ConvertTo-GoogleContentMessage $messages) $Config = @{} $Data = @{ contents = $GoogleContent.content systemInstruction = @{ parts = @( @{text = $GoogleContent.SystemMessage } ) } generationConfig = $Config } if($Tools){ $Data.tools = @{ function_declarations = $Tools } $Data.tool_config = @{ function_calling_config = @{ mode = "AUTO" } } } $Data = HashTableMerge $Data $RawParams; $ReqParams = @{ body = $Data method = "POST" } $OpName = "generateContent" if($StreamCallback){ $ReqParams['StreamCallback'] = $StreamCallback $OpName = "streamGenerateContent?alt=sse" } InvokeGoogleApi "v1beta/models/$($model):$OpName" @ReqParams } function ConvertTo-GoogleToolFunction { param($OpenaiTool) $FuncName = $OpenaiTool.function.name -replace '[\-\.]',''; $GoogleFunction = HashTableMerge $OpenaiTool.function @{ name = $FuncName } if($GoogleFunction.contains('arguments')){ $GoogleFunction.args = $GoogleFunction.arguments | ConvertFrom-Json; $GoogleFunction.remove('arguments'); } return $GoogleFunction; } function google_Chat { [CmdletBinding()] param( $prompt ,$temperature = 0.6 ,$model = $null ,$MaxTokens = 200 ,$ResponseFormat = $null ,$Functions = @() ,$RawParams = @{} ,$StreamCallback = $null ,[switch]$IncludeRawResp ) $CalcParams = @{ generationConfig = @{ temperature = $temperature maxOutputTokens = $MaxTokens } safetySettings = @( @{ category = "HARM_CATEGORY_HATE_SPEECH"; threshold = "BLOCK_NONE" } @{ category = "HARM_CATEGORY_SEXUALLY_EXPLICIT"; threshold = "BLOCK_NONE" } @{ category = "HARM_CATEGORY_DANGEROUS_CONTENT"; threshold = "BLOCK_NONE" } @{ category = "HARM_CATEGORY_HARASSMENT"; threshold = "BLOCK_NONE" } @{ category = 'HARM_CATEGORY_CIVIC_INTEGRITY'; threshold = 'BLOCK_NONE' } ) } if($ResponseFormat.type -eq "json_object"){ $CalcParams.generationConfig.response_mime_type = "application/json" } elseif($ResponseFormat.json_schema){ $CalcParams.generationConfig.response_mime_type = "application/json" $CalcParams.generationConfig.response_schema = $ResponseFormat.json_schema.schema } $OpenApiFunctions = @() $FunctionMap = @{} foreach($Function in $Functions){ $GoogleFunction = ConvertTo-GoogleToolFunction $Function; $GoogleName = $GoogleFunction.name; $FunctionMap[$GoogleName] = $Function.function $OpenApiFunctions += $GoogleFunction } function Convert-GoogleFunctionCallToOpenai { param($Call) $OriginalFunction = $FunctionMap[$Call.name] return [PsCustomObject]@{ id = New-OpenaiToolCallId type = "function" 'function' = @{ name = $OriginalFunction.name arguments = $Call.args | ConvertTo-Json -Depth 10 } } } function Convert-GoogleAnswerToOpenaiAnswer { param($Answer, $ChunkId) $Result = @{ _raw = @{ obj = $Answer } id = [guid]::NewGuid().Guid object = "chat.completion" created = [int](((Get-Date) - (Get-Date "1970-01-01T00:00:00Z")).TotalSeconds) service_tier = $null system_fingerprint = $null usage = @{ completion_tokens = $Answer.usageMetadata.candidatesTokenCount prompt_tokens = $Answer.usageMetadata.promptTokenCount total_tokens = $Answer.usageMetadata.totalTokenCount } logprobs = $null choices = @() } foreach($Candidate in $Answer.candidates){ $FinishReason = "stop" switch($Candidate.finishReason){ "MAX_TOKENS" { $FinishReason = "length" } { $_ -in "SAFETY","RECITATION","LANGUAGE","BLOCKLIST","PROHIBITED_CONTENT","SPII" } { $FinishReason = "content_filter" } default { if($Candidate.finishReason){ $FinishReason = $Candidate.finishReason.toLower(); } } } if($FinishReason -eq "content_filter"){ $Refusal = "Google:" + $Candidate.finishReason; } #Tools! $OpenAiTools = @() foreach($Part in $Candidate.content.parts){ if($Part.functionCall){ $OpenAiTools += Convert-GoogleFunctionCallToOpenai $Part.functionCall } } if($OpenAiTools){ $FinishReason = "tool_calls" } $NewChoice = @{ index = $Result.choices.length logprobs = $null finish_reason = $FinishReason } $RespContent = $null $Message = @{ role = "assistant" refusal = $null content = @($Candidate.content.parts)[0].text } if($OpenAiTools){ $Message.tool_calls = $OpenAiTools } if($ChunkId){ $Result.object = "chat.completion.chunk" $Result.id = $ChunkId $NewChoice.delta = $Message } else { $NewChoice.message = $Message } $Result.choices += $NewChoice; } return $Result; } $Params = @{ messages = $prompt model = $model RawParams = (HashTableMerge $CalcParams $RawParams) Tools = $OpenApiFunctions } if($StreamCallback){ # dados para serem usados durante a leitura do stream! $StreamData = @{ #Todas as respostas enviadas! answers = @() fullContent = "" #Todas as functions calls calls = @{ all = @() funcs = @{} } CurrentCall = $null ChunkId = [guid]::NewGuid().guid OpenaiAnswer = $null } $UserScriptCallback = $StreamCallback $StreamScript = { param($data) $line = $data.line if($line -like 'data: {*'){ $RawJson = $line.replace("data: ",""); } else { return; } $Answer = $RawJson | ConvertFrom-Json; $Answer | Add-Member Noteproperty _raw $data; $StreamData.answers += $Answer; $OpenaiAnswer = Convert-GoogleAnswerToOpenaiAnswer $Answer -ChunkId $StreamData.ChunkId if($StreamData.OpenaiAnswer){ if($OpenaiAnswer.choices[0].delta.content){ $StreamData.OpenaiAnswer.choices[0].delta.content += $OpenaiAnswer.choices[0].delta.content } } else { $StreamData.OpenaiAnswer = $OpenaiAnswer } if(!$StreamData.OpenaiAnswer.usage.completion_tokens){ $StreamData.OpenaiAnswer.usage.completion_tokens = 0 } if($OpenaiAnswer.usage.completion_tokens){ $StreamData.OpenaiAnswer.usage.completion_tokens += $OpenaiAnswer.usage.completion_tokens } & $UserScriptCallback $OpenaiAnswer } $Params['StreamCallback'] = $StreamScript } try { $Resp = Invoke-GoogleGenerateContent @Params } catch { $_.Exception | Add-Member -Force Noteproperty GoogleProvider @{ params = $params } throw; } if($resp.stream){ return @{ stream = @{ RawResp = $resp answers = $StreamData.answers tools = $StreamData.OpenaiAnswer.choices[0].delta.tool_calls } message = @{ role = "assistant" content = $StreamData.OpenaiAnswer.choices[0].delta.content tool_calls = $StreamData.OpenaiAnswer.choices[0].delta.tool_calls } finish_reason = $StreamData.OpenaiAnswer.choices[0].finish_reason usage = $StreamData.OpenaiAnswer.usage model = $StreamData.OpenaiAnswer.model } } else { $OpenaiAnswer = Convert-GoogleAnswerToOpenaiAnswer $resp return $OpenaiAnswer; } } <# .SYNOPSIS Converte OpenAI messages para um array de Content message https://ai.google.dev/api/caching#Content #> function ConvertTo-GoogleContentMessage { param($messages) [object[]]$messages = @(ConvertTo-OpenaiMessage $messages) $ContentMessages = @() $SystemMessage = @() $CallsIds = @{} :MsgLoop foreach($m in $messages){ $NewContent = @{ parts = @() role = $null } $MsgContent = $m.content; $NewContent.role = $m.role; # default conversion! $MsgPart = @() if($MsgContent -is [string]){ $MsgPart += @{ text = $MsgContent } } else { foreach($content in $MsgContent){ if($content.text){ $MsgPart += @{ text = $content.text } } if($content.type -eq "image_url" -and $content.image_url.url -match 'data:(.+?);base64,(.+)'){ $MimeType = $matches[1]; $Base64 = $matches[2]; $MsgPart += @{ inlineData = @{ mimeType = $MimeType; data = $Base64 }} } } } switch($m.role){ "user" { $NewContent.role = "user" } "assistant" { $NewContent.role = "model" if($m.tool_calls){ $MsgPart = @() foreach($tool in $m.tool_calls){ $GoogleFunction = ConvertTo-GoogleToolFunction $tool; $MsgPart += @{ functionCall = $GoogleFunction } $CallsIds[$tool.id] = @{ GoogleFunction = $GoogleFunction } } } } "tool" { $NewContent.role = "model" $MsgPart = @{ functionResponse = @{ name = $CallsIds[$m.tool_call_id].GoogleFunction.name response = @{ result = $m.content } } } } "system" { $SystemMessage += $MsgContent; continue MsgLoop; } default { write-warning "Message role not recognized: $($NewContent.role)"; break; } } $NewContent.parts = @($MsgPart) $ContentMessages += $NewContent; } return [PsCustomObject]@{ SystemMessage = ($SystemMessage -Join "`n") content = $ContentMessages } } Set-Alias -Name OpenAiChat -Value Get-OpenaiChat; Set-Alias -Name openai_Chat -Value Get-OpenaiChat; return @{ RequireToken = $true ApiVersion = "v1" BaseUrl = "https://generativelanguage.googleapis.com" DefaultModel = "gemini-1.5-flash" TokenEnvName = "GOOGLE_API_KEY" info = @{ desc = "Google Gemini" url = "https://ai.google.dev/" } } |