oweb: move Error and Do to client/ollama
This allows users of the ollama client library to need only import the client/ollama package, rather than the oweb package as well when inspecting errors.
This commit is contained in:
parent
c49947dcf5
commit
112ffed189
@ -1,14 +1,18 @@
|
|||||||
package ollama
|
package ollama
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"cmp"
|
"cmp"
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"iter"
|
"iter"
|
||||||
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"bllamo.com/client/ollama/apitype"
|
"bllamo.com/client/ollama/apitype"
|
||||||
"bllamo.com/oweb"
|
|
||||||
"bllamo.com/types/empty"
|
"bllamo.com/types/empty"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -41,7 +45,7 @@ func (c *Client) Build(ctx context.Context, ref string, modelfile []byte, source
|
|||||||
|
|
||||||
// Push requests the remote Ollama service to push a model to the server.
|
// Push requests the remote Ollama service to push a model to the server.
|
||||||
func (c *Client) Push(ctx context.Context, ref string) error {
|
func (c *Client) Push(ctx context.Context, ref string) error {
|
||||||
_, err := oweb.Do[empty.Message](ctx, "POST", c.BaseURL+"/v1/push", apitype.PushRequest{Name: ref})
|
_, err := Do[empty.Message](ctx, "POST", c.BaseURL+"/v1/push", apitype.PushRequest{Name: ref})
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -68,3 +72,73 @@ func (c *Client) Copy(ctx context.Context, dstRef, srcRef string) error {
|
|||||||
func (c *Client) Run(ctx context.Context, ref string, messages []apitype.Message) error {
|
func (c *Client) Run(ctx context.Context, ref string, messages []apitype.Message) error {
|
||||||
panic("not implemented")
|
panic("not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Error struct {
|
||||||
|
Status int `json:"-"`
|
||||||
|
Code string `json:"code"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
Field string `json:"field,omitempty"`
|
||||||
|
RawBody []byte `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Error) Error() string {
|
||||||
|
var b strings.Builder
|
||||||
|
b.WriteString("ollama: ")
|
||||||
|
b.WriteString(e.Code)
|
||||||
|
if e.Message != "" {
|
||||||
|
b.WriteString(": ")
|
||||||
|
b.WriteString(e.Message)
|
||||||
|
}
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func Do[Res any](ctx context.Context, method, urlStr string, in any) (*Res, error) {
|
||||||
|
var body bytes.Buffer
|
||||||
|
// TODO(bmizerany): pool and reuse this buffer AND the encoder
|
||||||
|
if err := encodeJSON(&body, in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
req, err := http.NewRequestWithContext(ctx, method, urlStr, &body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := http.DefaultClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer res.Body.Close()
|
||||||
|
|
||||||
|
if res.StatusCode/100 != 2 {
|
||||||
|
var b bytes.Buffer
|
||||||
|
body := io.TeeReader(res.Body, &b)
|
||||||
|
e, err := decodeJSON[Error](body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
e.RawBody = b.Bytes()
|
||||||
|
return nil, e
|
||||||
|
}
|
||||||
|
|
||||||
|
return decodeJSON[Res](res.Body)
|
||||||
|
}
|
||||||
|
|
||||||
|
// decodeJSON decodes JSON from r into a new value of type T.
|
||||||
|
//
|
||||||
|
// NOTE: This is (and encodeJSON) are copies and paste from oweb.go, please
|
||||||
|
// do not try and consolidate so we can keep ollama/client free from
|
||||||
|
// dependencies which are moving targets and not pulling enough weight to
|
||||||
|
// justify their inclusion.
|
||||||
|
func decodeJSON[T any](r io.Reader) (*T, error) {
|
||||||
|
var v T
|
||||||
|
if err := json.NewDecoder(r).Decode(&v); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &v, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: see NOT above decodeJSON
|
||||||
|
func encodeJSON(w io.Writer, v any) error {
|
||||||
|
// TODO(bmizerany): pool and reuse encoder
|
||||||
|
return json.NewEncoder(w).Encode(v)
|
||||||
|
}
|
||||||
|
81
oweb/oweb.go
81
oweb/oweb.go
@ -1,28 +1,19 @@
|
|||||||
package oweb
|
package oweb
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"cmp"
|
"cmp"
|
||||||
"context"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
|
||||||
|
"bllamo.com/client/ollama"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Error struct {
|
|
||||||
Status int `json:"-"`
|
|
||||||
Code string `json:"code"`
|
|
||||||
Message string `json:"message"`
|
|
||||||
Field string `json:"field,omitempty"`
|
|
||||||
RawBody []byte `json:"-"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func Missing(field string) error {
|
func Missing(field string) error {
|
||||||
return &Error{
|
return &ollama.Error{
|
||||||
Status: 400,
|
Status: 400,
|
||||||
Code: "missing",
|
Code: "missing",
|
||||||
Field: field,
|
Field: field,
|
||||||
@ -31,7 +22,7 @@ func Missing(field string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func Mistake(code, field, message string) error {
|
func Mistake(code, field, message string) error {
|
||||||
return &Error{
|
return &ollama.Error{
|
||||||
Status: 400,
|
Status: 400,
|
||||||
Code: code,
|
Code: code,
|
||||||
Field: field,
|
Field: field,
|
||||||
@ -40,29 +31,18 @@ func Mistake(code, field, message string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func Fault(code, message string) error {
|
func Fault(code, message string) error {
|
||||||
return &Error{
|
return &ollama.Error{
|
||||||
Status: 500,
|
Status: 500,
|
||||||
Code: "fault",
|
Code: "fault",
|
||||||
Message: message,
|
Message: message,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Error) Error() string {
|
|
||||||
var b strings.Builder
|
|
||||||
b.WriteString("ollama: ")
|
|
||||||
b.WriteString(e.Code)
|
|
||||||
if e.Message != "" {
|
|
||||||
b.WriteString(": ")
|
|
||||||
b.WriteString(e.Message)
|
|
||||||
}
|
|
||||||
return b.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convinience errors
|
// Convinience errors
|
||||||
var (
|
var (
|
||||||
ErrNotFound = &Error{Status: 404, Code: "not_found"}
|
ErrNotFound = &ollama.Error{Status: 404, Code: "not_found"}
|
||||||
ErrInternal = &Error{Status: 500, Code: "internal_error"}
|
ErrInternal = &ollama.Error{Status: 500, Code: "internal_error"}
|
||||||
ErrMethodNotAllowed = &Error{Status: 405, Code: "method_not_allowed"}
|
ErrMethodNotAllowed = &ollama.Error{Status: 405, Code: "method_not_allowed"}
|
||||||
)
|
)
|
||||||
|
|
||||||
type HandlerFunc func(w http.ResponseWriter, r *http.Request) error
|
type HandlerFunc func(w http.ResponseWriter, r *http.Request) error
|
||||||
@ -71,12 +51,12 @@ func Serve(h HandlerFunc, w http.ResponseWriter, r *http.Request) {
|
|||||||
if err := h(w, r); err != nil {
|
if err := h(w, r); err != nil {
|
||||||
// TODO: take a slog.Logger
|
// TODO: take a slog.Logger
|
||||||
log.Printf("error: %v", err)
|
log.Printf("error: %v", err)
|
||||||
var e *Error
|
var oe *ollama.Error
|
||||||
if !errors.As(err, &e) {
|
if !errors.As(err, &oe) {
|
||||||
e = ErrInternal
|
oe = ErrInternal
|
||||||
}
|
}
|
||||||
w.WriteHeader(cmp.Or(e.Status, 400))
|
w.WriteHeader(cmp.Or(oe.Status, 400))
|
||||||
if err := EncodeJSON(w, e); err != nil {
|
if err := EncodeJSON(w, oe); err != nil {
|
||||||
log.Printf("error encoding error: %v", err)
|
log.Printf("error encoding error: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -86,11 +66,11 @@ func DecodeUserJSON[T any](r io.Reader) (*T, error) {
|
|||||||
v, err := DecodeJSON[T](r)
|
v, err := DecodeJSON[T](r)
|
||||||
var e *json.SyntaxError
|
var e *json.SyntaxError
|
||||||
if errors.As(err, &e) {
|
if errors.As(err, &e) {
|
||||||
return nil, &Error{Code: "invalid_json", Message: e.Error()}
|
return nil, &ollama.Error{Code: "invalid_json", Message: e.Error()}
|
||||||
}
|
}
|
||||||
var se *json.UnmarshalTypeError
|
var se *json.UnmarshalTypeError
|
||||||
if errors.As(err, &se) {
|
if errors.As(err, &se) {
|
||||||
return nil, &Error{
|
return nil, &ollama.Error{
|
||||||
Code: "invalid_json",
|
Code: "invalid_json",
|
||||||
Message: fmt.Sprintf("%s (%q) is not a %s", se.Field, se.Value, se.Type),
|
Message: fmt.Sprintf("%s (%q) is not a %s", se.Field, se.Value, se.Type),
|
||||||
}
|
}
|
||||||
@ -110,34 +90,3 @@ func DecodeJSON[T any](r io.Reader) (*T, error) {
|
|||||||
func EncodeJSON(w io.Writer, v any) error {
|
func EncodeJSON(w io.Writer, v any) error {
|
||||||
return json.NewEncoder(w).Encode(v)
|
return json.NewEncoder(w).Encode(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Do[Res any](ctx context.Context, method, urlStr string, in any) (*Res, error) {
|
|
||||||
var body bytes.Buffer
|
|
||||||
if err := EncodeJSON(&body, in); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
req, err := http.NewRequestWithContext(ctx, method, urlStr, &body)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
res, err := http.DefaultClient.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer res.Body.Close()
|
|
||||||
|
|
||||||
if res.StatusCode/100 != 2 {
|
|
||||||
var b bytes.Buffer
|
|
||||||
body := io.TeeReader(res.Body, &b)
|
|
||||||
e, err := DecodeJSON[Error](body)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
e.RawBody = b.Bytes()
|
|
||||||
return nil, e
|
|
||||||
}
|
|
||||||
|
|
||||||
return DecodeJSON[Res](res.Body)
|
|
||||||
}
|
|
||||||
|
@ -6,7 +6,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"bllamo.com/oweb"
|
"bllamo.com/client/ollama"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Client struct {
|
type Client struct {
|
||||||
@ -16,7 +16,7 @@ type Client struct {
|
|||||||
// Push pushes a manifest to the server.
|
// Push pushes a manifest to the server.
|
||||||
func (c *Client) Push(ctx context.Context, ref string, manifest []byte) ([]Requirement, error) {
|
func (c *Client) Push(ctx context.Context, ref string, manifest []byte) ([]Requirement, error) {
|
||||||
// TODO(bmizerany): backoff
|
// TODO(bmizerany): backoff
|
||||||
v, err := oweb.Do[PushResponse](ctx, "POST", c.BaseURL+"/v1/push/"+ref, struct {
|
v, err := ollama.Do[PushResponse](ctx, "POST", c.BaseURL+"/v1/push/"+ref, struct {
|
||||||
Manifest json.RawMessage `json:"manifest"`
|
Manifest json.RawMessage `json:"manifest"`
|
||||||
}{manifest})
|
}{manifest})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -38,7 +38,7 @@ func PushLayer(ctx context.Context, dstURL string, size int64, file io.Reader) e
|
|||||||
}
|
}
|
||||||
defer res.Body.Close()
|
defer res.Body.Close()
|
||||||
if res.StatusCode != 200 {
|
if res.StatusCode != 200 {
|
||||||
e := &oweb.Error{Status: res.StatusCode}
|
e := &ollama.Error{Status: res.StatusCode}
|
||||||
msg, err := io.ReadAll(res.Body)
|
msg, err := io.ReadAll(res.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -10,6 +10,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"bllamo.com/client/ollama"
|
||||||
"bllamo.com/oweb"
|
"bllamo.com/oweb"
|
||||||
"github.com/minio/minio-go/v7"
|
"github.com/minio/minio-go/v7"
|
||||||
"github.com/minio/minio-go/v7/pkg/credentials"
|
"github.com/minio/minio-go/v7/pkg/credentials"
|
||||||
@ -19,8 +20,8 @@ type Server struct{}
|
|||||||
|
|
||||||
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
if err := s.serveHTTP(w, r); err != nil {
|
if err := s.serveHTTP(w, r); err != nil {
|
||||||
log.Printf("error: %v", err)
|
log.Printf("error: %v", err) // TODO(bmizerany): take a slog.Logger
|
||||||
var e *oweb.Error
|
var e *ollama.Error
|
||||||
if !errors.As(err, &e) {
|
if !errors.As(err, &e) {
|
||||||
e = oweb.ErrInternal
|
e = oweb.ErrInternal
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user