Skip to content

Commit 2288e7f

Browse files
authored
feat: update localrecall and use postgresql as default (#394)
Signed-off-by: Ettore Di Giacinto <[email protected]>
1 parent c703495 commit 2288e7f

File tree

5 files changed

+152
-47
lines changed

5 files changed

+152
-47
lines changed

docker-compose.amd.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@ services:
1616
file: docker-compose.yaml
1717
service: dind
1818

19+
localrecall-postgres:
20+
extends:
21+
file: docker-compose.yaml
22+
service: localrecall-postgres
23+
1924
localrecall:
2025
extends:
2126
file: docker-compose.yaml

docker-compose.intel.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@ services:
1717
file: docker-compose.yaml
1818
service: dind
1919

20+
localrecall-postgres:
21+
extends:
22+
file: docker-compose.yaml
23+
service: localrecall-postgres
24+
2025
localrecall:
2126
extends:
2227
file: docker-compose.yaml

docker-compose.nvidia.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@ services:
2222
file: docker-compose.yaml
2323
service: dind
2424

25+
localrecall-postgres:
26+
extends:
27+
file: docker-compose.yaml
28+
service: localrecall-postgres
29+
2530
localrecall:
2631
extends:
2732
file: docker-compose.yaml

docker-compose.yaml

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,18 +26,41 @@ services:
2626
- ./volumes/backends:/backends
2727
- ./volumes/images:/tmp/generated/images
2828

29+
localrecall-postgres:
30+
image: quay.io/mudler/localrecall:${LOCALRECALL_VERSION:-v0.5.2}-postgresql
31+
environment:
32+
- POSTGRES_DB=localrecall
33+
- POSTGRES_USER=localrecall
34+
- POSTGRES_PASSWORD=localrecall
35+
ports:
36+
- 5432:5432
37+
volumes:
38+
- postgres_data:/var/lib/postgresql/data
39+
healthcheck:
40+
test: ["CMD-SHELL", "pg_isready -U localrecall"]
41+
interval: 10s
42+
timeout: 5s
43+
retries: 5
44+
2945
localrecall:
30-
image: quay.io/mudler/localrecall:main
46+
image: quay.io/mudler/localrecall:${LOCALRECALL_VERSION:-v0.5.2}
47+
depends_on:
48+
localrecall-postgres:
49+
condition: service_healthy
50+
localai:
51+
condition: service_started
3152
ports:
3253
- 8080
3354
environment:
34-
- COLLECTION_DB_PATH=/db
55+
- DATABASE_URL=postgresql://localrecall:localrecall@localrecall-postgres:5432/localrecall?sslmode=disable
56+
- VECTOR_ENGINE=postgres
3557
- EMBEDDING_MODEL=granite-embedding-107m-multilingual
3658
- FILE_ASSETS=/assets
3759
- OPENAI_API_KEY=sk-1234567890
3860
- OPENAI_BASE_URL=http://localai:8080
61+
- HYBRID_SEARCH_BM25_WEIGHT=0.5
62+
- HYBRID_SEARCH_VECTOR_WEIGHT=0.5
3963
volumes:
40-
- ./volumes/localrag/db:/db
4164
- ./volumes/localrag/assets/:/assets
4265

4366
localrecall-healthcheck:
@@ -102,3 +125,6 @@ services:
102125
- "host.docker.internal:host-gateway"
103126
volumes:
104127
- ./volumes/localagi/:/pool
128+
129+
volumes:
130+
postgres_data:

pkg/localrag/client.go

Lines changed: 108 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,32 @@ func (c *WrappedClient) Store(s string) error {
9090
return c.Client.Store(c.collection, f)
9191
}
9292

93+
// apiResponse is the standardized LocalRecall API response wrapper (since 3f73ff3a).
94+
type apiResponse struct {
95+
Success bool `json:"success"`
96+
Message string `json:"message,omitempty"`
97+
Data json.RawMessage `json:"data,omitempty"`
98+
Error *apiError `json:"error,omitempty"`
99+
}
100+
101+
type apiError struct {
102+
Code string `json:"code"`
103+
Message string `json:"message"`
104+
Details string `json:"details,omitempty"`
105+
}
106+
107+
// parseAPIError reads the response body and returns an error from the API response or a generic message.
108+
func parseAPIError(resp *http.Response, body []byte, fallback string) error {
109+
var wrap apiResponse
110+
if err := json.Unmarshal(body, &wrap); err == nil && wrap.Error != nil {
111+
if wrap.Error.Details != "" {
112+
return fmt.Errorf("%s: %s", wrap.Error.Message, wrap.Error.Details)
113+
}
114+
return errors.New(wrap.Error.Message)
115+
}
116+
return fmt.Errorf("%s: %s", fallback, string(body))
117+
}
118+
93119
// Result represents a single result from a query.
94120
type Result struct {
95121
ID string
@@ -153,7 +179,8 @@ func (c *Client) CreateCollection(name string) error {
153179
defer resp.Body.Close()
154180

155181
if resp.StatusCode != http.StatusCreated {
156-
return errors.New("failed to create collection")
182+
body, _ := io.ReadAll(resp.Body)
183+
return parseAPIError(resp, body, "failed to create collection")
157184
}
158185

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

206+
body, err := io.ReadAll(resp.Body)
207+
if err != nil {
208+
return nil, err
209+
}
210+
179211
if resp.StatusCode != http.StatusOK {
180-
return nil, errors.New("failed to list collections")
212+
return nil, parseAPIError(resp, body, "failed to list collections")
181213
}
182214

183-
var collections []string
184-
err = json.NewDecoder(resp.Body).Decode(&collections)
185-
if err != nil {
186-
return nil, err
215+
var wrap apiResponse
216+
if err := json.Unmarshal(body, &wrap); err != nil || !wrap.Success {
217+
if wrap.Error != nil {
218+
return nil, errors.New(wrap.Error.Message)
219+
}
220+
return nil, fmt.Errorf("invalid response: %w", err)
187221
}
188222

189-
return collections, nil
223+
var data struct {
224+
Collections []string `json:"collections"`
225+
}
226+
if err := json.Unmarshal(wrap.Data, &data); err != nil {
227+
return nil, err
228+
}
229+
return data.Collections, nil
190230
}
191231

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

249+
body, err := io.ReadAll(resp.Body)
250+
if err != nil {
251+
return nil, err
252+
}
253+
209254
if resp.StatusCode != http.StatusOK {
210-
return nil, errors.New("failed to list entries")
255+
return nil, parseAPIError(resp, body, "failed to list entries")
211256
}
212257

213-
var entries []string
214-
err = json.NewDecoder(resp.Body).Decode(&entries)
215-
if err != nil {
216-
return nil, err
258+
var wrap apiResponse
259+
if err := json.Unmarshal(body, &wrap); err != nil || !wrap.Success {
260+
if wrap.Error != nil {
261+
return nil, errors.New(wrap.Error.Message)
262+
}
263+
return nil, fmt.Errorf("invalid response: %w", err)
217264
}
218265

219-
return entries, nil
266+
var data struct {
267+
Entries []string `json:"entries"`
268+
}
269+
if err := json.Unmarshal(wrap.Data, &data); err != nil {
270+
return nil, err
271+
}
272+
return data.Entries, nil
220273
}
221274

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

302+
body, err := io.ReadAll(resp.Body)
303+
if err != nil {
304+
return nil, err
305+
}
306+
249307
if resp.StatusCode != http.StatusOK {
250-
bodyResult := new(bytes.Buffer)
251-
bodyResult.ReadFrom(resp.Body)
252-
return nil, errors.New("failed to delete entry: " + bodyResult.String())
308+
return nil, parseAPIError(resp, body, "failed to delete entry")
253309
}
254310

255-
var results []string
256-
err = json.NewDecoder(resp.Body).Decode(&results)
257-
if err != nil {
258-
return nil, err
311+
var wrap apiResponse
312+
if err := json.Unmarshal(body, &wrap); err != nil || !wrap.Success {
313+
if wrap.Error != nil {
314+
return nil, errors.New(wrap.Error.Message)
315+
}
316+
return nil, fmt.Errorf("invalid response: %w", err)
259317
}
260318

261-
return results, nil
319+
var data struct {
320+
RemainingEntries []string `json:"remaining_entries"`
321+
}
322+
if err := json.Unmarshal(wrap.Data, &data); err != nil {
323+
return nil, err
324+
}
325+
return data.RemainingEntries, nil
262326
}
263327

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

356+
body, err := io.ReadAll(resp.Body)
357+
if err != nil {
358+
return nil, err
359+
}
360+
292361
if resp.StatusCode != http.StatusOK {
293-
return nil, errors.New("failed to search collection")
362+
return nil, parseAPIError(resp, body, "failed to search collection")
294363
}
295364

296-
var results []Result
297-
err = json.NewDecoder(resp.Body).Decode(&results)
298-
if err != nil {
299-
return nil, err
365+
var wrap apiResponse
366+
if err := json.Unmarshal(body, &wrap); err != nil || !wrap.Success {
367+
if wrap.Error != nil {
368+
return nil, errors.New(wrap.Error.Message)
369+
}
370+
return nil, fmt.Errorf("invalid response: %w", err)
300371
}
301372

302-
return results, nil
373+
var data struct {
374+
Results []Result `json:"results"`
375+
}
376+
if err := json.Unmarshal(wrap.Data, &data); err != nil {
377+
return nil, err
378+
}
379+
return data.Results, nil
303380
}
304381

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

322399
if resp.StatusCode != http.StatusOK {
323-
b := new(bytes.Buffer)
324-
b.ReadFrom(resp.Body)
325-
return errors.New("failed to reset collection: " + b.String())
400+
body, _ := io.ReadAll(resp.Body)
401+
return parseAPIError(resp, body, "failed to reset collection")
326402
}
327403

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

373449
if resp.StatusCode != http.StatusOK {
374-
b := new(bytes.Buffer)
375-
b.ReadFrom(resp.Body)
376-
377-
type response struct {
378-
Error string `json:"error"`
379-
}
380-
381-
var r response
382-
err = json.Unmarshal(b.Bytes(), &r)
383-
if err == nil {
384-
return errors.New("failed to upload file: " + r.Error)
385-
}
386-
387-
return errors.New("failed to upload file")
450+
body, _ := io.ReadAll(resp.Body)
451+
return parseAPIError(resp, body, "failed to upload file")
388452
}
389453

390454
return nil

0 commit comments

Comments
 (0)