Skip to main content

Universal Object Mapping (UOM) Orchestrator: System Architecture & LangGraph Design

The Universal Object Mapping (UOM) Orchestrator serves as the central orchestration engine for executing, validating, and evaluating translations between Object-Relational Mapping (ORM) paradigms (relational .NET structures) and NoSQL Document/Graph schemas (Java MongoDB and Neo4j architectures). Rather than relying on unstructured, single-agent tool loops (which suffer from token bloat, high latencies, and regression bugs), the system is built as a highly deterministic, stateful workflow using LangGraph. This architecture implements strict structural validation gates, compiling target schemas and checking target queries against isolated compiler sandboxes before assessing equivalence or committing results.

1. System Topology and Boundaries

The UOM Orchestrator sits between the client-facing UI (UOM UI Web App) and isolated execution environments (Daytona validation sandboxes). The primary execution sequence coordinates the following system bounds:

2. Comprehensive LangGraph Workflow

The orchestrator’s execution sequence is modeled as a StateGraph which determines execution paths, compiler validation, and equivalence checks. The workflow transitions through a series of specialized nodes and conditional edges.

2.1 The State Machine Diagram

The following Mermaid diagram outlines the precise, step-by-step state machine implemented in react_agent/graph.py:

3. Node-by-Node Functional Breakdown

Each node in the state machine is a stateless python function taking the current State, a RunnableConfig, and the context Runtime as parameters, and returns state updates or a Command object to control routing.

4. Conditional Transition Routing

Conditional routing determines the state machine’s transitions. This layer is modeled using deterministic python functions returning Literal branch names:

4.1 Initial Extraction Gate (should_extract_input)

Executed after START and extract_input.
  • Route to schema_inspection: If all required structured parameters exist (source code and target frameworks matching the requested translation_type).
  • Route to extract_input: If required parameters are missing and state.extraction_loop_count < MAX_EXTRACTION_LOOPS (3).
  • Route to __end__ (END): If inputs remain un-extracted after 3 attempts, exiting with an error explanation printed to conversation history.

4.2 Generation Gate (route_post_translation)

Executed after generate_translation_node.
  • Route to prep_schema_validation: If state.translation_type == TranslationType.SCHEMA.
  • Route to prep_query_validation: If state.translation_type is either QUERY or BOTH.

4.3 Schema Validation Gate (route_post_schema_validation)

Executed after validate_schema_node.
  • Route to prep_query_validation: If schema compilation succeeded and the translation type requested is BOTH.
  • Route to __end__ (END): If schema validation succeeded and only SCHEMA translation was requested.
  • Route to generate_translation_node: If schema compilation failed due to syntax or mapping errors, and state.translation_loop_count < MAX_TRANSLATION_LOOPS (3). The compiler stderr logs are passed in translation_messages so the LLM has direct debug context.
  • Route to human_intervention_node: If schema compilation failed and the loop count has reached MAX_TRANSLATION_LOOPS (3).

4.4 Query Validation Gate (route_post_query_validation)

Executed after validate_query_node.
  • Route to prep_query_equivalence: If both source and target query validations compiled and executed successfully (returning standard [Validation Passed] markers).
  • Route to evaluation_node: If either source or target compilation or execution failed (e.g. C# compiles but Java throws a Driver Connection Timeout). Skipping equivalence checking saves resources and lets the judge node format the errors.

4.5 Evaluation Gate (route_post_evaluation)

Executed after evaluation_node.
  • Route to __end__ (END): If the judge issued an ACCEPT decision.
  • Route to generate_translation_node: If the judge issued REJECT and state.translation_loop_count < MAX_TRANSLATION_LOOPS (3).
  • Route to human_intervention_node: If the judge issued REJECT and loop count is exhausted.

5. Architectural Shifts: Deprecation of the ReAct Translation Agent

Historically, the orchestrator relied on a unified ReAct Translation Agent (translation_agent node in graph.py, currently marked as deprecated). Under that setup, the LLM was given direct access to sandboxes and database tools, tasked with autonomously compiling code, checking logs, and deciding when it was done. While highly flexible in theory, this pattern suffered from major flaws:
  1. Context Window Explosion: Every single tool call, build log, and compiler stderr trace was written directly to the main conversation thread. An agent looping 5 times to fix syntax errors would ingest over 80,000 tokens of noisy logs, causing severe context window pollution, high API costs, and degraded reasoning.
  2. Hallucinated Outcomes: ReAct agents occasionally failed to interpret a non-zero exit code or compiler failure correctly, hallucinating that a broken script had “succeeded” and returning incorrect schemas to the user.
  3. Lack of Parallelism: Standard sequential ReAct loops execute tool calls one-by-one. In contrast, the current architecture runs the source and target validations concurrently in separate sandboxes, cutting validation latency in half.

Comparison Table: ReAct Loop vs. Deterministic State Machine