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

@@ -51,7 +51,7 @@ see if the change were accepted.
The title should look like: The title should look like:
<package>: <short description> <package>: <short description>
The package is the most affected Go package. If the change does not affect Go The package is the most affected Go package. If the change does not affect Go
code, then use the directory name instead. Changes to a single well-known code, then use the directory name instead. Changes to a single well-known

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,9 +215,9 @@ 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"`
} `json:"parameters"` } `json:"parameters"`
} }

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,24 +283,24 @@ 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"`
}{ }{
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 {
digest, err := digestForFile(f) g.Go(func() error {
if err != nil { digest, err := digestForFile(f)
return nil, err if err != nil {
} return err
fl[f] = digest }
mu.Lock()
defer mu.Unlock()
fl[f] = digest
return nil
})
}
if err := g.Wait(); err != nil {
return nil, err
} }
return fl, nil return fl, nil

View File

@@ -372,24 +372,24 @@ 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"`
}{ }{
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,24 +469,24 @@ 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"`
}{ }{
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"},
}, },
}, },