Spaces:
Sleeping
Sleeping
File size: 9,328 Bytes
1993d5c | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 | import json
import logging
import re
from typing import Dict, Any, Optional, Tuple
from dotenv import load_dotenv
load_dotenv()
logger = logging.getLogger(__name__)
from agents.runtime import get_agent_runtime, AgentRuntime
class GeometryParserAgent:
"""
Unified Geometry Parser Agent (v7.0 - Agent Runtime & Cascading Controller):
Directly extracts semantic entities, dimensions, target question,
and generates high-precision Geometry DSL in a single, high-fidelity LLM inference step.
"""
def __init__(self, runtime: Optional[AgentRuntime] = None):
self.runtime = runtime or get_agent_runtime()
def _clean_json(self, raw: str) -> str:
s = raw.strip()
json_match = re.search(r"```(?:json)?(.*?)```", s, re.DOTALL)
if json_match:
return json_match.group(1).strip()
brace_match = re.search(r"(\{.*\})", s, re.DOTALL)
if brace_match:
return brace_match.group(1).strip()
return s.strip()
def _validate_parser_output(self, raw: str) -> Tuple[bool, Any]:
"""Validates JSON structure and extracts DSL."""
try:
cleaned = self._clean_json(raw)
data = json.loads(cleaned)
if not isinstance(data, dict):
return False, "Output must be a JSON object"
if "type" not in data and "geometry_dsl" not in data:
return False, "Missing required 'type' or 'geometry_dsl' fields"
dsl = data.get("geometry_dsl", "")
if isinstance(dsl, list):
dsl = "\n".join(dsl)
data["geometry_dsl"] = dsl.strip()
return True, data
except Exception as e:
return False, f"JSON parse error: {e}"
async def process(
self,
text: str,
feedback: Optional[str] = None,
context: Optional[Dict[str, Any]] = None,
) -> Dict[str, Any]:
logger.info(f"==[GeometryParserAgent] Parsing problem & generating DSL (len={len(text)}) (v7.0)==")
if feedback:
logger.warning(f"[GeometryParserAgent] Feedback from previous attempt: {feedback}")
if context:
logger.info(f"[GeometryParserAgent] Using previous context (dsl_len={len(context.get('geometry_dsl', ''))})")
system_prompt = """You are an expert Geometry Parser & DSL Generator (v6.0 - Hybrid Procedural & 3D Analytical).
Analyze the Vietnamese/LaTeX mathematical geometry problem and extract both the structured semantics AND the executable Geometry DSL program in a single step.
=== DSL SPECIFICATION v6.0 ===
-- 2D & 3D Basic Primitives --
POINT(A) — declare a point (supports A, B, A1, B1, A', B', S, O, M, N, H, I, G, D)
POINT(A, x, y, z) — declare a point with explicit coordinates
LENGTH(AB, 5) — distance between A and B is 5
ANGLE(A, 90) — angle at vertex A is 90° (or ANGLE(A, B, C, 60))
PARALLEL(AB, CD) — segment AB is parallel to CD
PERPENDICULAR(AB, CD) — segment AB is perpendicular to CD
MIDPOINT(M, AB) — M is the midpoint of segment AB
SECTION(E, A, C, k) — E satisfies vector AE = k * vector AC (k is decimal, e.g. 0.5)
LINE(A, B) — infinite line passing through A and B
RAY(A, B) — ray starting at A and passing through B
CIRCLE(O, 5) — circle with center O and radius 5
SEGMENT(M, N) — auxiliary segment MN to be drawn
POLYGON_ORDER(A, B, C, D) — polygon boundary vertex ordering
TRIANGLE(ABC) — triangle
SQUARE(ABCD) — square
RECTANGLE(ABCD) — rectangle
PARALLELOGRAM(ABCD) — parallelogram
RHOMBUS(ABCD) — rhombus
TRAPEZOID(ABCD) — trapezoid
-- Deterministic Construction Primitives (O(1) analytic, GeoBuildBench-inspired) --
INTERSECT_LINES(P, AB, CD) — P is intersection of lines AB and CD
INTERSECT_LINE_PLANE(P, Line, Plane) — P is intersection of line and plane (e.g. INTERSECT_LINE_PLANE(M, SO, ABCD))
INCENTER(I, A, B, C) — I is incenter of triangle ABC (tâm đường tròn nội tiếp)
CIRCUMCENTER(O, A, B, C) — O is circumcenter of triangle ABC (tâm đường tròn ngoại tiếp)
ORTHOCENTER(H, A, B, C) — H is orthocenter of triangle ABC (trực tâm)
CENTROID(G, A, B, C) — G is centroid of triangle ABC (trọng tâm)
ANGLE_BISECTOR(D, A, B, C) — D is foot of internal angle bisector of angle BAC on side BC
PROJECT_POINT_PLANE(H, P, ABC) — H is orthogonal projection of point P onto plane ABC (H in plane, PH ⊥ plane)
PROJECT_POINT_LINE(H, P, AB) — H is orthogonal projection of point P onto line AB
-- Micro-Predicates & Topological Constraints (MagicGeo-inspired) --
ANGLE_RELATION(A, B, C, D, E, F, 2.0)— angle(ABC) = 2.0 * angle(DEF)
ANGLE_SUM(A, B, C, D, E, F, 90) — angle(ABC) + angle(DEF) = 90°
INSIDE_SEGMENT(M, A, B) — point M lies strictly inside segment AB (0 < t < 1)
ON_RAY(M, A, B) — point M lies on ray AB extended past B (t > 1)
RATIO_SEGMENT(A, M, B, 2.0) — point M divides segment with length ratio MA / MB = 2.0
-- 3D Polyhedrons & Round Solids --
PYRAMID(S_ABCD) — pyramid with apex S and base ABCD (supports S_ABC, S_ABCD)
PRISM(ABC_DEF) — triangular prism
PRISM(ABCD_A1B1C1D1) — quadrilateral prism
TETRAHEDRON(ABCD) — tetrahedron
CUBE(ABCD_A1B1C1D1) — cube
CUBOID(ABCD_A1B1C1D1) — rectangular cuboid
FRUSTUM(ABCD_A1B1C1D1) — frustum of a pyramid
CONE(S_O, r, h) — cone with apex S, base center O, radius r, height h
CYLINDER(O1_O2, r) — cylinder with axis O1-O2, radius r
SPHERE(O, r) — sphere with center O and radius r
-- 3D Spatial Relations & Oxyz Analytical Geometry --
PERPENDICULAR_PLANE(SA, ABCD) — line SA ⊥ plane ABCD
ANGLE_LINE_PLANE(Line, Plane, 45) — angle between line and plane is 45°
DIHEDRAL_ANGLE(Plane1, Plane2, 60) — dihedral angle between two planes is 60°
DISTANCE_SKEW_LINES(AB, CD, 5) — distance between skew lines AB and CD is 5
DISTANCE_POINT_PLANE(P, ABC, 3) — distance from point P to plane ABC is 3
PLANE_EQ(P, A, B, C, D) — plane P: Ax + By + Cz + D = 0
SPHERE_EQ(S, center, R) — sphere S with center and radius R
TANGENT_PLANE_SPHERE(P, S) — plane P is tangent to sphere S
=== OUTPUT FORMAT ===
Output ONLY a JSON object with this EXACT structure (no markdown, no extra keys):
{
"type": "cube|cuboid|tetrahedron|cone|cylinder|frustum|pyramid|prism|sphere|rectangle|triangle|circle|parallelogram|trapezoid|square|rhombus|oxyz|general",
"entities": ["Point S", "Point A", "Point B", "Point C", "Point D", "Point O"],
"values": {"AB": 10, "SO": 15},
"target_question": "Tính thể tích khối chóp S.ABCD",
"analysis": "Tóm tắt bài toán ngắn gọn bằng tiếng Việt.",
"geometry_dsl": "PYRAMID(S_ABCD)\\nSQUARE(ABCD)\\nLENGTH(AB, 4)\\nLENGTH(SA, 5)\\nPERPENDICULAR_PLANE(SA, ABCD)"
}
=== RULES ===
1. For pyramids with explicit base and height:
PYRAMID(S_ABCD)
SQUARE(ABCD)
LENGTH(AB, 4)
PERPENDICULAR_PLANE(SA, ABCD)
2. For projection/height foot: use PROJECT_POINT_PLANE(H, S, ABCD) or PERPENDICULAR_PLANE.
3. For Oxyz problems with given coordinates: declare POINT(A, x, y, z) directly.
4. Keep DSL commands clean, uppercase, and syntactically valid.
"""
user_content = f"Đề bài toán:\n{text}"
if context:
user_content = f"PREVIOUS CONTEXT:\n{context.get('analysis', '')}\nDSL:\n{context.get('geometry_dsl', '')}\n\nNEW REQUEST:\n{text}"
if feedback:
user_content += f"\n\nPhản hồi từ lần chạy trước: {feedback}. Vui lòng sửa lại DSL và ràng buộc chính xác."
messages = [
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_content},
]
try:
data = await self.runtime.run(
agent="geometry_parser",
messages=messages,
validator=self._validate_parser_output,
)
except Exception as e:
logger.warning(f"[GeometryParserAgent] Agent runtime cascade failed: {e}. Using fallback structure.")
data = {
"type": "general",
"entities": [],
"values": {},
"target_question": text,
"analysis": text,
"geometry_dsl": "",
}
dsl = data.get("geometry_dsl", "")
if isinstance(dsl, list):
dsl = "\n".join(dsl)
data["geometry_dsl"] = dsl.strip()
logger.info(f"[GeometryParserAgent] Success: type={data.get('type')}, dsl_lines={len(data['geometry_dsl'].splitlines())}")
return data
|