improve interacting with ollama api and models
This commit is contained in:
@@ -7,7 +7,6 @@ use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use InvalidArgumentException;
|
||||
use Laravel\Ai\Ai;
|
||||
use Laravel\Ai\Embeddings;
|
||||
use Laravel\Ai\Enums\Lab;
|
||||
use RuntimeException;
|
||||
@@ -20,6 +19,8 @@ class EmbeddingWorkbench
|
||||
*/
|
||||
protected ?Collection $ollamaModelNames = null;
|
||||
|
||||
protected ?string $ollamaErrorMessage = null;
|
||||
|
||||
public function providerName(): string
|
||||
{
|
||||
$provider = config('ai.default_for_embeddings', 'ollama');
|
||||
@@ -35,45 +36,55 @@ class EmbeddingWorkbench
|
||||
return $this->validateModel($model);
|
||||
}
|
||||
|
||||
$default = Ai::embeddingProvider($this->providerName())->defaultEmbeddingsModel();
|
||||
$availableModels = $this->availableModelNames();
|
||||
$model = $this->availableModelNames()->first();
|
||||
|
||||
if ($availableModels->contains($default)) {
|
||||
return $default;
|
||||
if ($model === null) {
|
||||
throw new InvalidArgumentException($this->unavailableMessage());
|
||||
}
|
||||
|
||||
$firstAvailableModel = $availableModels->first();
|
||||
|
||||
if ($firstAvailableModel === null) {
|
||||
throw new InvalidArgumentException('No embedding models are available.');
|
||||
}
|
||||
|
||||
return $firstAvailableModel;
|
||||
return $model;
|
||||
}
|
||||
|
||||
public function dimensions(?string $model = null): int
|
||||
{
|
||||
$this->modelName($model);
|
||||
if (trim((string) $model) !== '') {
|
||||
$this->validateModel((string) $model);
|
||||
}
|
||||
|
||||
return Ai::embeddingProvider($this->providerName())->defaultEmbeddingsDimensions();
|
||||
return (int) config('ai.providers.'.$this->providerName().'.models.embeddings.dimensions', 768);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, array{name: string, dimensions: int, default: bool}>
|
||||
* @return array<int, array{name: string, dimensions: int}>
|
||||
*/
|
||||
public function modelOptions(): array
|
||||
{
|
||||
$default = Ai::embeddingProvider($this->providerName())->defaultEmbeddingsModel();
|
||||
|
||||
return $this->availableModelNames()
|
||||
->map(fn (string $model): array => [
|
||||
'name' => $model,
|
||||
'dimensions' => $this->dimensions($model),
|
||||
'default' => $model === $default,
|
||||
])
|
||||
->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{available: bool, message: string, url: string, model_count: int}
|
||||
*/
|
||||
public function ollamaStatus(): array
|
||||
{
|
||||
$models = $this->availableModelNames();
|
||||
$modelCount = $models->count();
|
||||
|
||||
return [
|
||||
'available' => $modelCount > 0,
|
||||
'message' => $modelCount > 0
|
||||
? 'Ollama is available and returned '.$modelCount.' model'.($modelCount === 1 ? '' : 's').'.'
|
||||
: $this->unavailableMessage(),
|
||||
'url' => $this->ollamaUrl(),
|
||||
'model_count' => $modelCount,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<string> $phrases
|
||||
* @return Collection<int, EmbeddingEntry>
|
||||
@@ -318,34 +329,11 @@ class EmbeddingWorkbench
|
||||
*/
|
||||
protected function availableModelNames(): Collection
|
||||
{
|
||||
if ($this->providerName() === 'ollama') {
|
||||
$ollamaModels = $this->ollamaModelNames();
|
||||
|
||||
if ($ollamaModels->isNotEmpty()) {
|
||||
return $ollamaModels;
|
||||
}
|
||||
if ($this->providerName() !== 'ollama') {
|
||||
return collect();
|
||||
}
|
||||
|
||||
return $this->configuredModelNames();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection<int, string>
|
||||
*/
|
||||
protected function configuredModelNames(): Collection
|
||||
{
|
||||
$models = config('ai.providers.'.$this->providerName().'.models.embeddings.available', []);
|
||||
|
||||
if (! is_array($models)) {
|
||||
$models = [];
|
||||
}
|
||||
|
||||
return collect($models)
|
||||
->push(Ai::embeddingProvider($this->providerName())->defaultEmbeddingsModel())
|
||||
->map(fn (mixed $model): string => trim((string) $model))
|
||||
->filter()
|
||||
->unique()
|
||||
->values();
|
||||
return $this->ollamaModelNames();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -357,6 +345,8 @@ class EmbeddingWorkbench
|
||||
return $this->ollamaModelNames;
|
||||
}
|
||||
|
||||
$this->ollamaErrorMessage = null;
|
||||
|
||||
try {
|
||||
$models = Http::baseUrl($this->ollamaUrl())
|
||||
->acceptJson()
|
||||
@@ -365,19 +355,29 @@ class EmbeddingWorkbench
|
||||
->get('api/tags')
|
||||
->throw()
|
||||
->json('models', []);
|
||||
} catch (Throwable) {
|
||||
} catch (Throwable $exception) {
|
||||
$this->ollamaErrorMessage = 'Ollama is unavailable at '.$this->ollamaUrl().'. Start Ollama and refresh the model list. '.$exception->getMessage();
|
||||
|
||||
return $this->ollamaModelNames = collect();
|
||||
}
|
||||
|
||||
if (! is_array($models)) {
|
||||
$this->ollamaErrorMessage = 'Ollama responded, but /api/tags did not return a valid model list.';
|
||||
|
||||
return $this->ollamaModelNames = collect();
|
||||
}
|
||||
|
||||
return $this->ollamaModelNames = collect($models)
|
||||
$modelNames = collect($models)
|
||||
->map(fn (mixed $model): string => is_array($model) ? trim((string) ($model['name'] ?? '')) : '')
|
||||
->filter()
|
||||
->unique()
|
||||
->values();
|
||||
|
||||
if ($modelNames->isEmpty()) {
|
||||
$this->ollamaErrorMessage = 'Ollama is reachable at '.$this->ollamaUrl().', but it did not return any models from /api/tags. Pull an embedding model, then refresh the model list.';
|
||||
}
|
||||
|
||||
return $this->ollamaModelNames = $modelNames;
|
||||
}
|
||||
|
||||
protected function ollamaUrl(): string
|
||||
@@ -395,10 +395,20 @@ class EmbeddingWorkbench
|
||||
|
||||
$availableModels = $this->availableModelNames();
|
||||
|
||||
if ($availableModels->isEmpty()) {
|
||||
throw new InvalidArgumentException($this->unavailableMessage());
|
||||
}
|
||||
|
||||
if ($availableModels->doesntContain($model)) {
|
||||
throw new InvalidArgumentException("The embedding model [{$model}] is not available.");
|
||||
throw new InvalidArgumentException("The embedding model [{$model}] was not returned by Ollama.");
|
||||
}
|
||||
|
||||
return $model;
|
||||
}
|
||||
|
||||
protected function unavailableMessage(): string
|
||||
{
|
||||
return $this->ollamaErrorMessage
|
||||
?: 'Ollama is unavailable or returned no models. Start Ollama, pull an embedding model, then refresh the model list.';
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user