Module: OllamaAgent::LLM::PlannerSchema

Defined in:
lib/ollama_agent/llm/planner_schema.rb

Overview

Validates a parsed JSON Hash against a small JSON-schema-shaped allowlist.

Constant Summary collapse

VALID_JSON_TYPES =
%w[string array object number boolean].freeze

Class Method Summary collapse

Class Method Details

.check_no_extra_keys(obj, allowed) ⇒ Object



44
45
46
47
48
49
# File 'lib/ollama_agent/llm/planner_schema.rb', line 44

def check_no_extra_keys(obj, allowed)
  obj.each_key do |key|
    return "extra key #{key}" unless allowed.include?(key.to_s)
  end
  nil
end

.check_property_types(obj, props) ⇒ Object



51
52
53
54
55
56
57
58
59
# File 'lib/ollama_agent/llm/planner_schema.rb', line 51

def check_property_types(obj, props)
  props.each do |key, spec|
    next unless obj.key?(key)

    ok, err = match_property_type(obj[key], spec)
    return err unless ok
  end
  nil
end

.check_required(obj, schema) ⇒ Object



36
37
38
39
40
41
42
# File 'lib/ollama_agent/llm/planner_schema.rb', line 36

def check_required(obj, schema)
  required_keys = Array(schema["required"] || schema[:required]).map(&:to_s)
  required_keys.each do |key|
    return "missing required key #{key}" unless obj.key?(key)
  end
  nil
end

.match_property_type(value, spec) ⇒ Object



61
62
63
64
65
66
67
68
69
# File 'lib/ollama_agent/llm/planner_schema.rb', line 61

def match_property_type(value, spec)
  want = (spec["type"] || spec[:type]).to_s
  return [false, "unknown or missing type for #{want}"] unless VALID_JSON_TYPES.include?(want)

  ok = matches_json_type?(value, want)
  return [true, nil] if ok

  [false, "type mismatch for value #{value.inspect} (expected #{want})"]
end

.matches_json_type?(value, want) ⇒ Boolean

Returns:

  • (Boolean)


71
72
73
74
75
76
77
78
79
80
# File 'lib/ollama_agent/llm/planner_schema.rb', line 71

def matches_json_type?(value, want)
  case want
  when "string" then value.is_a?(String)
  when "array" then value.is_a?(Array)
  when "object" then value.is_a?(Hash)
  when "boolean" then [true, false].include?(value)
  when "number" then value.is_a?(Integer) || value.is_a?(Float)
  else false
  end
end

.stringify_keys_shallow(hash) ⇒ Object



82
83
84
85
86
# File 'lib/ollama_agent/llm/planner_schema.rb', line 82

def stringify_keys_shallow(hash)
  return {} unless hash.is_a?(Hash)

  hash.transform_keys(&:to_s)
end

.validate(obj, schema) ⇒ Object



10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# File 'lib/ollama_agent/llm/planner_schema.rb', line 10

def validate(obj, schema)
  err = validate_root_object(obj, schema)
  return [false, err] if err

  props = stringify_keys_shallow(schema["properties"] || schema[:properties] || {})
  err = check_required(obj, schema)
  return [false, err] if err

  err = check_no_extra_keys(obj, props.keys)
  return [false, err] if err

  err = check_property_types(obj, props)
  return [false, err] if err

  [true, nil]
end

.validate_root_object(obj, schema) ⇒ Object



27
28
29
30
31
32
33
34
# File 'lib/ollama_agent/llm/planner_schema.rb', line 27

def validate_root_object(obj, schema)
  return "root must be a JSON object" unless obj.is_a?(Hash)

  root_type = schema["type"] || schema[:type]
  return "schema root type must be 'object'" unless root_type.to_s == "object"

  nil
end