api: PDF cache with revalidation; pages and grep; refuse questions on very long documents
Answer extracted PDFs from the document cache: fresh for PDFTTL, then a conditional GET (304 reuses the stored text, a 200 is extracted and stored without a second GET). Store after extraction with the response hash, ETag, fetch time and extracted pages. Route pages/grep through pdfdoc.Options with 400s for bad ranges and non-PDFs, and refuse a question when the document exceeds PDFMaxQuestionChunks with a pointer to grep and pages. Co-Authored-By: ornith-1.5-35b-a3b <ornith-1.5-35b-a3b@llama.cpp.invalid>
This commit is contained in:
+76
-3
@@ -2,11 +2,14 @@ package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"git.wntrmute.dev/kyle/sift/internal/auth"
|
||||
"git.wntrmute.dev/kyle/sift/internal/dcache"
|
||||
"git.wntrmute.dev/kyle/sift/internal/fetch"
|
||||
"git.wntrmute.dev/kyle/sift/internal/pdfdoc"
|
||||
"git.wntrmute.dev/kyle/sift/internal/pdfpool"
|
||||
@@ -80,7 +83,77 @@ func pdfError(err error) error {
|
||||
}
|
||||
|
||||
// renderPDF draws the document for an agent: the whole text when it fits the
|
||||
// output cap, otherwise the map.
|
||||
func (s *Server) renderPDF(doc *pdftext.Doc) (pdfdoc.Rendered, error) {
|
||||
return pdfdoc.Render(doc, pdfdoc.Options{OutCap: s.OutCap})
|
||||
// output cap, otherwise the map, a page range, or grep results.
|
||||
func (s *Server) renderPDF(doc *pdftext.Doc, opts pdfdoc.Options) (pdfdoc.Rendered, error) {
|
||||
return pdfdoc.Render(doc, opts)
|
||||
}
|
||||
|
||||
// pdfFromCache answers a PDF from the document cache before any fetch: a fresh
|
||||
// entry directly, a stale one after a conditional GET (a 304 reuses the stored
|
||||
// text), or a miss. A 200 from the revalidation is returned with ok=false so the
|
||||
// caller extracts it; a nil cache is always a miss.
|
||||
func (s *Server) pdfFromCache(ctx context.Context, c *auth.Caller, rawURL string) (*fetch.Resource, *pdftext.Doc, bool, error) {
|
||||
cache := s.cache()
|
||||
if cache == nil {
|
||||
return nil, nil, false, nil
|
||||
}
|
||||
key := dcache.PDFKey(rawURL, s.callerClass(c))
|
||||
e, ok := cache.GetPDF(key)
|
||||
if !ok {
|
||||
return nil, nil, false, nil
|
||||
}
|
||||
ttl := s.PDFTTL
|
||||
if ttl == 0 {
|
||||
ttl = DefaultPDFTTL
|
||||
}
|
||||
if time.Since(e.FetchedAt) < ttl {
|
||||
res, doc := s.pdfFromEntry(rawURL, e)
|
||||
return res, doc, true, nil
|
||||
}
|
||||
res, err := s.Fetch.FetchIf(ctx, rawURL, e.ETag, e.LastModified)
|
||||
if err != nil {
|
||||
return nil, nil, false, err
|
||||
}
|
||||
if res.Status == http.StatusNotModified {
|
||||
cache.TouchPDF(key, time.Now().UTC())
|
||||
res, doc := s.pdfFromEntry(rawURL, e)
|
||||
return res, doc, true, nil
|
||||
}
|
||||
return res, nil, false, nil
|
||||
}
|
||||
|
||||
// pdfFromEntry rebuilds the synthetic response and doc for a cached entry: the
|
||||
// request's URL, a 200 PDF with the stored fetch time and hash, and no body.
|
||||
func (s *Server) pdfFromEntry(rawURL string, e dcache.PDFEntry) (*fetch.Resource, *pdftext.Doc) {
|
||||
var doc pdftext.Doc
|
||||
_ = json.Unmarshal(e.Meta, &doc.Meta)
|
||||
doc.Text = e.Pages
|
||||
res := &fetch.Resource{
|
||||
URL: rawURL,
|
||||
FinalURL: rawURL,
|
||||
Status: 200,
|
||||
ContentType: "application/pdf",
|
||||
FetchedAt: e.FetchedAt,
|
||||
Hash: e.ContentHash,
|
||||
}
|
||||
return res, &doc
|
||||
}
|
||||
|
||||
// storePDF writes an extracted PDF into the document cache for its TTL: its
|
||||
// hash, the response's validators, the metadata as JSON and the per-page text.
|
||||
// A nil cache is a no-op.
|
||||
func (s *Server) storePDF(key string, res *fetch.Resource, doc *pdftext.Doc) {
|
||||
cache := s.cache()
|
||||
if cache == nil {
|
||||
return
|
||||
}
|
||||
meta, _ := json.Marshal(doc.Meta)
|
||||
cache.PutPDF(key, dcache.PDFEntry{
|
||||
ContentHash: res.SHA256(),
|
||||
ETag: res.Header.Get("ETag"),
|
||||
LastModified: res.Header.Get("Last-Modified"),
|
||||
FetchedAt: time.Now().UTC(),
|
||||
Meta: meta,
|
||||
Pages: doc.Text,
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user