Ollama-Chatのバックエンドにおけるモデル管理システム構築

データベーススキーマの設計

open-webuiとの互換性を確保するため、以下の必須テーブルを構築します。フィールド定義は厳密に維持する必要があります。

CREATE TABLE `conversation` (
  `identifier` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  `user_identifier` VARCHAR(255) NOT NULL DEFAULT '',
  `subject` VARCHAR(255) NOT NULL DEFAULT '',
  `content_data` LONGTEXT NOT NULL,
  `is_archived` TINYINT(1) UNSIGNED NOT NULL DEFAULT '0',
  `shared_identifier` VARCHAR(255) NOT NULL DEFAULT '',
  `created_at` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
  `updated_at` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`identifier`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE `instruction` (
  `identifier` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  `command_key` VARCHAR(255) NOT NULL DEFAULT '',
  `user_identifier` VARCHAR(255) NOT NULL DEFAULT '',
  `subject` VARCHAR(255) NOT NULL DEFAULT '',
  `content_body` LONGTEXT NOT NULL,
  `created_at` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
  `updated_at` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`identifier`),
  UNIQUE KEY `command_key` (`command_key`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

モデル生成コマンド:

goctl model mysql ddl -src ./schema/conversation.sql -dir ./internal/model -c
goctl model mysql ddl -src ./schema/instruction.sql -dir ./internal/model -c

APIインターフェース実装

open-webuiとの通信要件を満たすため、厳密なエンドポイント定義が必要です。以下は再設計したAPI仕様例:

type (
  NewConversationRequest {
    UserIdentifier string `json:"userId"`
    Subject        string `json:"title"`
    ContentData    string `json:"chat"`
    SharedId       string `json:"shareId"`
    IsArchived     int64  `json:"archived"`
  }
  ConversationDetail {
    Identifier   int64  `json:"id"`
    UserIdentifier string `json:"userId"`
    Subject      string `json:"title"`
    ContentData  string `json:"chat"`
    SharedId     string `json:"shareId"`
    IsArchived   int64  `json:"archived"`
  }
  ModelMetadata {
    Identifier       string      `json:"id"`
    DisplayName      string      `json:"name"`
    ModelName        string      `json:"model"`
    ModifiedAt       string      `json:"modified_at"`
    SizeBytes        int64       `json:"size"`
    Checksum         string      `json:"digest"`
    Architecture     ModelDetail `json:"details"`
  }
)

service OllamaBackend {
  @handler ModelList
  get /ollama/api/tags returns (ModelResponse)

  @handler ChatStream
  post /ollama/api/chat (ConversationEntity) returns (stream ChatResponse)

  @handler ConversationCreate
  post /api/v1/conversations (NewConversationRequest) returns (CreateResult)
  
  @handler ConversationFetch
  get /api/v1/conversations/:id returns (ConversationDetail)
}

API生成コマンド:

goctl api go -api ./api/ollama.api -dir ./api -style=goZero

RPCサービスの実装

内部通信用のgRPCサービスを定義します。プロトコルバッファ仕様は以下の通り:

message ConversationEntity {
  string model = 1;
  repeated MessageEntry messages = 2;
  OptionsConfig options = 3;
}

message MessageEntry {
  string role = 1;
  string content = 2;
}

message ModelResponse {
  repeated ModelMetadata models = 1;
}

service ModelService {
  rpc GenerateStream(ConversationEntity) returns (stream CompletionChunk);
  rpc FetchConversations(EmptyRequest) returns (ConversationList);
  rpc RegisterInstruction(PromptInstruction) returns (OperationResult);
}

RPCスタブ生成コマンド:

goctl rpc protoc ./rpc/ollama.proto \
  --go_out=./internal/types \
  --go-grpc_out=./internal/types \
  --zrpc_out=./rpc

設定ファイルの構成

環境依存設定をYAML形式で管理します。主要設定項目:

# APIサービス設定
Mysql:
  Host: db-host.example.com
  Port: 3306
  DbName: ollama_core
  User: service_account
  Password: "secure_password"
  MaxOpenConns: 100

OllamaEndpoint: "http://ollama-server:11434"

# RPCサービス設定
Etcd:
  Hosts: 
    - "etcd-cluster:2379"
  Key: model-service

CacheConfig:
  RedisAddress: "cache:6379"
  Database: 1

ストリーミング処理の実装

大規模言語モデルとの双方向通信を実現するため、イベント駆動型ストリーミングを採用:

func (l *ChatLogic) ProcessStream(req *types.ConversationEntity) (<-chan *types.StreamChunk, error) {
  chunkStream := make(chan *types.StreamChunk, 10)
  
  go func() {
    defer close(chunkStream)
    for {
      select {
      case chunk := <-l.ollamaClient.Generate(req):
        chunkStream <- &types.StreamChunk{Content: chunk.Text}
      case <-time.After(30 * time.Second):
        return
      }
    }
  }()
  
  return chunkStream, nil
}

この実装により、サーバー送信イベント( SSE )を介したリアルタイムレスポンスが可能となります。

タグ: Ollama go-zero goctl open-webui protobuf

7月28日 21:21 投稿