Merge branch 'ollama:main' into main

This commit is contained in:
likelovewant
2025-04-08 10:11:28 +08:00
committed by GitHub
8 changed files with 181 additions and 35 deletions

View File

@@ -370,7 +370,7 @@ See the [API documentation](./docs/api.md) for all endpoints.
- [PartCAD](https://github.com/openvmp/partcad/) (CAD model generation with OpenSCAD and CadQuery) - [PartCAD](https://github.com/openvmp/partcad/) (CAD model generation with OpenSCAD and CadQuery)
- [Ollama4j Web UI](https://github.com/ollama4j/ollama4j-web-ui) - Java-based Web UI for Ollama built with Vaadin, Spring Boot and Ollama4j - [Ollama4j Web UI](https://github.com/ollama4j/ollama4j-web-ui) - Java-based Web UI for Ollama built with Vaadin, Spring Boot and Ollama4j
- [PyOllaMx](https://github.com/kspviswa/pyOllaMx) - macOS application capable of chatting with both Ollama and Apple MLX models. - [PyOllaMx](https://github.com/kspviswa/pyOllaMx) - macOS application capable of chatting with both Ollama and Apple MLX models.
- [Claude Dev](https://github.com/saoudrizwan/claude-dev) - VSCode extension for multi-file/whole-repo coding - [Cline](https://github.com/cline/cline) - Formerly known as Claude Dev is a VSCode extension for multi-file/whole-repo coding
- [Cherry Studio](https://github.com/kangfenmao/cherry-studio) (Desktop client with Ollama support) - [Cherry Studio](https://github.com/kangfenmao/cherry-studio) (Desktop client with Ollama support)
- [ConfiChat](https://github.com/1runeberg/confichat) (Lightweight, standalone, multi-platform, and privacy focused LLM chat interface with optional encryption) - [ConfiChat](https://github.com/1runeberg/confichat) (Lightweight, standalone, multi-platform, and privacy focused LLM chat interface with optional encryption)
- [Archyve](https://github.com/nickthecook/archyve) (RAG-enabling document library) - [Archyve](https://github.com/nickthecook/archyve) (RAG-enabling document library)
@@ -462,6 +462,7 @@ See the [API documentation](./docs/api.md) for all endpoints.
- [DeepShell](https://github.com/Abyss-c0re/deepshell) Your self-hosted AI assistant. Interactive Shell, Files and Folders analysis. - [DeepShell](https://github.com/Abyss-c0re/deepshell) Your self-hosted AI assistant. Interactive Shell, Files and Folders analysis.
- [orbiton](https://github.com/xyproto/orbiton) Configuration-free text editor and IDE with support for tab completion with Ollama. - [orbiton](https://github.com/xyproto/orbiton) Configuration-free text editor and IDE with support for tab completion with Ollama.
- [orca-cli](https://github.com/molbal/orca-cli) Ollama Registry CLI Application - Browse, pull and download models from Ollama Registry in your terminal. - [orca-cli](https://github.com/molbal/orca-cli) Ollama Registry CLI Application - Browse, pull and download models from Ollama Registry in your terminal.
- [GGUF-to-Ollama](https://github.com/jonathanhecl/gguf-to-ollama) - Importing GGUF to Ollama made easy (multiplatform)
### Apple Vision Pro ### Apple Vision Pro

View File

@@ -166,6 +166,48 @@ type Tool struct {
Function ToolFunction `json:"function"` Function ToolFunction `json:"function"`
} }
// PropertyType can be either a string or an array of strings
type PropertyType []string
// UnmarshalJSON implements the json.Unmarshaler interface
func (pt *PropertyType) UnmarshalJSON(data []byte) error {
// Try to unmarshal as a string first
var s string
if err := json.Unmarshal(data, &s); err == nil {
*pt = []string{s}
return nil
}
// If that fails, try to unmarshal as an array of strings
var a []string
if err := json.Unmarshal(data, &a); err != nil {
return err
}
*pt = a
return nil
}
// MarshalJSON implements the json.Marshaler interface
func (pt PropertyType) MarshalJSON() ([]byte, error) {
if len(pt) == 1 {
// If there's only one type, marshal as a string
return json.Marshal(pt[0])
}
// Otherwise marshal as an array
return json.Marshal([]string(pt))
}
// String returns a string representation of the PropertyType
func (pt PropertyType) String() string {
if len(pt) == 0 {
return ""
}
if len(pt) == 1 {
return pt[0]
}
return fmt.Sprintf("%v", []string(pt))
}
type ToolFunction struct { type ToolFunction struct {
Name string `json:"name"` Name string `json:"name"`
Description string `json:"description"` Description string `json:"description"`
@@ -173,7 +215,7 @@ type ToolFunction struct {
Type string `json:"type"` Type string `json:"type"`
Required []string `json:"required"` Required []string `json:"required"`
Properties map[string]struct { Properties map[string]struct {
Type string `json:"type"` Type PropertyType `json:"type"`
Description string `json:"description"` Description string `json:"description"`
Enum []string `json:"enum,omitempty"` Enum []string `json:"enum,omitempty"`
} `json:"properties"` } `json:"properties"`

View File

@@ -231,3 +231,83 @@ func TestMessage_UnmarshalJSON(t *testing.T) {
} }
} }
} }
func TestPropertyType_UnmarshalJSON(t *testing.T) {
tests := []struct {
name string
input string
expected PropertyType
}{
{
name: "string type",
input: `"string"`,
expected: PropertyType{"string"},
},
{
name: "array of types",
input: `["string", "number"]`,
expected: PropertyType{"string", "number"},
},
{
name: "array with single type",
input: `["string"]`,
expected: PropertyType{"string"},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
var pt PropertyType
if err := json.Unmarshal([]byte(test.input), &pt); err != nil {
t.Errorf("Unexpected error: %v", err)
}
if len(pt) != len(test.expected) {
t.Errorf("Length mismatch: got %v, expected %v", len(pt), len(test.expected))
}
for i, v := range pt {
if v != test.expected[i] {
t.Errorf("Value mismatch at index %d: got %v, expected %v", i, v, test.expected[i])
}
}
})
}
}
func TestPropertyType_MarshalJSON(t *testing.T) {
tests := []struct {
name string
input PropertyType
expected string
}{
{
name: "single type",
input: PropertyType{"string"},
expected: `"string"`,
},
{
name: "multiple types",
input: PropertyType{"string", "number"},
expected: `["string","number"]`,
},
{
name: "empty type",
input: PropertyType{},
expected: `[]`,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
data, err := json.Marshal(test.input)
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
if string(data) != test.expected {
t.Errorf("Marshaled data mismatch: got %v, expected %v", string(data), test.expected)
}
})
}
}

View File

@@ -319,7 +319,14 @@ func New(ctx context.Context, r *os.File, params ml.BackendParams) (ml.Backend,
tts[i] = tt tts[i] = tt
} }
sr := io.NewSectionReader(r, int64(meta.Tensors().Offset+t.Offset), int64(t.Size())) // Create a new FD for each goroutine so that each FD is read sequentially, rather than
// seeking around within an FD shared between all goroutines.
file, err := os.Open(r.Name())
if err != nil {
return err
}
defer file.Close()
sr := io.NewSectionReader(file, int64(meta.Tensors().Offset+t.Offset), int64(t.Size()))
bts := make([]byte, 128*format.KibiByte) bts := make([]byte, 128*format.KibiByte)
var s uint64 var s uint64

View File

@@ -283,7 +283,7 @@ func TestChatMiddleware(t *testing.T) {
Type string `json:"type"` Type string `json:"type"`
Required []string `json:"required"` Required []string `json:"required"`
Properties map[string]struct { Properties map[string]struct {
Type string `json:"type"` Type api.PropertyType `json:"type"`
Description string `json:"description"` Description string `json:"description"`
Enum []string `json:"enum,omitempty"` Enum []string `json:"enum,omitempty"`
} `json:"properties"` } `json:"properties"`
@@ -291,16 +291,16 @@ func TestChatMiddleware(t *testing.T) {
Type: "object", Type: "object",
Required: []string{"location"}, Required: []string{"location"},
Properties: map[string]struct { Properties: map[string]struct {
Type string `json:"type"` Type api.PropertyType `json:"type"`
Description string `json:"description"` Description string `json:"description"`
Enum []string `json:"enum,omitempty"` Enum []string `json:"enum,omitempty"`
}{ }{
"location": { "location": {
Type: "string", Type: api.PropertyType{"string"},
Description: "The city and state", Description: "The city and state",
}, },
"unit": { "unit": {
Type: "string", Type: api.PropertyType{"string"},
Enum: []string{"celsius", "fahrenheit"}, Enum: []string{"celsius", "fahrenheit"},
}, },
}, },

View File

@@ -11,10 +11,13 @@ import (
"os" "os"
"os/user" "os/user"
"path/filepath" "path/filepath"
"runtime"
"slices" "slices"
"strconv" "strconv"
"strings" "strings"
"sync"
"golang.org/x/sync/errgroup"
"golang.org/x/text/encoding/unicode" "golang.org/x/text/encoding/unicode"
"golang.org/x/text/transform" "golang.org/x/text/transform"
@@ -144,12 +147,25 @@ func fileDigestMap(path string) (map[string]string, error) {
files = []string{path} files = []string{path}
} }
var mu sync.Mutex
var g errgroup.Group
g.SetLimit(max(runtime.GOMAXPROCS(0)-1, 1))
for _, f := range files { for _, f := range files {
g.Go(func() error {
digest, err := digestForFile(f) digest, err := digestForFile(f)
if err != nil { if err != nil {
return nil, err return err
} }
mu.Lock()
defer mu.Unlock()
fl[f] = digest fl[f] = digest
return nil
})
}
if err := g.Wait(); err != nil {
return nil, err
} }
return fl, nil return fl, nil

View File

@@ -372,7 +372,7 @@ func TestGenerateChat(t *testing.T) {
Type string `json:"type"` Type string `json:"type"`
Required []string `json:"required"` Required []string `json:"required"`
Properties map[string]struct { Properties map[string]struct {
Type string `json:"type"` Type api.PropertyType `json:"type"`
Description string `json:"description"` Description string `json:"description"`
Enum []string `json:"enum,omitempty"` Enum []string `json:"enum,omitempty"`
} `json:"properties"` } `json:"properties"`
@@ -380,16 +380,16 @@ func TestGenerateChat(t *testing.T) {
Type: "object", Type: "object",
Required: []string{"location"}, Required: []string{"location"},
Properties: map[string]struct { Properties: map[string]struct {
Type string `json:"type"` Type api.PropertyType `json:"type"`
Description string `json:"description"` Description string `json:"description"`
Enum []string `json:"enum,omitempty"` Enum []string `json:"enum,omitempty"`
}{ }{
"location": { "location": {
Type: "string", Type: api.PropertyType{"string"},
Description: "The city and state", Description: "The city and state",
}, },
"unit": { "unit": {
Type: "string", Type: api.PropertyType{"string"},
Enum: []string{"celsius", "fahrenheit"}, Enum: []string{"celsius", "fahrenheit"},
}, },
}, },
@@ -469,7 +469,7 @@ func TestGenerateChat(t *testing.T) {
Type string `json:"type"` Type string `json:"type"`
Required []string `json:"required"` Required []string `json:"required"`
Properties map[string]struct { Properties map[string]struct {
Type string `json:"type"` Type api.PropertyType `json:"type"`
Description string `json:"description"` Description string `json:"description"`
Enum []string `json:"enum,omitempty"` Enum []string `json:"enum,omitempty"`
} `json:"properties"` } `json:"properties"`
@@ -477,16 +477,16 @@ func TestGenerateChat(t *testing.T) {
Type: "object", Type: "object",
Required: []string{"location"}, Required: []string{"location"},
Properties: map[string]struct { Properties: map[string]struct {
Type string `json:"type"` Type api.PropertyType `json:"type"`
Description string `json:"description"` Description string `json:"description"`
Enum []string `json:"enum,omitempty"` Enum []string `json:"enum,omitempty"`
}{ }{
"location": { "location": {
Type: "string", Type: api.PropertyType{"string"},
Description: "The city and state", Description: "The city and state",
}, },
"unit": { "unit": {
Type: "string", Type: api.PropertyType{"string"},
Enum: []string{"celsius", "fahrenheit"}, Enum: []string{"celsius", "fahrenheit"},
}, },
}, },