mirror of
https://github.com/patriceckhart/zot.git
synced 2026-06-27 05:46:34 +02:00
sanitize gemini tool schemas
This commit is contained in:
parent
37ef90bbb3
commit
583b2a7db2
2 changed files with 77 additions and 4 deletions
|
|
@ -157,10 +157,7 @@ func (c *geminiClient) buildRequest(req Request) (*gemRequest, string, error) {
|
|||
if functionsEnabled && len(req.Tools) > 0 {
|
||||
decls := make([]gemFunctionDecl, 0, len(req.Tools))
|
||||
for _, t := range req.Tools {
|
||||
schema := t.Schema
|
||||
if len(schema) == 0 || !json.Valid(schema) {
|
||||
schema = json.RawMessage(`{"type":"object","properties":{}}`)
|
||||
}
|
||||
schema := sanitizeGeminiToolSchema(t.Schema)
|
||||
decls = append(decls, gemFunctionDecl{
|
||||
Name: t.Name,
|
||||
Description: t.Description,
|
||||
|
|
@ -226,6 +223,46 @@ func (c *geminiClient) buildRequest(req Request) (*gemRequest, string, error) {
|
|||
return out, m.ID, nil
|
||||
}
|
||||
|
||||
func sanitizeGeminiToolSchema(schema json.RawMessage) json.RawMessage {
|
||||
if len(schema) == 0 || !json.Valid(schema) {
|
||||
return json.RawMessage(`{"type":"object","properties":{}}`)
|
||||
}
|
||||
var v any
|
||||
if err := json.Unmarshal(schema, &v); err != nil {
|
||||
return json.RawMessage(`{"type":"object","properties":{}}`)
|
||||
}
|
||||
v = stripGeminiUnsupportedSchemaFields(v)
|
||||
out, err := json.Marshal(v)
|
||||
if err != nil || len(out) == 0 || !json.Valid(out) {
|
||||
return json.RawMessage(`{"type":"object","properties":{}}`)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func stripGeminiUnsupportedSchemaFields(v any) any {
|
||||
switch x := v.(type) {
|
||||
case map[string]any:
|
||||
out := make(map[string]any, len(x))
|
||||
for k, val := range x {
|
||||
switch k {
|
||||
case "additionalProperties", "$schema":
|
||||
continue
|
||||
default:
|
||||
out[k] = stripGeminiUnsupportedSchemaFields(val)
|
||||
}
|
||||
}
|
||||
return out
|
||||
case []any:
|
||||
out := make([]any, len(x))
|
||||
for i, val := range x {
|
||||
out[i] = stripGeminiUnsupportedSchemaFields(val)
|
||||
}
|
||||
return out
|
||||
default:
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
func convertGemUserParts(blocks []Content) []gemPart {
|
||||
var parts []gemPart
|
||||
for _, b := range blocks {
|
||||
|
|
|
|||
|
|
@ -206,6 +206,42 @@ func TestGeminiBuildRequestSystemAndTools(t *testing.T) {
|
|||
|
||||
// TestGeminiBuildRequestImageModelOmitsTools confirms image-generation
|
||||
// models receive direct multimodal prompts without function declarations.
|
||||
func TestGeminiBuildRequestStripsUnsupportedSchemaFields(t *testing.T) {
|
||||
c := NewGemini("k", "https://example.invalid").(*geminiClient)
|
||||
wire, _, err := c.buildRequest(Request{
|
||||
Model: "gemini-2.5-pro",
|
||||
Tools: []Tool{{
|
||||
Name: "edit",
|
||||
Description: "edit a file",
|
||||
Schema: json.RawMessage(`{
|
||||
"$schema":"http://json-schema.org/draft-07/schema#",
|
||||
"type":"object",
|
||||
"additionalProperties":false,
|
||||
"properties":{
|
||||
"edits":{
|
||||
"type":"array",
|
||||
"items":{
|
||||
"type":"object",
|
||||
"additionalProperties":false,
|
||||
"properties":{"oldText":{"type":"string"},"newText":{"type":"string"}}
|
||||
}
|
||||
}
|
||||
}
|
||||
}`),
|
||||
}},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got := string(wire.Tools[0].FunctionDeclarations[0].Parameters)
|
||||
if strings.Contains(got, "additionalProperties") || strings.Contains(got, "$schema") {
|
||||
t.Fatalf("Gemini schema should strip unsupported fields, got %s", got)
|
||||
}
|
||||
if !strings.Contains(got, `"oldText"`) || !strings.Contains(got, `"newText"`) {
|
||||
t.Fatalf("Gemini schema lost nested properties, got %s", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGeminiBuildRequestImageModelOmitsTools(t *testing.T) {
|
||||
c := NewGemini("k", "https://example.invalid").(*geminiClient)
|
||||
wire, _, err := c.buildRequest(Request{
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue