- harness/es.py: append-only event store + проекции (образец — кооп-портал) - harness/levels.py: уровни 1с/1м/1ч/1д, контекст ∝ масштабу - harness/scheduler.py: уровневые потоки, идемпотентные тики, absorbed-стабы - harness/isolated_runner.py + _llm_worker.py: LLM-тик в сабпроцессе (глобальный asyncio-рантайм Strands не переживает 2+ create_harness) - cli.py: init/task/tasks/status/events + requeue (написан самим минутным агентом) - 9router: отдельный ключ sk-simbiot, модель auto (glm-5.3 первым)
144 lines
5.4 KiB
Python
144 lines
5.4 KiB
Python
"""Strict JSON schema transformation for tool definitions.
|
|
|
|
When model providers require `strict: true` on tool definitions, they also require
|
|
`"additionalProperties": false` on every `object` type in the input schema. This module
|
|
provides a utility to recursively apply that constraint.
|
|
|
|
Modeled after OpenAI's `_ensure_strict_json_schema`:
|
|
https://github.com/openai/openai-python/blob/main/src/openai/lib/_pydantic.py
|
|
"""
|
|
|
|
import copy
|
|
import logging
|
|
from typing import Any
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
def ensure_strict_json_schema(
|
|
schema: dict[str, Any],
|
|
*,
|
|
require_all_properties: bool = False,
|
|
) -> dict[str, Any]:
|
|
"""Ensure a JSON schema conforms to strict tool use requirements.
|
|
|
|
Creates a deep copy of the schema and recursively:
|
|
1. Adds ``"additionalProperties": false`` to all ``object`` types that do not already define it
|
|
2. Optionally adds all properties to the ``required`` array (needed for OpenAI)
|
|
3. Handles ``$defs``, ``definitions``, ``anyOf``, ``allOf``, ``items``, and ``$ref``
|
|
|
|
Args:
|
|
schema: The JSON schema to process. A deep copy is made internally so the original is not mutated.
|
|
require_all_properties: If True, set ``required`` to include all property keys. OpenAI strict mode
|
|
requires this; Bedrock and Anthropic do not.
|
|
|
|
Returns:
|
|
A new schema dict with strict-mode constraints applied.
|
|
"""
|
|
schema_copy = copy.deepcopy(schema)
|
|
_apply_strict(schema_copy, root=schema_copy, require_all_properties=require_all_properties)
|
|
return schema_copy
|
|
|
|
|
|
def _apply_strict(
|
|
schema: dict[str, Any],
|
|
*,
|
|
root: dict[str, Any],
|
|
require_all_properties: bool,
|
|
) -> None:
|
|
"""Recursively apply strict-mode constraints to a JSON schema in place.
|
|
|
|
Args:
|
|
schema: The schema node to process (modified in place).
|
|
root: The root schema, used for resolving ``$ref`` pointers.
|
|
require_all_properties: If True, add all properties to ``required``.
|
|
"""
|
|
# Process $defs / definitions blocks
|
|
for defs_key in ("$defs", "definitions"):
|
|
defs = schema.get(defs_key)
|
|
if isinstance(defs, dict):
|
|
for def_schema in defs.values():
|
|
if isinstance(def_schema, dict):
|
|
_apply_strict(def_schema, root=root, require_all_properties=require_all_properties)
|
|
|
|
# Add additionalProperties: false to object types that lack it
|
|
if schema.get("type") == "object" and "additionalProperties" not in schema:
|
|
schema["additionalProperties"] = False
|
|
|
|
# Process properties and optionally enforce required
|
|
properties = schema.get("properties")
|
|
if isinstance(properties, dict):
|
|
if require_all_properties:
|
|
schema["required"] = list(properties.keys())
|
|
|
|
for prop_schema in properties.values():
|
|
if isinstance(prop_schema, dict):
|
|
_apply_strict(prop_schema, root=root, require_all_properties=require_all_properties)
|
|
|
|
# Process array items
|
|
items = schema.get("items")
|
|
if isinstance(items, dict):
|
|
_apply_strict(items, root=root, require_all_properties=require_all_properties)
|
|
|
|
# Process anyOf variants
|
|
any_of = schema.get("anyOf")
|
|
if isinstance(any_of, list):
|
|
for variant in any_of:
|
|
if isinstance(variant, dict):
|
|
_apply_strict(variant, root=root, require_all_properties=require_all_properties)
|
|
|
|
# Process allOf variants
|
|
all_of = schema.get("allOf")
|
|
if isinstance(all_of, list):
|
|
for entry in all_of:
|
|
if isinstance(entry, dict):
|
|
_apply_strict(entry, root=root, require_all_properties=require_all_properties)
|
|
|
|
# Process oneOf variants
|
|
one_of = schema.get("oneOf")
|
|
if isinstance(one_of, list):
|
|
for variant in one_of:
|
|
if isinstance(variant, dict):
|
|
_apply_strict(variant, root=root, require_all_properties=require_all_properties)
|
|
|
|
# Resolve $ref combined with other keys by inlining the referenced schema
|
|
ref = schema.get("$ref")
|
|
if isinstance(ref, str) and len(schema) > 1:
|
|
resolved = _resolve_ref(root, ref)
|
|
if isinstance(resolved, dict):
|
|
# Inline the resolved schema, giving priority to existing keys
|
|
merged = {**copy.deepcopy(resolved), **schema}
|
|
merged.pop("$ref", None)
|
|
schema.clear()
|
|
schema.update(merged)
|
|
# Re-apply strict to the inlined schema
|
|
_apply_strict(schema, root=root, require_all_properties=require_all_properties)
|
|
|
|
|
|
def _resolve_ref(root: dict[str, Any], ref: str) -> dict[str, Any] | None:
|
|
"""Resolve a JSON Schema ``$ref`` pointer against the root schema.
|
|
|
|
Args:
|
|
root: The root schema containing definitions.
|
|
ref: A JSON pointer string (e.g., ``#/$defs/MyModel``).
|
|
|
|
Returns:
|
|
The resolved schema dict, or None if resolution fails.
|
|
"""
|
|
if not ref.startswith("#/"):
|
|
logger.warning("ref=<%s> | unexpected $ref format, skipping resolution", ref)
|
|
return None
|
|
|
|
path = ref[2:].split("/")
|
|
current: Any = root
|
|
for key in path:
|
|
if not isinstance(current, dict) or key not in current:
|
|
logger.warning("ref=<%s> | failed to resolve $ref path", ref)
|
|
return None
|
|
current = current[key]
|
|
|
|
if not isinstance(current, dict):
|
|
logger.warning("ref=<%s> | resolved to non-dict value", ref)
|
|
return None
|
|
|
|
return current
|