참고소스 수정본
This commit is contained in:
138
참고/instructor-main/instructor/dsl/json_tracker.py
Normal file
138
참고/instructor-main/instructor/dsl/json_tracker.py
Normal file
@@ -0,0 +1,138 @@
|
||||
"""
|
||||
JSON Completeness Tracker for Partial Streaming.
|
||||
|
||||
Tracks which parts of accumulated JSON are "closed" (complete) vs "open" (incomplete).
|
||||
Uses jiter for parsing and a simple heuristic: if a value has a next sibling,
|
||||
it must be complete (because jiter had to finish parsing it to find the next one).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from jiter import from_json
|
||||
|
||||
|
||||
def is_json_complete(json_str: str) -> bool:
|
||||
"""
|
||||
Check if a JSON string represents a complete structure.
|
||||
|
||||
Uses jiter in strict mode - parsing fails if JSON is incomplete.
|
||||
"""
|
||||
if not json_str or not json_str.strip():
|
||||
return False
|
||||
try:
|
||||
from_json(json_str.encode()) # No partial_mode = strict parsing
|
||||
return True
|
||||
except ValueError:
|
||||
return False
|
||||
|
||||
|
||||
class JsonCompleteness:
|
||||
"""
|
||||
Track completeness of JSON structures during streaming.
|
||||
|
||||
Uses a simple heuristic: if a value has a next sibling in the parsed
|
||||
structure, it must be complete. For the last sibling, we don't know
|
||||
until the parent completes - but that's fine because parent validation
|
||||
will cover it.
|
||||
|
||||
Example:
|
||||
tracker = JsonCompleteness()
|
||||
|
||||
# Incomplete - missing closing brace
|
||||
tracker.analyze('{"name": "Alice", "address": {"city": "NY')
|
||||
tracker.is_path_complete("") # False - root incomplete
|
||||
tracker.is_path_complete("name") # True - has next sibling "address"
|
||||
tracker.is_path_complete("address") # False - last sibling, unknown
|
||||
|
||||
# Complete
|
||||
tracker.analyze('{"name": "Alice"}')
|
||||
tracker.is_path_complete("") # True - root complete
|
||||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self._complete_paths: set[str] = set()
|
||||
|
||||
def analyze(self, json_str: str) -> None:
|
||||
"""Analyze a JSON string and determine completeness of each path."""
|
||||
self._complete_paths = set()
|
||||
|
||||
if not json_str or not json_str.strip():
|
||||
return
|
||||
|
||||
# Try strict parsing first - if it succeeds, JSON is complete
|
||||
try:
|
||||
parsed = from_json(json_str.encode())
|
||||
self._mark_all(parsed, "")
|
||||
return
|
||||
except ValueError:
|
||||
pass # JSON is incomplete, continue with partial parsing
|
||||
|
||||
# Root incomplete - use sibling heuristic
|
||||
try:
|
||||
parsed = from_json(json_str.encode(), partial_mode="trailing-strings")
|
||||
except ValueError:
|
||||
return
|
||||
|
||||
self._check_siblings(parsed, "")
|
||||
|
||||
def _mark_all(self, data: Any, path: str) -> None:
|
||||
"""Recursively mark path and all children as complete."""
|
||||
self._complete_paths.add(path)
|
||||
if isinstance(data, dict):
|
||||
for key, value in data.items():
|
||||
child_path = f"{path}.{key}" if path else key
|
||||
self._mark_all(value, child_path)
|
||||
elif isinstance(data, list):
|
||||
for i, item in enumerate(data):
|
||||
self._mark_all(item, f"{path}[{i}]")
|
||||
|
||||
def _check_siblings(self, data: Any, path: str) -> None:
|
||||
"""
|
||||
Check completeness using sibling heuristic.
|
||||
|
||||
If a value has a next sibling, it's complete (jiter had to finish
|
||||
parsing it to find the next sibling). Last sibling is unknown.
|
||||
"""
|
||||
if isinstance(data, dict):
|
||||
keys = list(data.keys())
|
||||
for i, key in enumerate(keys):
|
||||
child_path = f"{path}.{key}" if path else key
|
||||
if i < len(keys) - 1:
|
||||
# Has next sibling → complete
|
||||
self._mark_all(data[key], child_path)
|
||||
else:
|
||||
# Last sibling → recurse to check children
|
||||
self._check_siblings(data[key], child_path)
|
||||
|
||||
elif isinstance(data, list):
|
||||
for i, item in enumerate(data):
|
||||
child_path = f"{path}[{i}]"
|
||||
if i < len(data) - 1:
|
||||
# Has next sibling → complete
|
||||
self._mark_all(item, child_path)
|
||||
else:
|
||||
# Last sibling → recurse
|
||||
self._check_siblings(item, child_path)
|
||||
|
||||
def is_path_complete(self, path: str) -> bool:
|
||||
"""
|
||||
Check if the sub-structure at the given path is complete.
|
||||
|
||||
Args:
|
||||
path: Dot-separated path (e.g., "user.address.city", "items[0]")
|
||||
Use "" for root object.
|
||||
|
||||
Returns:
|
||||
True if the structure at path is complete (closed), False otherwise.
|
||||
"""
|
||||
return path in self._complete_paths
|
||||
|
||||
def get_complete_paths(self) -> set[str]:
|
||||
"""Return all paths that are complete."""
|
||||
return self._complete_paths.copy()
|
||||
|
||||
def is_root_complete(self) -> bool:
|
||||
"""Check if the root JSON structure is complete."""
|
||||
return "" in self._complete_paths
|
||||
Reference in New Issue
Block a user