diff --git a/README.md b/README.md index eba18656..1bcbfca8 100644 --- a/README.md +++ b/README.md @@ -377,6 +377,7 @@ See the [API documentation](./docs/api.md) for all endpoints. - [Nosia](https://github.com/nosia-ai/nosia) (Easy to install and use RAG platform based on Ollama) - [Witsy](https://github.com/nbonamy/witsy) (An AI Desktop application avaiable for Mac/Windows/Linux) - [Abbey](https://github.com/US-Artificial-Intelligence/abbey) (A configurable AI interface server with notebooks, document storage, and YouTube support) +- [Minima](https://github.com/dmayboroda/minima) (RAG with on-premises or fully local workflow) ### Cloud diff --git a/api/types.go b/api/types.go index e5291a02..d2108f88 100644 --- a/api/types.go +++ b/api/types.go @@ -146,6 +146,7 @@ type ToolCall struct { } type ToolCallFunction struct { + Index int `json:"index,omitempty"` Name string `json:"name"` Arguments ToolCallFunctionArguments `json:"arguments"` } diff --git a/cmd/cmd_test.go b/cmd/cmd_test.go index 2e6428cf..18488048 100644 --- a/cmd/cmd_test.go +++ b/cmd/cmd_test.go @@ -8,7 +8,6 @@ import ( "net/http" "net/http/httptest" "os" - "path/filepath" "strings" "testing" @@ -180,18 +179,14 @@ Weigh anchor! t.Run("license", func(t *testing.T) { var b bytes.Buffer - license, err := os.ReadFile(filepath.Join("..", "LICENSE")) - if err != nil { - t.Fatal(err) - } - + license := "MIT License\nCopyright (c) Ollama\n" if err := showInfo(&api.ShowResponse{ Details: api.ModelDetails{ Family: "test", ParameterSize: "7B", QuantizationLevel: "FP16", }, - License: string(license), + License: license, }, &b); err != nil { t.Fatal(err) } diff --git a/docs/api.md b/docs/api.md index 9fd87590..5353261a 100644 --- a/docs/api.md +++ b/docs/api.md @@ -49,10 +49,10 @@ Advanced parameters (optional): - `options`: additional model parameters listed in the documentation for the [Modelfile](./modelfile.md#valid-parameters-and-values) such as `temperature` - `system`: system message to (overrides what is defined in the `Modelfile`) - `template`: the prompt template to use (overrides what is defined in the `Modelfile`) -- `context`: the context parameter returned from a previous request to `/generate`, this can be used to keep a short conversational memory - `stream`: if `false` the response will be returned as a single response object, rather than a stream of objects - `raw`: if `true` no formatting will be applied to the prompt. You may choose to use the `raw` parameter if you are specifying a full templated prompt in your request to the API - `keep_alive`: controls how long the model will stay loaded into memory following the request (default: `5m`) +- `context` (deprecated): the context parameter returned from a previous request to `/generate`, this can be used to keep a short conversational memory #### JSON mode diff --git a/docs/modelfile.md b/docs/modelfile.md index c73f960a..4f12232c 100644 --- a/docs/modelfile.md +++ b/docs/modelfile.md @@ -63,7 +63,7 @@ SYSTEM You are Mario from super mario bros, acting as an assistant. To use this: 1. Save it as a file (e.g. `Modelfile`) -2. `ollama create choose-a-model-name -f '` +2. `ollama create choose-a-model-name -f ` 3. `ollama run choose-a-model-name` 4. Start using the model! diff --git a/openai/openai.go b/openai/openai.go index 6b469da7..bf1879f9 100644 --- a/openai/openai.go +++ b/openai/openai.go @@ -140,6 +140,7 @@ type CompletionChunk struct { type ToolCall struct { ID string `json:"id"` + Index int `json:"index"` Type string `json:"type"` Function struct { Name string `json:"name"` @@ -206,6 +207,7 @@ func toToolCalls(tc []api.ToolCall) []ToolCall { toolCalls[i].ID = toolCallId() toolCalls[i].Type = "function" toolCalls[i].Function.Name = tc.Function.Name + toolCalls[i].Index = tc.Function.Index args, err := json.Marshal(tc.Function.Arguments) if err != nil { diff --git a/openai/openai_test.go b/openai/openai_test.go index eabf5b66..e17037de 100644 --- a/openai/openai_test.go +++ b/openai/openai_test.go @@ -195,7 +195,86 @@ func TestChatMiddleware(t *testing.T) { Stream: &False, }, }, - + { + name: "chat handler with streaming tools", + body: `{ + "model": "test-model", + "messages": [ + {"role": "user", "content": "What's the weather like in Paris?"} + ], + "stream": true, + "tools": [{ + "type": "function", + "function": { + "name": "get_weather", + "description": "Get the current weather", + "parameters": { + "type": "object", + "required": ["location"], + "properties": { + "location": { + "type": "string", + "description": "The city and state" + }, + "unit": { + "type": "string", + "enum": ["celsius", "fahrenheit"] + } + } + } + } + }] + }`, + req: api.ChatRequest{ + Model: "test-model", + Messages: []api.Message{ + { + Role: "user", + Content: "What's the weather like in Paris?", + }, + }, + Tools: []api.Tool{ + { + Type: "function", + Function: api.ToolFunction{ + Name: "get_weather", + Description: "Get the current weather", + Parameters: struct { + Type string `json:"type"` + Required []string `json:"required"` + Properties map[string]struct { + Type string `json:"type"` + Description string `json:"description"` + Enum []string `json:"enum,omitempty"` + } `json:"properties"` + }{ + Type: "object", + Required: []string{"location"}, + Properties: map[string]struct { + Type string `json:"type"` + Description string `json:"description"` + Enum []string `json:"enum,omitempty"` + }{ + "location": { + Type: "string", + Description: "The city and state", + }, + "unit": { + Type: "string", + Enum: []string{"celsius", "fahrenheit"}, + }, + }, + }, + }, + }, + }, + Options: map[string]any{ + "temperature": 1.0, + "top_p": 1.0, + }, + Stream: &True, + }, + }, { name: "chat handler error forwarding", body: `{ diff --git a/server/routes.go b/server/routes.go index d9e4fb66..b8980a65 100644 --- a/server/routes.go +++ b/server/routes.go @@ -251,6 +251,7 @@ func (s *Server) GenerateHandler(c *gin.Context) { var b bytes.Buffer if req.Context != nil { + slog.Warn("the context field is deprecated and will be removed in a future version of Ollama") s, err := r.Detokenize(c.Request.Context(), req.Context) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) @@ -1469,7 +1470,7 @@ func (s *Server) ChatHandler(c *gin.Context) { go func() { defer close(ch) var sb strings.Builder - var hasToolCalls bool + var toolCallIndex int = 0 if err := r.Completion(c.Request.Context(), llm.CompletionRequest{ Prompt: prompt, Images: images, @@ -1509,16 +1510,19 @@ func (s *Server) ChatHandler(c *gin.Context) { sb.WriteString(r.Content) if toolCalls, ok := m.parseToolCalls(sb.String()); ok { res.Message.ToolCalls = toolCalls + for i := range toolCalls { + toolCalls[i].Function.Index = toolCallIndex + toolCallIndex++ + } res.Message.Content = "" sb.Reset() - hasToolCalls = true ch <- res return } if r.Done { // Send any remaining content if no tool calls were detected - if !hasToolCalls { + if toolCallIndex == 0 { res.Message.Content = sb.String() } ch <- res