Files
tm_back/pkg/ollama/service.go
T
n.nakhostin cc3d6163ed Refactor Tender Management to Introduce Notice Entity and Repository
- Replaced the tender repository with a new notice repository, encapsulating notice-related data access methods.
- Introduced the Notice entity to represent tender/contract notices, including relevant fields and methods for managing notice data.
- Updated the TED scraper to utilize the new notice repository for creating and managing notices, enhancing the integration with the tender management system.
- Implemented Ollama SDK initialization in the web bootstrap process, allowing for improved AI interactions.
- Enhanced the tender service to include Ollama SDK for additional functionality, ensuring a more robust service layer.
2025-10-04 15:22:49 +03:30

445 lines
10 KiB
Go

package ollama
import (
"context"
"fmt"
"tm/pkg/logger"
)
// Service implements the OllamaService interface
type Service struct {
client *Client
config *Config
logger logger.Logger
}
// NewService creates a new Ollama service
func NewService(client *Client) OllamaService {
return &Service{
client: client,
config: client.config,
logger: client.logger,
}
}
// Generate generates text using a specified model and prompt
func (s *Service) Generate(ctx context.Context, req *GenerateRequest) (*GenerateResponse, error) {
if s.logger != nil {
s.logger.Info("Generating text", map[string]interface{}{
"model": req.Model,
"prompt_length": len(req.Prompt),
})
}
// Set defaults
if req.Model == "" {
req.Model = s.config.DefaultModel
}
var resp GenerateResponse
err := s.client.Post(ctx, "/api/generate", req, &resp)
if err != nil {
if s.logger != nil {
s.logger.Error("Text generation failed", map[string]interface{}{
"model": req.Model,
"error": err.Error(),
})
}
return nil, ErrGenerationFailed(err.Error())
}
if s.logger != nil {
s.logger.Info("Text generation completed", map[string]interface{}{
"model": req.Model,
"response_length": len(resp.Response),
"eval_count": resp.EvalCount,
})
}
return &resp, nil
}
// GenerateWithOptions generates text with additional options
func (s *Service) GenerateWithOptions(ctx context.Context, req *GenerateRequest, opts *GenerateOptions) (*GenerateResponse, error) {
if s.logger != nil {
s.logger.Info("Generating text with options", map[string]interface{}{
"model": req.Model,
"prompt_length": len(req.Prompt),
})
}
// Apply options to request
if opts != nil {
if req.Options == nil {
req.Options = make(map[string]interface{})
}
if opts.Temperature != nil {
req.Options["temperature"] = *opts.Temperature
}
if opts.TopP != nil {
req.Options["top_p"] = *opts.TopP
}
if opts.TopK != nil {
req.Options["top_k"] = *opts.TopK
}
if opts.RepeatPenalty != nil {
req.Options["repeat_penalty"] = *opts.RepeatPenalty
}
if opts.Seed != nil {
req.Options["seed"] = *opts.Seed
}
if opts.Stop != nil {
req.Options["stop"] = opts.Stop
}
if opts.Format != "" {
req.Format = opts.Format
}
// Merge additional options
for k, v := range opts.Options {
req.Options[k] = v
}
}
return s.Generate(ctx, req)
}
// Chat performs a conversational chat with the model
func (s *Service) Chat(ctx context.Context, req *ChatRequest) (*ChatResponse, error) {
if s.logger != nil {
s.logger.Info("Starting chat", map[string]interface{}{
"model": req.Model,
"message_count": len(req.Messages),
})
}
// Set defaults
if req.Model == "" {
req.Model = s.config.DefaultModel
}
var resp ChatResponse
err := s.client.Post(ctx, "/api/chat", req, &resp)
if err != nil {
if s.logger != nil {
s.logger.Error("Chat failed", map[string]interface{}{
"model": req.Model,
"error": err.Error(),
})
}
return nil, ErrChatFailed(err.Error())
}
if s.logger != nil {
s.logger.Info("Chat completed", map[string]interface{}{
"model": req.Model,
"response_length": len(resp.Message.Content),
"eval_count": resp.EvalCount,
})
}
return &resp, nil
}
// Embed generates embeddings for the given text
func (s *Service) Embed(ctx context.Context, req *EmbedRequest) (*EmbedResponse, error) {
if s.logger != nil {
s.logger.Info("Generating embeddings", map[string]interface{}{
"model": req.Model,
"text_length": len(req.Prompt),
})
}
var resp EmbedResponse
err := s.client.Post(ctx, "/api/embeddings", req, &resp)
if err != nil {
if s.logger != nil {
s.logger.Error("Embedding generation failed", map[string]interface{}{
"model": req.Model,
"error": err.Error(),
})
}
return nil, ErrEmbeddingFailed(err.Error())
}
if s.logger != nil {
s.logger.Info("Embeddings generated", map[string]interface{}{
"model": req.Model,
"embedding_dimension": len(resp.Embedding),
})
}
return &resp, nil
}
// PullModel pulls a model from the registry
func (s *Service) PullModel(ctx context.Context, model string) (*PullResponse, error) {
if s.logger != nil {
s.logger.Info("Pulling model", map[string]interface{}{
"model": model,
})
}
req := &PullRequest{
Name: model,
}
var resp PullResponse
err := s.client.Post(ctx, "/api/pull", req, &resp)
if err != nil {
if s.logger != nil {
s.logger.Error("Model pull failed", map[string]interface{}{
"model": model,
"error": err.Error(),
})
}
return nil, ErrModelPullFailed(model, err.Error())
}
if s.logger != nil {
s.logger.Info("Model pulled successfully", map[string]interface{}{
"model": model,
"status": resp.Status,
})
}
return &resp, nil
}
// ListModels lists available models
func (s *Service) ListModels(ctx context.Context) (*ListModelsResponse, error) {
if s.logger != nil {
s.logger.Debug("Listing models", map[string]interface{}{})
}
var resp ListModelsResponse
err := s.client.Get(ctx, "/api/tags", &resp)
if err != nil {
if s.logger != nil {
s.logger.Error("Failed to list models", map[string]interface{}{
"error": err.Error(),
})
}
return nil, fmt.Errorf("failed to list models: %w", err)
}
if s.logger != nil {
s.logger.Debug("Models listed", map[string]interface{}{
"model_count": len(resp.Models),
})
}
return &resp, nil
}
// DeleteModel deletes a model
func (s *Service) DeleteModel(ctx context.Context, model string) (*DeleteResponse, error) {
if s.logger != nil {
s.logger.Info("Deleting model", map[string]interface{}{
"model": model,
})
}
req := &DeleteRequest{
Name: model,
}
var resp DeleteResponse
err := s.client.Delete(ctx, "/api/delete", req, &resp)
if err != nil {
if s.logger != nil {
s.logger.Error("Model deletion failed", map[string]interface{}{
"model": model,
"error": err.Error(),
})
}
return nil, ErrModelDeleteFailed(model, err.Error())
}
if s.logger != nil {
s.logger.Info("Model deleted successfully", map[string]interface{}{
"model": model,
"status": resp.Status,
})
}
return &resp, nil
}
// ShowModel shows model information
func (s *Service) ShowModel(ctx context.Context, model string) (*ShowModelResponse, error) {
if s.logger != nil {
s.logger.Debug("Showing model information", map[string]interface{}{
"model": model,
})
}
req := &ShowModelRequest{
Name: model,
}
var resp ShowModelResponse
err := s.client.Post(ctx, "/api/show", req, &resp)
if err != nil {
if s.logger != nil {
s.logger.Error("Failed to show model", map[string]interface{}{
"model": model,
"error": err.Error(),
})
}
return nil, fmt.Errorf("failed to show model %s: %w", model, err)
}
if s.logger != nil {
s.logger.Debug("Model information retrieved", map[string]interface{}{
"model": model,
})
}
return &resp, nil
}
// Health checks if the Ollama service is available
func (s *Service) Health(ctx context.Context) error {
if s.logger != nil {
s.logger.Debug("Checking Ollama health", map[string]interface{}{})
}
var resp HealthResponse
err := s.client.Get(ctx, "/api/version", &resp)
if err != nil {
if s.logger != nil {
s.logger.Error("Health check failed", map[string]interface{}{
"error": err.Error(),
})
}
return ErrConnectionFailed(err.Error())
}
if s.logger != nil {
s.logger.Debug("Health check passed", map[string]interface{}{})
}
return nil
}
// NewChatBuilder creates a new chat builder for conversational interactions
func (s *Service) NewChatBuilder() ChatBuilder {
return &chatBuilder{
service: s,
request: &ChatRequest{
Model: s.config.DefaultModel,
Messages: make([]Message, 0),
Options: ChatOptions{
Temperature: &s.config.DefaultTemperature,
TopP: &s.config.DefaultTopP,
},
},
}
}
// chatBuilder implements the ChatBuilder interface
type chatBuilder struct {
service OllamaService
request *ChatRequest
}
func (b *chatBuilder) SetModel(model string) ChatBuilder {
b.request.Model = model
return b
}
func (b *chatBuilder) SetSystem(system string) ChatBuilder {
// Add or update system message
for i, msg := range b.request.Messages {
if msg.Role == MessageRoleSystem {
b.request.Messages[i].Content = system
return b
}
}
// Add new system message at the beginning
systemMsg := Message{
Role: MessageRoleSystem,
Content: system,
}
b.request.Messages = append([]Message{systemMsg}, b.request.Messages...)
return b
}
func (b *chatBuilder) AddMessage(role MessageRole, content string) ChatBuilder {
msg := Message{
Role: role,
Content: content,
}
b.request.Messages = append(b.request.Messages, msg)
return b
}
func (b *chatBuilder) SetTemperature(temperature float64) ChatBuilder {
b.request.Options.Temperature = &temperature
return b
}
func (b *chatBuilder) SetTopP(topP float64) ChatBuilder {
b.request.Options.TopP = &topP
return b
}
func (b *chatBuilder) SetMaxTokens(maxTokens int) ChatBuilder {
b.request.Options.NumPredict = &maxTokens
return b
}
func (b *chatBuilder) SetStream(stream bool) ChatBuilder {
b.request.Stream = &stream
return b
}
func (b *chatBuilder) SetFormat(format string) ChatBuilder {
b.request.Format = format
return b
}
func (b *chatBuilder) SetOptions(options map[string]interface{}) ChatBuilder {
if b.request.Options.Options == nil {
b.request.Options.Options = make(map[string]interface{})
}
for k, v := range options {
b.request.Options.Options[k] = v
}
return b
}
func (b *chatBuilder) Build() (*ChatRequest, error) {
if len(b.request.Messages) == 0 {
return nil, ErrInvalidRequest("at least one message is required")
}
return b.request, nil
}
func (b *chatBuilder) Send(ctx context.Context) (*ChatResponse, error) {
req, err := b.Build()
if err != nil {
return nil, err
}
return b.service.Chat(ctx, req)
}
func (b *chatBuilder) Continue(ctx context.Context, userMessage string) (*ChatResponse, error) {
// Add user message
b.AddMessage(MessageRoleUser, userMessage)
// Send chat request
resp, err := b.Send(ctx)
if err != nil {
return nil, err
}
// Add assistant response to conversation history
b.AddMessage(MessageRoleAssistant, resp.Message.Content)
return resp, nil
}