From 8bd3d9f4c4d6718f7907eea0cda5794f36cdc71a Mon Sep 17 00:00:00 2001 From: Rodrigo Ribeiro Gomes Date: Fri, 16 Aug 2024 01:14:02 -0300 Subject: [PATCH] Adicionado suporte ao Google Gemini --- powershai/powershai.psm1 | 5 +- powershai/providers/google.ps1 | 404 +++++++++++++++++++++++++++++++++ 2 files changed, 407 insertions(+), 2 deletions(-) create mode 100644 powershai/providers/google.ps1 diff --git a/powershai/powershai.psm1 b/powershai/powershai.psm1 index 8051567..50a976c 100644 --- a/powershai/powershai.psm1 +++ b/powershai/powershai.psm1 @@ -276,7 +276,7 @@ Function InvokeHttp { $Lines = @(); - while($SseResult -ne $false){ + while($SseResult -ne $false -and !$IO.EndOfStream){ verbose " Reading next line..." $line = $IO.ReadLine() @@ -687,12 +687,13 @@ function Get-AiChat { if(!$model){ $FuncParams['model'] = GetCurrentProviderData DefaultModel } + + PowerShaiProviderFunc "Chat" -FuncParams $FuncParams; } - <# .DESCRIPTION Esta é uma função auxiliar para ajudar a fazer o processamento de tools mais fácil com powershell. diff --git a/powershai/providers/google.ps1 b/powershai/providers/google.ps1 new file mode 100644 index 0000000..8f5f8c3 --- /dev/null +++ b/powershai/providers/google.ps1 @@ -0,0 +1,404 @@ +<# + 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 + write-verbose "InvokeGoogleApi, current provider = $($Provider.name)" + $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_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} + + write-verbose "InvokeGoogleApi: Converting body to json (depth: $($JsonParams.Depth))... $($body|out-string)" + $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 + } + + + if($StreamCallback){ + $ReqParams['SseCallBack'] = $StreamCallback + } + + + write-verbose "ReqParams:`n$($ReqParams|out-string)" + $RawResp = InvokeHttp @ReqParams + write-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 + ) + + + #Note que reaproveitamos o convert to openai message do provider! + $GoogleContent = @(ConvertTo-GoogleContentMessage $messages) + + $Data = @{ + contents = $GoogleContent.content + #tools = @() + systemInstruction = @{ + parts = @( + @{text = $GoogleContent.SystemMessage } + ) + } + } + + $ReqParams = @{ + body = $Data + method = "POST" + } + + $OpName = "generateContent" + if($StreamCallback){ + $ReqParams['StreamCallback'] = $StreamCallback + $OpName = "streamGenerateContent?alt=sse" + } + + InvokeGoogleApi "v1beta/models/$($model):$OpName" @ReqParams +} + +function google_Chat { + [CmdletBinding()] + param( + $prompt + ,$temperature = 0.6 + ,$model = $null + ,$MaxTokens = 200 + ,$ResponseFormat = $null + ,$Functions = @() + ,$RawParams = @{} + ,$StreamCallback = $null + ) + + $Params = @{ + messages = $prompt + model = $model + } + + 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 + } + + $UserScriptCallback = $StreamCallback + $StreamScript = { + param($data) + + $line = $data.line + + if($line -like 'data: {*'){ + $RawJson = $line.replace("data: ",""); + } else { + continue; + } + + $AnswerJson = $RawJson | ConvertFrom-Json; + + $AnswerText = $AnswerJson.candidates[0].content.parts[0].text; + $StreamData.answers += $AnswerJson; + + $StreamData.fullContent += $AnswerText + + $StdAnswer = @{ + choices = @( + @{ + delta = @{content = $AnswerText } + } + ) + } + + & $UserScriptCallback $StdAnswer + + + + # foreach($ToolCall in $DeltaResp.tool_calls){ + # $CallId = $ToolCall.id; + # $CallType = $ToolCall.type; + # verbose "Processing tool`n$($ToolCall|out-string)" + # + # + # if($CallId){ + # $StreamData.calls.all += $ToolCall; + # $StreamData.CurrentCall = $ToolCall; + # continue; + # } + # + # $CurrentCall = $StreamData.CurrentCall; + # + # + # if($CurrentCall.type -eq 'function' -and $ToolCall.function){ + # $CurrentCall.function.arguments += $ToolCall.function.arguments; + # } + # } + + # + } + + $Params['StreamCallback'] = $StreamScript + } + + $Resp = Invoke-GoogleGenerateContent @Params + + if($resp.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; + #} + + return @{ + stream = @{ + RawResp = $resp + answers = $StreamData.answers + tools = $null + } + + message = $MessageResponse + finish_reason = $StreamData.answers[-1].candidates[0].finishReason + usage = @{ + prompt_tokens = $StreamData.answers[-1].usageMetadata.promptTokenCount + completion_tokens = $StreamData.answers[-1].usageMetadata.candidatesTokenCount + total_tokens = $StreamData.answers[-1].usageMetadata.totalTokenCount + } + model = $model + } + + } + + #Nao-stream! + return @{ + choices = @( + @{ + finish_reason = $resp.finishReason + index = $resp.candidates[0].index + logprobs = $null + message = @{ + role = "assistant" + content = $resp.candidates[0].content.parts[0].text + } + } + ) + + usage = @{ + prompt_tokens = $resp.usageMetadata.promptTokenCount + completion_tokens = $resp.usageMetadata.candidatesTokenCount + total_tokens = $resp.usageMetadata.totalTokenCount + } + model = $model + created = [int](((Get-Date) - (Get-Date "1970-01-01T00:00:00Z")).TotalSeconds) + object = "chat.completion" + id = $null + system_fingerprint = $null + } +} + +<# + .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 = @() + + foreach($m in $messages){ + + $NewContent = @{ + parts = @() + role = $null + } + + $MsgContent = $m.content; + $NewContent.role = $m.role; + if($m.role -ne "user"){ + $NewContent.role = "model" + } + + if($m.role -eq "system"){ + $SystemMessage += $MsgContent; + continue; + } + + $NewContent.parts = @( + @{ text = $MsgContent } + ) + + $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 = "OpenAI" + url = "https://openai.com/" + } +} +