573 lines
16 KiB
PowerShell
573 lines
16 KiB
PowerShell
<#
|
|
.SYNOPSIS
|
|
Generic Ollama API client module for PowerShell.
|
|
|
|
.DESCRIPTION
|
|
Provides a simple interface to interact with Ollama's local LLM API:
|
|
- Text generation (chat/completion)
|
|
- Embeddings generation
|
|
- Model management
|
|
- RAG utilities (cosine similarity, clustering)
|
|
|
|
.REQUIREMENTS
|
|
- Ollama running locally (default: http://localhost:11434)
|
|
|
|
.USAGE
|
|
Import-Module .\OllamaClient.psm1
|
|
|
|
# Configure
|
|
Set-OllamaConfig -ApiUrl "http://localhost:11434"
|
|
|
|
# Check availability
|
|
if (Test-OllamaAvailable) {
|
|
# Generate text
|
|
$response = Invoke-OllamaPrompt -Model "llama3.1:8b" -Prompt "Hello!"
|
|
|
|
# Get embeddings
|
|
$embedding = Get-OllamaEmbedding -Model "nomic-embed-text" -Text "Sample text"
|
|
}
|
|
#>
|
|
|
|
# ==============================================================================
|
|
# MODULE CONFIGURATION
|
|
# ==============================================================================
|
|
|
|
$script:OllamaConfig = @{
|
|
ApiUrl = "http://localhost:11434"
|
|
DefaultTimeout = 180
|
|
DefaultTemperature = 0.2
|
|
DefaultMaxTokens = 0
|
|
DefaultContextWindow = 0
|
|
}
|
|
|
|
# ==============================================================================
|
|
# CONFIGURATION FUNCTIONS
|
|
# ==============================================================================
|
|
|
|
function Set-OllamaConfig {
|
|
<#
|
|
.SYNOPSIS
|
|
Configure Ollama client settings.
|
|
.PARAMETER ApiUrl
|
|
Ollama API endpoint URL (default: http://localhost:11434).
|
|
.PARAMETER DefaultTimeout
|
|
Default timeout in seconds for API calls.
|
|
.PARAMETER DefaultTemperature
|
|
Default temperature for text generation (0.0-1.0).
|
|
.PARAMETER DefaultMaxTokens
|
|
Default maximum tokens to generate.
|
|
.PARAMETER DefaultContextWindow
|
|
Default context window size (num_ctx).
|
|
#>
|
|
param(
|
|
[string]$ApiUrl,
|
|
[int]$DefaultTimeout,
|
|
[double]$DefaultTemperature,
|
|
[int]$DefaultMaxTokens,
|
|
[int]$DefaultContextWindow
|
|
)
|
|
|
|
if ($ApiUrl) {
|
|
$script:OllamaConfig.ApiUrl = $ApiUrl
|
|
}
|
|
|
|
if ($PSBoundParameters.ContainsKey('DefaultTimeout')) {
|
|
$script:OllamaConfig.DefaultTimeout = $DefaultTimeout
|
|
}
|
|
|
|
if ($PSBoundParameters.ContainsKey('DefaultTemperature')) {
|
|
$script:OllamaConfig.DefaultTemperature = $DefaultTemperature
|
|
}
|
|
|
|
if ($PSBoundParameters.ContainsKey('DefaultMaxTokens')) {
|
|
$script:OllamaConfig.DefaultMaxTokens = $DefaultMaxTokens
|
|
}
|
|
|
|
if ($PSBoundParameters.ContainsKey('DefaultContextWindow')) {
|
|
$script:OllamaConfig.DefaultContextWindow = $DefaultContextWindow
|
|
}
|
|
}
|
|
|
|
function Get-OllamaConfig {
|
|
<#
|
|
.SYNOPSIS
|
|
Get current Ollama client configuration.
|
|
#>
|
|
return $script:OllamaConfig.Clone()
|
|
}
|
|
|
|
# ==============================================================================
|
|
# CONNECTION & STATUS
|
|
# ==============================================================================
|
|
|
|
function Test-OllamaAvailable {
|
|
<#
|
|
.SYNOPSIS
|
|
Check if Ollama API is available and responding.
|
|
.OUTPUTS
|
|
Boolean indicating if Ollama is available.
|
|
#>
|
|
try {
|
|
$null = Invoke-RestMethod -Uri "$($script:OllamaConfig.ApiUrl)/api/tags" -TimeoutSec 5 -ErrorAction Stop
|
|
return $true
|
|
}
|
|
catch {
|
|
return $false
|
|
}
|
|
}
|
|
|
|
function Get-OllamaModels {
|
|
<#
|
|
.SYNOPSIS
|
|
Get list of available models from Ollama.
|
|
.OUTPUTS
|
|
Array of model objects with name, size, and other properties.
|
|
#>
|
|
try {
|
|
$response = Invoke-RestMethod -Uri "$($script:OllamaConfig.ApiUrl)/api/tags" -TimeoutSec 10 -ErrorAction Stop
|
|
return $response.models
|
|
}
|
|
catch {
|
|
Write-Warning "Failed to get Ollama models: $_"
|
|
return @()
|
|
}
|
|
}
|
|
|
|
function Test-OllamaModel {
|
|
<#
|
|
.SYNOPSIS
|
|
Check if a specific model is available in Ollama.
|
|
.PARAMETER Model
|
|
Model name to check.
|
|
#>
|
|
param([Parameter(Mandatory)][string]$Model)
|
|
|
|
$models = Get-OllamaModels
|
|
return ($models | Where-Object { $_.name -eq $Model -or $_.name -like "${Model}:*" }) -ne $null
|
|
}
|
|
|
|
# ==============================================================================
|
|
# TEXT GENERATION
|
|
# ==============================================================================
|
|
|
|
function Invoke-OllamaPrompt {
|
|
<#
|
|
.SYNOPSIS
|
|
Send a prompt to an Ollama model and get a response.
|
|
.PARAMETER Model
|
|
Model name (e.g., "llama3.1:8b", "qwen2.5-coder:7b").
|
|
.PARAMETER Prompt
|
|
The prompt text to send.
|
|
.PARAMETER ContextWindow
|
|
Context window size (num_ctx). Uses default if not specified.
|
|
.PARAMETER MaxTokens
|
|
Maximum tokens to generate (num_predict). Uses default if not specified.
|
|
.PARAMETER Temperature
|
|
Temperature for generation (0.0-1.0). Uses default if not specified.
|
|
.PARAMETER Timeout
|
|
Timeout in seconds. Uses default if not specified.
|
|
.PARAMETER System
|
|
Optional system prompt.
|
|
.OUTPUTS
|
|
Generated text response or $null if failed.
|
|
#>
|
|
param(
|
|
[Parameter(Mandatory)][string]$Model,
|
|
[Parameter(Mandatory)][string]$Prompt,
|
|
[int]$ContextWindow,
|
|
[int]$MaxTokens,
|
|
[double]$Temperature,
|
|
[int]$Timeout,
|
|
[string]$System
|
|
)
|
|
|
|
$config = $script:OllamaConfig
|
|
|
|
# Use defaults if not specified
|
|
if (-not $PSBoundParameters.ContainsKey('MaxTokens')) { $MaxTokens = $config.DefaultMaxTokens }
|
|
if (-not $PSBoundParameters.ContainsKey('Temperature')) { $Temperature = $config.DefaultTemperature }
|
|
if (-not $PSBoundParameters.ContainsKey('Timeout')) { $Timeout = $config.DefaultTimeout }
|
|
|
|
$options = @{
|
|
temperature = $Temperature
|
|
}
|
|
|
|
# Only set num_predict if MaxTokens > 0 (0 = unlimited/model default)
|
|
if ($MaxTokens -and $MaxTokens -gt 0) {
|
|
$options.num_predict = $MaxTokens
|
|
}
|
|
|
|
# Only set context window if explicitly provided (let model use its default otherwise)
|
|
if ($ContextWindow -and $ContextWindow -gt 0) {
|
|
$options.num_ctx = $ContextWindow
|
|
}
|
|
|
|
$body = @{
|
|
model = $Model
|
|
prompt = $Prompt
|
|
stream = $false
|
|
options = $options
|
|
}
|
|
|
|
if ($System) {
|
|
$body.system = $System
|
|
}
|
|
|
|
$jsonBody = $body | ConvertTo-Json -Depth 3
|
|
|
|
# TimeoutSec 0 = infinite wait
|
|
$restParams = @{
|
|
Uri = "$($config.ApiUrl)/api/generate"
|
|
Method = "Post"
|
|
Body = $jsonBody
|
|
ContentType = "application/json"
|
|
}
|
|
if ($Timeout -gt 0) { $restParams.TimeoutSec = $Timeout }
|
|
|
|
try {
|
|
$response = Invoke-RestMethod @restParams
|
|
return $response.response.Trim()
|
|
}
|
|
catch {
|
|
Write-Warning "Ollama prompt failed: $_"
|
|
return $null
|
|
}
|
|
}
|
|
|
|
function Invoke-OllamaChat {
|
|
<#
|
|
.SYNOPSIS
|
|
Send a chat conversation to an Ollama model.
|
|
.PARAMETER Model
|
|
Model name.
|
|
.PARAMETER Messages
|
|
Array of message objects with 'role' and 'content' properties.
|
|
Roles: "system", "user", "assistant"
|
|
.PARAMETER ContextWindow
|
|
Context window size.
|
|
.PARAMETER MaxTokens
|
|
Maximum tokens to generate.
|
|
.PARAMETER Temperature
|
|
Temperature for generation.
|
|
.OUTPUTS
|
|
Generated response text or $null if failed.
|
|
#>
|
|
param(
|
|
[Parameter(Mandatory)][string]$Model,
|
|
[Parameter(Mandatory)][array]$Messages,
|
|
[int]$ContextWindow,
|
|
[int]$MaxTokens,
|
|
[double]$Temperature,
|
|
[int]$Timeout
|
|
)
|
|
|
|
$config = $script:OllamaConfig
|
|
|
|
if (-not $PSBoundParameters.ContainsKey('MaxTokens')) { $MaxTokens = $config.DefaultMaxTokens }
|
|
if (-not $PSBoundParameters.ContainsKey('Temperature')) { $Temperature = $config.DefaultTemperature }
|
|
if (-not $PSBoundParameters.ContainsKey('Timeout')) { $Timeout = $config.DefaultTimeout }
|
|
|
|
$options = @{
|
|
temperature = $Temperature
|
|
}
|
|
|
|
# Only set num_predict if MaxTokens > 0 (0 = unlimited/model default)
|
|
if ($MaxTokens -and $MaxTokens -gt 0) {
|
|
$options.num_predict = $MaxTokens
|
|
}
|
|
|
|
# Only set context window if explicitly provided
|
|
if ($ContextWindow -and $ContextWindow -gt 0) {
|
|
$options.num_ctx = $ContextWindow
|
|
}
|
|
|
|
$body = @{
|
|
model = $Model
|
|
messages = $Messages
|
|
stream = $false
|
|
options = $options
|
|
}
|
|
|
|
$jsonBody = $body | ConvertTo-Json -Depth 4
|
|
|
|
# TimeoutSec 0 = infinite wait
|
|
$restParams = @{
|
|
Uri = "$($config.ApiUrl)/api/chat"
|
|
Method = "Post"
|
|
Body = $jsonBody
|
|
ContentType = "application/json"
|
|
}
|
|
if ($Timeout -gt 0) { $restParams.TimeoutSec = $Timeout }
|
|
|
|
try {
|
|
$response = Invoke-RestMethod @restParams
|
|
return $response.message.content.Trim()
|
|
}
|
|
catch {
|
|
Write-Warning "Ollama chat failed: $_"
|
|
return $null
|
|
}
|
|
}
|
|
|
|
# ==============================================================================
|
|
# EMBEDDINGS
|
|
# ==============================================================================
|
|
|
|
function Get-OllamaEmbedding {
|
|
<#
|
|
.SYNOPSIS
|
|
Get embedding vector for text using an Ollama embedding model.
|
|
.PARAMETER Model
|
|
Embedding model name (e.g., "nomic-embed-text", "mxbai-embed-large").
|
|
.PARAMETER Text
|
|
Text to embed.
|
|
.PARAMETER Timeout
|
|
Timeout in seconds.
|
|
.OUTPUTS
|
|
Array of doubles representing the embedding vector, or $null if failed.
|
|
#>
|
|
param(
|
|
[Parameter(Mandatory)][string]$Model,
|
|
[Parameter(Mandatory)][string]$Text,
|
|
[int]$Timeout = 30
|
|
)
|
|
|
|
$body = @{
|
|
model = $Model
|
|
prompt = $Text
|
|
} | ConvertTo-Json
|
|
|
|
try {
|
|
$response = Invoke-RestMethod -Uri "$($script:OllamaConfig.ApiUrl)/api/embeddings" -Method Post -Body $body -ContentType "application/json" -TimeoutSec $Timeout
|
|
return $response.embedding
|
|
}
|
|
catch {
|
|
Write-Warning "Ollama embedding failed: $_"
|
|
return $null
|
|
}
|
|
}
|
|
|
|
function Get-OllamaEmbeddings {
|
|
<#
|
|
.SYNOPSIS
|
|
Get embeddings for multiple texts (batch).
|
|
.PARAMETER Model
|
|
Embedding model name.
|
|
.PARAMETER Texts
|
|
Array of texts to embed.
|
|
.PARAMETER ShowProgress
|
|
Show progress indicator.
|
|
.OUTPUTS
|
|
Array of objects with Text and Embedding properties.
|
|
#>
|
|
param(
|
|
[Parameter(Mandatory)][string]$Model,
|
|
[Parameter(Mandatory)][string[]]$Texts,
|
|
[switch]$ShowProgress
|
|
)
|
|
|
|
$results = @()
|
|
$total = $Texts.Count
|
|
$current = 0
|
|
|
|
foreach ($text in $Texts) {
|
|
$current++
|
|
if ($ShowProgress) {
|
|
Write-Progress -Activity "Getting embeddings" -Status "$current of $total" -PercentComplete (($current / $total) * 100)
|
|
}
|
|
|
|
$embedding = Get-OllamaEmbedding -Model $Model -Text $text
|
|
if ($embedding) {
|
|
$results += @{
|
|
Text = $text
|
|
Embedding = $embedding
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($ShowProgress) {
|
|
Write-Progress -Activity "Getting embeddings" -Completed
|
|
}
|
|
|
|
return $results
|
|
}
|
|
|
|
# ==============================================================================
|
|
# RAG UTILITIES
|
|
# ==============================================================================
|
|
|
|
function Get-CosineSimilarity {
|
|
<#
|
|
.SYNOPSIS
|
|
Calculate cosine similarity between two embedding vectors.
|
|
.PARAMETER Vector1
|
|
First embedding vector.
|
|
.PARAMETER Vector2
|
|
Second embedding vector.
|
|
.OUTPUTS
|
|
Cosine similarity value between -1 and 1.
|
|
#>
|
|
param(
|
|
[Parameter(Mandatory)][double[]]$Vector1,
|
|
[Parameter(Mandatory)][double[]]$Vector2
|
|
)
|
|
|
|
if ($Vector1.Length -ne $Vector2.Length) {
|
|
Write-Warning "Vector lengths don't match: $($Vector1.Length) vs $($Vector2.Length)"
|
|
return 0
|
|
}
|
|
|
|
$dotProduct = 0.0
|
|
$norm1 = 0.0
|
|
$norm2 = 0.0
|
|
|
|
for ($i = 0; $i -lt $Vector1.Length; $i++) {
|
|
$dotProduct += $Vector1[$i] * $Vector2[$i]
|
|
$norm1 += $Vector1[$i] * $Vector1[$i]
|
|
$norm2 += $Vector2[$i] * $Vector2[$i]
|
|
}
|
|
|
|
$norm1 = [Math]::Sqrt($norm1)
|
|
$norm2 = [Math]::Sqrt($norm2)
|
|
|
|
if ($norm1 -eq 0 -or $norm2 -eq 0) { return 0 }
|
|
return $dotProduct / ($norm1 * $norm2)
|
|
}
|
|
|
|
function Group-TextsByEmbedding {
|
|
<#
|
|
.SYNOPSIS
|
|
Cluster texts by embedding similarity.
|
|
.PARAMETER Model
|
|
Embedding model name.
|
|
.PARAMETER Texts
|
|
Array of texts to cluster.
|
|
.PARAMETER SimilarityThreshold
|
|
Minimum cosine similarity to group texts together (0.0-1.0).
|
|
.PARAMETER ShowProgress
|
|
Show progress during embedding.
|
|
.OUTPUTS
|
|
Array of clusters (each cluster is an array of texts).
|
|
#>
|
|
param(
|
|
[Parameter(Mandatory)][string]$Model,
|
|
[Parameter(Mandatory)][string[]]$Texts,
|
|
[double]$SimilarityThreshold = 0.65,
|
|
[switch]$ShowProgress
|
|
)
|
|
|
|
if ($Texts.Length -eq 0) { return @() }
|
|
if ($Texts.Length -eq 1) { return @(,@($Texts[0])) }
|
|
|
|
# Get embeddings
|
|
$embeddings = Get-OllamaEmbeddings -Model $Model -Texts $Texts -ShowProgress:$ShowProgress
|
|
|
|
if ($embeddings.Length -eq 0) {
|
|
return @($Texts | ForEach-Object { ,@($_) })
|
|
}
|
|
|
|
# Mark all as unclustered
|
|
$embeddings | ForEach-Object { $_.Clustered = $false }
|
|
|
|
# Cluster similar texts
|
|
$clusters = @()
|
|
|
|
for ($i = 0; $i -lt $embeddings.Length; $i++) {
|
|
if ($embeddings[$i].Clustered) { continue }
|
|
|
|
$cluster = @($embeddings[$i].Text)
|
|
$embeddings[$i].Clustered = $true
|
|
|
|
for ($j = $i + 1; $j -lt $embeddings.Length; $j++) {
|
|
if ($embeddings[$j].Clustered) { continue }
|
|
|
|
$similarity = Get-CosineSimilarity -Vector1 $embeddings[$i].Embedding -Vector2 $embeddings[$j].Embedding
|
|
|
|
if ($similarity -ge $SimilarityThreshold) {
|
|
$cluster += $embeddings[$j].Text
|
|
$embeddings[$j].Clustered = $true
|
|
}
|
|
}
|
|
|
|
$clusters += ,@($cluster)
|
|
}
|
|
|
|
return $clusters
|
|
}
|
|
|
|
function Find-SimilarTexts {
|
|
<#
|
|
.SYNOPSIS
|
|
Find texts most similar to a query using embeddings.
|
|
.PARAMETER Model
|
|
Embedding model name.
|
|
.PARAMETER Query
|
|
Query text to find similar texts for.
|
|
.PARAMETER Texts
|
|
Array of texts to search through.
|
|
.PARAMETER TopK
|
|
Number of most similar texts to return.
|
|
.PARAMETER MinSimilarity
|
|
Minimum similarity threshold.
|
|
.OUTPUTS
|
|
Array of objects with Text and Similarity properties, sorted by similarity.
|
|
#>
|
|
param(
|
|
[Parameter(Mandatory)][string]$Model,
|
|
[Parameter(Mandatory)][string]$Query,
|
|
[Parameter(Mandatory)][string[]]$Texts,
|
|
[int]$TopK = 5,
|
|
[double]$MinSimilarity = 0.0
|
|
)
|
|
|
|
# Get query embedding
|
|
$queryEmbedding = Get-OllamaEmbedding -Model $Model -Text $Query
|
|
if (-not $queryEmbedding) { return @() }
|
|
|
|
# Get text embeddings and calculate similarities
|
|
$results = @()
|
|
foreach ($text in $Texts) {
|
|
$textEmbedding = Get-OllamaEmbedding -Model $Model -Text $text
|
|
if ($textEmbedding) {
|
|
$similarity = Get-CosineSimilarity -Vector1 $queryEmbedding -Vector2 $textEmbedding
|
|
if ($similarity -ge $MinSimilarity) {
|
|
$results += @{
|
|
Text = $text
|
|
Similarity = $similarity
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
# Sort by similarity and return top K
|
|
return $results | Sort-Object -Property Similarity -Descending | Select-Object -First $TopK
|
|
}
|
|
|
|
# ==============================================================================
|
|
# MODULE EXPORTS
|
|
# ==============================================================================
|
|
|
|
Export-ModuleMember -Function @(
|
|
# Configuration
|
|
'Set-OllamaConfig'
|
|
'Get-OllamaConfig'
|
|
|
|
# Connection & Status
|
|
'Test-OllamaAvailable'
|
|
'Get-OllamaModels'
|
|
'Test-OllamaModel'
|
|
|
|
# Text Generation
|
|
'Invoke-OllamaPrompt'
|
|
'Invoke-OllamaChat'
|
|
|
|
# Embeddings
|
|
'Get-OllamaEmbedding'
|
|
'Get-OllamaEmbeddings'
|
|
|
|
# RAG Utilities
|
|
'Get-CosineSimilarity'
|
|
'Group-TextsByEmbedding'
|
|
'Find-SimilarTexts'
|
|
)
|