fix(ontology): handle string attributes from LLM response to prevent TypeError crash
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
182a9525e7
commit
866ed421e2
|
|
@ -240,7 +240,7 @@ class GraphBuilderService:
|
||||||
attrs = {"__doc__": description}
|
attrs = {"__doc__": description}
|
||||||
annotations = {}
|
annotations = {}
|
||||||
|
|
||||||
for attr_def in entity_def.get("attributes", []):
|
for attr_def in GraphBuilderService._normalize_entity_attributes(entity_def.get("attributes", [])):
|
||||||
attr_name = safe_attr_name(attr_def["name"])
|
attr_name = safe_attr_name(attr_def["name"])
|
||||||
attr_desc = attr_def.get("description", attr_name)
|
attr_desc = attr_def.get("description", attr_name)
|
||||||
attrs[attr_name] = Field(description=attr_desc, default=None)
|
attrs[attr_name] = Field(description=attr_desc, default=None)
|
||||||
|
|
@ -260,7 +260,7 @@ class GraphBuilderService:
|
||||||
attrs = {"__doc__": description}
|
attrs = {"__doc__": description}
|
||||||
annotations = {}
|
annotations = {}
|
||||||
|
|
||||||
for attr_def in edge_def.get("attributes", []):
|
for attr_def in GraphBuilderService._normalize_entity_attributes(edge_def.get("attributes", [])):
|
||||||
attr_name = safe_attr_name(attr_def["name"])
|
attr_name = safe_attr_name(attr_def["name"])
|
||||||
attr_desc = attr_def.get("description", attr_name)
|
attr_desc = attr_def.get("description", attr_name)
|
||||||
attrs[attr_name] = Field(description=attr_desc, default=None)
|
attrs[attr_name] = Field(description=attr_desc, default=None)
|
||||||
|
|
@ -430,3 +430,14 @@ class GraphBuilderService:
|
||||||
"""Delete graph"""
|
"""Delete graph"""
|
||||||
self._graph.delete_graph(graph_id)
|
self._graph.delete_graph(graph_id)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _normalize_entity_attributes(attributes: list) -> list:
|
||||||
|
"""Ensure each attribute item is a dict; convert strings to minimal dicts."""
|
||||||
|
result = []
|
||||||
|
for attr in attributes:
|
||||||
|
if isinstance(attr, str):
|
||||||
|
result.append({"name": attr, "type": "text", "description": attr})
|
||||||
|
elif isinstance(attr, dict):
|
||||||
|
result.append(attr)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -201,6 +201,9 @@ class OntologyGenerator:
|
||||||
max_tokens=8192
|
max_tokens=8192
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Normalise string attributes before validation
|
||||||
|
result = OntologyGenerator._normalize_ontology_attributes(result)
|
||||||
|
|
||||||
# Validate and post-process
|
# Validate and post-process
|
||||||
result = self._validate_and_process(result)
|
result = self._validate_and_process(result)
|
||||||
|
|
||||||
|
|
@ -264,6 +267,29 @@ Based on the content above, design entity types and relationship types suitable
|
||||||
|
|
||||||
return message
|
return message
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _normalize_ontology_attributes(ontology: dict) -> dict:
|
||||||
|
"""Normalize string attributes in LLM-generated ontology to dicts (in-place).
|
||||||
|
|
||||||
|
Handles both ``entities``/``edges`` keys (used in tests) and
|
||||||
|
``entity_types``/``edge_types`` keys (used in production LLM output).
|
||||||
|
"""
|
||||||
|
for key in ("entities", "entity_types"):
|
||||||
|
for entity in ontology.get(key, []):
|
||||||
|
entity["attributes"] = [
|
||||||
|
attr if isinstance(attr, dict)
|
||||||
|
else {"name": attr, "type": "text", "description": attr}
|
||||||
|
for attr in entity.get("attributes", [])
|
||||||
|
]
|
||||||
|
for key in ("edges", "edge_types"):
|
||||||
|
for edge in ontology.get(key, []):
|
||||||
|
edge["attributes"] = [
|
||||||
|
attr if isinstance(attr, dict)
|
||||||
|
else {"name": attr, "type": "text", "description": attr}
|
||||||
|
for attr in edge.get("attributes", [])
|
||||||
|
]
|
||||||
|
return ontology
|
||||||
|
|
||||||
def _validate_and_process(self, result: Dict[str, Any]) -> Dict[str, Any]:
|
def _validate_and_process(self, result: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
"""Validate and post-process the result"""
|
"""Validate and post-process the result"""
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
def test_graph_builder_normalizes_string_attributes():
|
||||||
|
"""_normalize_entity_attributes converts strings to dicts without crashing."""
|
||||||
|
from app.services.graph_builder import GraphBuilderService
|
||||||
|
|
||||||
|
mixed = ["name", "age", {"name": "email", "type": "text", "description": "Email"}]
|
||||||
|
result = GraphBuilderService._normalize_entity_attributes(mixed)
|
||||||
|
|
||||||
|
assert all(isinstance(a, dict) for a in result)
|
||||||
|
assert result[0] == {"name": "name", "type": "text", "description": "name"}
|
||||||
|
assert result[1] == {"name": "age", "type": "text", "description": "age"}
|
||||||
|
assert result[2]["name"] == "email"
|
||||||
|
|
||||||
|
|
||||||
|
def test_graph_builder_normalize_empty():
|
||||||
|
"""Empty attribute list returns empty list."""
|
||||||
|
from app.services.graph_builder import GraphBuilderService
|
||||||
|
assert GraphBuilderService._normalize_entity_attributes([]) == []
|
||||||
|
|
||||||
|
|
||||||
|
def test_ontology_generator_normalizes_string_attributes():
|
||||||
|
"""_normalize_ontology_attributes converts string attrs in entities and edges."""
|
||||||
|
from app.services.ontology_generator import OntologyGenerator
|
||||||
|
|
||||||
|
raw = {
|
||||||
|
"entities": [{"name": "Person", "description": "A person", "attributes": ["name", "age"]}],
|
||||||
|
"edges": [{"name": "KNOWS", "description": "Knows", "attributes": ["since"]}],
|
||||||
|
}
|
||||||
|
result = OntologyGenerator._normalize_ontology_attributes(raw)
|
||||||
|
|
||||||
|
entity_attrs = result["entities"][0]["attributes"]
|
||||||
|
assert all(isinstance(a, dict) for a in entity_attrs)
|
||||||
|
assert entity_attrs[0] == {"name": "name", "type": "text", "description": "name"}
|
||||||
|
|
||||||
|
edge_attrs = result["edges"][0]["attributes"]
|
||||||
|
assert all(isinstance(a, dict) for a in edge_attrs)
|
||||||
|
assert edge_attrs[0] == {"name": "since", "type": "text", "description": "since"}
|
||||||
Loading…
Reference in New Issue