feat: add EoG (Explanations over Graphs) agent pattern#993
feat: add EoG (Explanations over Graphs) agent pattern#993UIPath-Harshit wants to merge 4 commits into
Conversation
Introduces a new agent type alongside ReAct for ontology-grounded graph investigation. EoG uses a deterministic controller to traverse an ontology graph, invoking bounded LLM inference at each node, propagating beliefs along edges, and converging on a minimal explanatory frontier. - agent/eog/types.py: EoGState, Belief, LedgerEntry, InvestigationConfig - agent/eog/ontology_client.py: async REST client for ontology-runtime - agent/eog/nodes.py: 7 graph nodes (bootstrap, pop, fetch, policy, update, propagate, frontier) - agent/eog/agent.py: create_eog_agent() graph builder - tests/agent/eog/: 44 tests covering types, client, nodes, and integration Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adds a new EoG (Explanations over Graphs) agent pattern to the UiPath LangChain integration, including a LangGraph-based controller, an ontology-runtime REST client, and supporting state/types plus test coverage.
Changes:
- Introduces EoG state/types (beliefs, ledger, edges, config) and LangGraph state reducers.
- Adds an async
OntologyClientfor ontology discovery, function invocation, and SPARQL. - Implements EoG node functions and a
create_eog_agentgraph builder, with a new test suite validating behavior.
Reviewed changes
Copilot reviewed 9 out of 10 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
src/uipath_langchain/agent/eog/types.py |
Defines EoG state models and reducer-annotated fields for LangGraph merging. |
src/uipath_langchain/agent/eog/ontology_client.py |
Adds async REST client to talk to ontology-runtime endpoints. |
src/uipath_langchain/agent/eog/nodes.py |
Implements bootstrap/pop/context/policy/update/propagate/frontier nodes for the EoG traversal. |
src/uipath_langchain/agent/eog/agent.py |
Builds the LangGraph StateGraph wiring EoG nodes together. |
src/uipath_langchain/agent/eog/__init__.py |
Exposes the EoG public API surface (create_eog_agent, types, client). |
tests/agent/eog/test_types.py |
Unit tests for new EoG types and reducer behavior. |
tests/agent/eog/test_ontology_client.py |
Unit tests for OntologyClient request construction and error handling. |
tests/agent/eog/test_nodes.py |
Unit tests for node logic (bootstrap, policy parsing, propagation, frontier). |
tests/agent/eog/test_agent.py |
Integration-style tests that compile/run the EoG graph with mocks. |
tests/agent/eog/__init__.py |
Adds package init for the new EoG test folder. |
| f"{self._api_base}/ontology/{ontology}" | ||
| f"/functions/{fn_name}/invoke" | ||
| ) | ||
| body: dict[str, Any] = {"params": params} if params else {} |
| for fn in functions: | ||
| fn_name = fn.get("name", "") | ||
| fn_desc = fn.get("description", "") | ||
| if entity_id.lower() in fn_desc.lower() or not fn_desc: | ||
| try: | ||
| result = await client.invoke_function( | ||
| ontology, fn_name, {"entity_id": entity_id} | ||
| ) |
| def _make_mock_client(self: OntologyClient) -> httpx.AsyncClient: | ||
| return httpx.AsyncClient(transport=httpx.MockTransport(handler)) | ||
|
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0da98e7c7b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| for prop in propagations: | ||
| target = prop.get("entity", "") |
There was a problem hiding this comment.
Validate propagation entries before iterating
When the model returns valid JSON but propagations is null, an object, a string, or a list containing non-object items, this loop will either fail iteration or call .get on a non-dict and abort the graph after the policy node. Since the LLM output is only json.loads-checked, malformed-but-valid JSON can crash investigations instead of falling back to no propagations; validate/normalize this field before iterating.
Useful? React with 👍 / 👎.
| if ( | ||
| target_flips < max_flips | ||
| and target not in state.active_set | ||
| and target not in new_active | ||
| ): | ||
| new_active.append(target) |
There was a problem hiding this comment.
Restrict propagation targets to ontology nodes
Every LLM-supplied target that passes the flip checks is appended to active_set, without verifying that it exists in the discovered ontology or is connected to current_entity. In a graph-grounded investigation, a hallucinated target will become an entity visit and fetch_context will invoke ontology functions for that arbitrary ID, polluting the frontier and spending budget on off-graph nodes; constrain activations to known/adjacent ontology nodes before enqueueing them.
Useful? React with 👍 / 👎.
The EoG agent was missing the dependency graph that the paper's algorithm
requires. Without it, propagation was LLM-controlled (defeating the
deterministic controller premise), context contract couldn't find neighbors,
and frontier was a filter instead of a minimality computation.
This commit adds:
graph_topology.py — OWL Functional Notation parser + OntologyGraph
- Parses classes → nodes, object properties → typed directed edges
- Adjacency indexes: neighbors(), outgoing(), incoming(), edges_of()
- Function-to-entity mapping via param names + SPARQL statement scanning
- Entity ID → type resolution via prefix conventions
- Serialization round-trip for LangGraph state storage
ontology_client.py — fetch_graph() method
- Downloads schema.ofn artifact + function list
- Parses OWL into OntologyGraph with adjacency built
nodes.py — fixed all nodes to use graph topology:
- bootstrap: calls fetch_graph() instead of discover()
- fetch_context: uses graph edges for 1-hop neighbors, binds function
params correctly using entity type + ID conventions
- policy: enforces label vocabulary, strips markdown fences
- update: applies damping (force Defer when flip_count > max_flips)
- propagate: follows GRAPH EDGES not LLM suggestions. Only propagates
when belief actually changed. Messages include relationship label.
- frontier: computes irreducibility (Source→DerivedEffect edges remove
the DerivedEffect from the frontier)
60 tests passing (was 44, added 16 for graph topology).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
OFN parsing alone couldn't bind function params like exceptionId to ToleranceException — the naming convention (toleranceExceptionId) didn't match. Now fetch_graph() also downloads mapping.yarrrml.yml and parses subject templates to extract the authoritative key property per entity (e.g., ToleranceException → exceptionId, PurchaseOrder → poId). Verified E2E: coded agent now produces 5 frontier findings (was 0 without YARRRML keys). All 7 entity types get correct function dispatch. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Documents all known limitations: regex-based OFN parser (POC only), YARRRML key binding dependency, hardcoded ID prefix conventions, namespace constraint, Ontop transitive property gap, and the path to production (server-side graph endpoint). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|



Summary
Test plan
uv run pytest tests/agent/eog/ -v— 44 tests passuv run ruff check src/uipath_langchain/agent/eog/— lint clean🤖 Generated with Claude Code