mirror of
https://github.com/mudler/LocalAI.git
synced 2026-11-05 20:13:23 -05:00
feat(sandbox): declare the execution descriptor in the manifest
The Sandbox add-on states in its manifest that its bash tool runs commands, under which title, with which note for the model, and that it supplies the runtime of the document skills. The manifest accepts API 1 to 2. A spec checks that the manifest matches the descriptor in code. A guard spec fails when the studio package or the add-on tool adapter compares a string with the ID of the Sandbox again, or when the files that build the commands text name it. The contracts suite inventory lists the new specs, and two specs of the network-policy status that it was missing. Assisted-by: Claude Code:claude-sonnet-5-5 Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
This commit is contained in:
5 files changed
+109
-3
No files matched your search
@@ -19,6 +19,7 @@ import (
|
||||
|
||||
"github.com/mudler/LocalAI/addons/internal/protocol"
|
||||
"github.com/mudler/LocalAI/addons/sandbox/internal/backends"
|
||||
"github.com/mudler/LocalAI/core/services/studio/sandbox"
|
||||
"github.com/mudler/LocalAI/core/services/studio/sandbox/runner"
|
||||
"github.com/mudler/LocalAI/core/services/studio/workspace/workfiles"
|
||||
"github.com/mudler/LocalAI/pkg/addons/contract"
|
||||
@@ -115,6 +116,18 @@ func (r *runtime) Close(ctx context.Context) error {
|
||||
|
||||
var errNoTool = errors.New("this tool is not served by the sandbox add-on")
|
||||
|
||||
// Execution is what the add-on declares about its bash tool: the host names the
|
||||
// provider by this title, tells the model the note when commands are isolated,
|
||||
// and offers the document skills of this package only while the status says so.
|
||||
func Execution() *contract.ExecutionDescriptor {
|
||||
return &contract.ExecutionDescriptor{
|
||||
Class: contract.ExecutionClassCommands,
|
||||
Title: "Sandbox",
|
||||
Note: sandbox.GuestToolsNote,
|
||||
Skills: &contract.ExecutionSkills{ProvidesRuntimeFor: []string{contract.SkillKindDocuments}},
|
||||
}
|
||||
}
|
||||
|
||||
// Definitions returns what a model sees of each tool. They come from the code
|
||||
// tools themselves, with the strictness the contract asks of an arguments
|
||||
// schema.
|
||||
@@ -133,6 +146,7 @@ func Definitions() []contract.ToolDefinition {
|
||||
def := contract.ToolDefinition{Name: t.Name, Kind: contract.ToolKindAction, Description: t.Description, InputSchema: raw}
|
||||
if t.Name == ShadowedTool {
|
||||
def.Shadows = ShadowedTool
|
||||
def.Execution = Execution()
|
||||
}
|
||||
out = append(out, def)
|
||||
}
|
||||
|
||||
@@ -261,6 +261,7 @@ var _ = Describe("Sandbox tools", func() {
|
||||
// add-on's own page, with the default grant of egress.check.
|
||||
Expect(m.Capabilities.UI.Controls).To(Equal([]string{contract.ControlNetworkPolicy, contract.ControlToolProviders}))
|
||||
Expect(contract.EntitledControls(m, []string{"workspace.", "egress."})).To(Equal(m.Capabilities.UI.Controls))
|
||||
Expect(m.API.Max).To(BeNumerically(">=", contract.MinExecutionAPI), "the execution descriptor needs api 2")
|
||||
want := tools.Definitions()
|
||||
Expect(m.Capabilities.ToolDefinitions).To(HaveLen(len(want)))
|
||||
for i, d := range m.Capabilities.ToolDefinitions {
|
||||
@@ -272,6 +273,7 @@ var _ = Describe("Sandbox tools", func() {
|
||||
Expect(d.Description).To(Equal(want[i].Description), "definition %s differs from the runtime", d.Name)
|
||||
Expect(d.UIOnly).To(Equal(want[i].UIOnly), "definition %s differs from the runtime", d.Name)
|
||||
Expect(d.Shadows).To(Equal(want[i].Shadows), "definition %s differs from the runtime", d.Name)
|
||||
Expect(d.Execution).To(Equal(want[i].Execution), "definition %s differs from the runtime", d.Name)
|
||||
Expect(a).To(Equal(b), "definition %s differs from the runtime", d.Name)
|
||||
Expect(contract.ValidateToolSchema(d.InputSchema, d.Kind)).To(Succeed(), "schema of %s", d.Name)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"api": {
|
||||
"max": 1,
|
||||
"max": 2,
|
||||
"min": 1
|
||||
},
|
||||
"arch": "amd64",
|
||||
@@ -41,7 +41,17 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"shadows": "bash"
|
||||
"shadows": "bash",
|
||||
"execution": {
|
||||
"class": "commands",
|
||||
"title": "Sandbox",
|
||||
"note": "The sandbox has: python3 (pandas, openpyxl, docx, pptx, pypdf, Pillow), pip, jq, ripgrep (rg), sqlite3, zip, unzip, file, tree, poppler (pdftotext, pdftoppm), git, curl. It has no Node, ImageMagick, ffmpeg or OCR, and apt cannot install anything.",
|
||||
"skills": {
|
||||
"providesRuntimeFor": [
|
||||
"documents"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "execution-status",
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
package studio_test
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
// Core decides what an add-on may do from what the add-on declares and what it
|
||||
// reports at run time. These specs fail when a file that sets the rules for
|
||||
// command execution names one add-on again. Tests, comments, documentation and
|
||||
// the add-on's own data are the places that may.
|
||||
var _ = Describe("the command execution code of the host", func() {
|
||||
// literals returns the string literals of the non-test Go files of dir.
|
||||
literals := func(dir string, only ...string) map[string][]string {
|
||||
out := map[string][]string{}
|
||||
entries, err := os.ReadDir(dir)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
for _, e := range entries {
|
||||
name := e.Name()
|
||||
if e.IsDir() || !strings.HasSuffix(name, ".go") || strings.HasSuffix(name, "_test.go") {
|
||||
continue
|
||||
}
|
||||
if len(only) > 0 && !slices.Contains(only, name) {
|
||||
continue
|
||||
}
|
||||
path := filepath.Join(dir, name)
|
||||
file, err := parser.ParseFile(token.NewFileSet(), path, nil, 0)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
ast.Inspect(file, func(n ast.Node) bool {
|
||||
if lit, ok := n.(*ast.BasicLit); ok && lit.Kind == token.STRING {
|
||||
v, err := strconv.Unquote(lit.Value)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
out[path] = append(out[path], v)
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
It("compares no string with the ID of the add-on that runs commands", func() {
|
||||
// Other add-on IDs join this list as their special cases are removed.
|
||||
ids := []string{"sandbox"}
|
||||
for _, dir := range []string{".", "../addons/tools"} {
|
||||
for path, values := range literals(dir) {
|
||||
for _, v := range values {
|
||||
Expect(ids).NotTo(ContainElement(v), "%s compares with the ID of an add-on", path)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
It("names no add-on in the text it builds for the commands of a chat", func() {
|
||||
for path, values := range literals(".", "capabilities.go", "addonskills.go", "loadskill.go", "shadow.go") {
|
||||
for _, v := range values {
|
||||
Expect(strings.ToLower(v)).NotTo(ContainSubstring("sandbox"), "%s writes a provider name into a sentence; take it from the manifest", path)
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -383,7 +383,17 @@
|
||||
"an unknown delivery",
|
||||
"a page the UI does not declare",
|
||||
"a preview interval too short",
|
||||
"a preview interval too long"
|
||||
"a preview interval too long",
|
||||
"is valid for a tool that shadows bash when the manifest accepts API 2",
|
||||
"needs the manifest to accept API 2",
|
||||
"is refused on a tool that does not shadow bash, with another class or with bad text",
|
||||
"keeps a manifest of API 1 valid on a host that speaks API 2, and refuses one that needs API 3",
|
||||
"names the document skills of a provider that declares it supplies their runtime",
|
||||
"fails closed for a commands tool without a descriptor",
|
||||
"names nothing for a descriptor that does not supply the runtime of documents",
|
||||
"names nothing for an add-on without a commands tool",
|
||||
"reports network-policy enforcement only for an available isolated provider",
|
||||
"refuses a non-boolean enforcement claim"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user