Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions docker-compose.amd.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ services:
file: docker-compose.yaml
service: dind

localrecall-postgres:
extends:
file: docker-compose.yaml
service: localrecall-postgres

localrecall:
extends:
file: docker-compose.yaml
Expand Down
5 changes: 5 additions & 0 deletions docker-compose.intel.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ services:
file: docker-compose.yaml
service: dind

localrecall-postgres:
extends:
file: docker-compose.yaml
service: localrecall-postgres

localrecall:
extends:
file: docker-compose.yaml
Expand Down
5 changes: 5 additions & 0 deletions docker-compose.nvidia.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ services:
file: docker-compose.yaml
service: dind

localrecall-postgres:
extends:
file: docker-compose.yaml
service: localrecall-postgres

localrecall:
extends:
file: docker-compose.yaml
Expand Down
32 changes: 29 additions & 3 deletions docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,18 +26,41 @@ services:
- ./volumes/backends:/backends
- ./volumes/images:/tmp/generated/images

localrecall-postgres:
image: quay.io/mudler/localrecall:${LOCALRECALL_VERSION:-v0.5.2}-postgresql
environment:
- POSTGRES_DB=localrecall
- POSTGRES_USER=localrecall
- POSTGRES_PASSWORD=localrecall
ports:
- 5432:5432
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U localrecall"]
interval: 10s
timeout: 5s
retries: 5

localrecall:
image: quay.io/mudler/localrecall:main
image: quay.io/mudler/localrecall:${LOCALRECALL_VERSION:-v0.5.2}
depends_on:
localrecall-postgres:
condition: service_healthy
localai:
condition: service_started
ports:
- 8080
environment:
- COLLECTION_DB_PATH=/db
- DATABASE_URL=postgresql://localrecall:localrecall@localrecall-postgres:5432/localrecall?sslmode=disable
- VECTOR_ENGINE=postgres
- EMBEDDING_MODEL=granite-embedding-107m-multilingual
- FILE_ASSETS=/assets
- OPENAI_API_KEY=sk-1234567890
- OPENAI_BASE_URL=http://localai:8080
- HYBRID_SEARCH_BM25_WEIGHT=0.5
- HYBRID_SEARCH_VECTOR_WEIGHT=0.5
volumes:
- ./volumes/localrag/db:/db
- ./volumes/localrag/assets/:/assets

localrecall-healthcheck:
Expand Down Expand Up @@ -102,3 +125,6 @@ services:
- "host.docker.internal:host-gateway"
volumes:
- ./volumes/localagi/:/pool

volumes:
postgres_data:
152 changes: 108 additions & 44 deletions pkg/localrag/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,32 @@ func (c *WrappedClient) Store(s string) error {
return c.Client.Store(c.collection, f)
}

// apiResponse is the standardized LocalRecall API response wrapper (since 3f73ff3a).
type apiResponse struct {
Success bool `json:"success"`
Message string `json:"message,omitempty"`
Data json.RawMessage `json:"data,omitempty"`
Error *apiError `json:"error,omitempty"`
}

type apiError struct {
Code string `json:"code"`
Message string `json:"message"`
Details string `json:"details,omitempty"`
}

// parseAPIError reads the response body and returns an error from the API response or a generic message.
func parseAPIError(resp *http.Response, body []byte, fallback string) error {
var wrap apiResponse
if err := json.Unmarshal(body, &wrap); err == nil && wrap.Error != nil {
if wrap.Error.Details != "" {
return fmt.Errorf("%s: %s", wrap.Error.Message, wrap.Error.Details)
}
return errors.New(wrap.Error.Message)
}
return fmt.Errorf("%s: %s", fallback, string(body))
}

// Result represents a single result from a query.
type Result struct {
ID string
Expand Down Expand Up @@ -153,7 +179,8 @@ func (c *Client) CreateCollection(name string) error {
defer resp.Body.Close()

if resp.StatusCode != http.StatusCreated {
return errors.New("failed to create collection")
body, _ := io.ReadAll(resp.Body)
return parseAPIError(resp, body, "failed to create collection")
}

return nil
Expand All @@ -176,17 +203,30 @@ func (c *Client) ListCollections() ([]string, error) {
}
defer resp.Body.Close()

body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}

if resp.StatusCode != http.StatusOK {
return nil, errors.New("failed to list collections")
return nil, parseAPIError(resp, body, "failed to list collections")
}

var collections []string
err = json.NewDecoder(resp.Body).Decode(&collections)
if err != nil {
return nil, err
var wrap apiResponse
if err := json.Unmarshal(body, &wrap); err != nil || !wrap.Success {
if wrap.Error != nil {
return nil, errors.New(wrap.Error.Message)
}
return nil, fmt.Errorf("invalid response: %w", err)
}

return collections, nil
var data struct {
Collections []string `json:"collections"`
}
if err := json.Unmarshal(wrap.Data, &data); err != nil {
return nil, err
}
return data.Collections, nil
}

// ListEntries lists all entries in a collection
Expand All @@ -206,17 +246,30 @@ func (c *Client) ListEntries(collection string) ([]string, error) {
}
defer resp.Body.Close()

body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}

if resp.StatusCode != http.StatusOK {
return nil, errors.New("failed to list entries")
return nil, parseAPIError(resp, body, "failed to list entries")
}

var entries []string
err = json.NewDecoder(resp.Body).Decode(&entries)
if err != nil {
return nil, err
var wrap apiResponse
if err := json.Unmarshal(body, &wrap); err != nil || !wrap.Success {
if wrap.Error != nil {
return nil, errors.New(wrap.Error.Message)
}
return nil, fmt.Errorf("invalid response: %w", err)
}

return entries, nil
var data struct {
Entries []string `json:"entries"`
}
if err := json.Unmarshal(wrap.Data, &data); err != nil {
return nil, err
}
return data.Entries, nil
}

// DeleteEntry deletes an entry in a collection
Expand Down Expand Up @@ -246,19 +299,30 @@ func (c *Client) DeleteEntry(collection, entry string) ([]string, error) {
}
defer resp.Body.Close()

body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}

if resp.StatusCode != http.StatusOK {
bodyResult := new(bytes.Buffer)
bodyResult.ReadFrom(resp.Body)
return nil, errors.New("failed to delete entry: " + bodyResult.String())
return nil, parseAPIError(resp, body, "failed to delete entry")
}

var results []string
err = json.NewDecoder(resp.Body).Decode(&results)
if err != nil {
return nil, err
var wrap apiResponse
if err := json.Unmarshal(body, &wrap); err != nil || !wrap.Success {
if wrap.Error != nil {
return nil, errors.New(wrap.Error.Message)
}
return nil, fmt.Errorf("invalid response: %w", err)
}

return results, nil
var data struct {
RemainingEntries []string `json:"remaining_entries"`
}
if err := json.Unmarshal(wrap.Data, &data); err != nil {
return nil, err
}
return data.RemainingEntries, nil
}

// Search searches a collection
Expand Down Expand Up @@ -289,17 +353,30 @@ func (c *Client) Search(collection, query string, maxResults int) ([]Result, err
}
defer resp.Body.Close()

body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}

if resp.StatusCode != http.StatusOK {
return nil, errors.New("failed to search collection")
return nil, parseAPIError(resp, body, "failed to search collection")
}

var results []Result
err = json.NewDecoder(resp.Body).Decode(&results)
if err != nil {
return nil, err
var wrap apiResponse
if err := json.Unmarshal(body, &wrap); err != nil || !wrap.Success {
if wrap.Error != nil {
return nil, errors.New(wrap.Error.Message)
}
return nil, fmt.Errorf("invalid response: %w", err)
}

return results, nil
var data struct {
Results []Result `json:"results"`
}
if err := json.Unmarshal(wrap.Data, &data); err != nil {
return nil, err
}
return data.Results, nil
}

// Reset resets a collection
Expand All @@ -320,9 +397,8 @@ func (c *Client) Reset(collection string) error {
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
b := new(bytes.Buffer)
b.ReadFrom(resp.Body)
return errors.New("failed to reset collection: " + b.String())
body, _ := io.ReadAll(resp.Body)
return parseAPIError(resp, body, "failed to reset collection")
}

return nil
Expand Down Expand Up @@ -371,20 +447,8 @@ func (c *Client) Store(collection, filePath string) error {
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
b := new(bytes.Buffer)
b.ReadFrom(resp.Body)

type response struct {
Error string `json:"error"`
}

var r response
err = json.Unmarshal(b.Bytes(), &r)
if err == nil {
return errors.New("failed to upload file: " + r.Error)
}

return errors.New("failed to upload file")
body, _ := io.ReadAll(resp.Body)
return parseAPIError(resp, body, "failed to upload file")
}

return nil
Expand Down