Files
semantica/tests/integrations/google_adk/test_kg_tools.py
T
Hitesh_GandZohaib Hassnain [109234410+ZohaibHassan16@users.noreply.github.com](mailto:109234410+ZohaibHassan16@users.noreply.github.com) 6b8122d757 This is resubmit of the pr for issue feat(integrations): add Google ADK support (#1312)
Adds Google ADK (Agent Development Kit) support to Semantica.

`semantica_kg_tools()` and `semantica_decision_tools()` expose entity/relation extraction, graph updates, and decision recording as ADK `FunctionTool`s. `SemanticaSessionService` implements ADK session storage on top of a Semantica `ContextGraph`, so session state, events, and knowledge graph data can live in the same graph instead of keeping sessions in memory.

There were also a number of dependency and CI fixes needed to get the integration working reliably. `google-adk` is pinned to a range that avoids the CI `websockets` conflict, the deprecated `pinecone-client` dependency was replaced with `pinecone`, Windows-only dependencies now have the appropriate platform markers, and `requirements-ci.txt` was regenerated to match. A `pip-audit` pass also required updates to `google-adk` and `starlette` for known CVEs.

Some unrelated `pyproject.toml` changes had slipped in during rebases, so the previous version, dependency bounds, `ingest-sap`/LangChain entries, and package-data settings were restored.

A few bugs in the initial ADK implementation were fixed during review:

* `extract_relations()` was calling `RelationExtractor.extract_entities()`, which doesn't exist on that extractor. The failure was being caught and returned in the tool's `error` field, leaving callers with an empty relation list. It now calls the correct extraction path.
* The repo's top-level `mcp/` package shadowed the third-party `mcp` package imported by `google.adk`, causing `google.adk` imports to fail from a normal repo checkout. The local package was moved to `semantica_mcp/mcp/`.

The MCP move needed a follow-up as well. `semantica/cli.py` and four existing tests were still importing from `mcp.*`, and the modules under `semantica_mcp/mcp/` still used the old absolute imports internally. `semantica_mcp` was also missing from the setuptools package include list and had no `__init__.py`, so it wouldn't have been included in an installed package. Those imports and packaging settings are fixed now.

The session service and ADK tools also had a few other problems:

* `list_sessions()` returned a plain list instead of ADK's `ListSessionsResponse`. The original import for that type doesn't work against the installed `google-adk` package, so it was silently falling back to a stub. `user_id` was also incorrectly required instead of being optional.
* Session node IDs were built by joining `app_name`, `user_id`, and `session_id` with unescaped colons, which allowed different identities to produce the same graph node ID. Each component is now encoded before joining.
* `kg_tools.py` and `decision_tools.py` each had their own lock registry and default graph instance. Sharing a graph between the two modules therefore didn't share the lock, and using both factories without an explicit graph produced two different defaults. The shared state now lives in one module used by both.
* `add_to_graph` had a `TypeError` compatibility fallback that couldn't succeed with the current `RelationExtractor` API and could hide the original extraction error. That fallback was removed.
* `append_event` persisted partial streaming events even though ADK's base session service skips them.
* `get_session()` ignored its `config` argument, so `num_recent_events` and `after_timestamp` had no effect.
* The async session-service methods performed synchronous graph scans while holding a `threading.RLock` on the event loop thread. That work now runs in worker threads with `asyncio.to_thread()` so a slow or contended graph operation doesn't block the loop.

---

Co-authored-by: Zohaib Hassnain [109234410+ZohaibHassan16@users.noreply.github.com](mailto:109234410+ZohaibHassan16@users.noreply.github.com)
2026-09-04 15:34:48 +05:00

122 lines
3.4 KiB
Python

import pytest
import sys
import importlib
from unittest.mock import patch
@pytest.fixture(autouse=True)
def require_adk(request):
"""Skip tests if ADK is missing, unless testing missing dependency behavior."""
if "missing_adk" not in request.node.name:
pytest.importorskip("google.adk")
from integrations.google_adk.kg_tools import (
ADK_AVAILABLE,
extract_entities,
extract_relations,
semantica_kg_tools,
)
def test_adk_available():
assert ADK_AVAILABLE is True
def test_extract_entities_returns_dict():
result = extract_entities(
"Google was founded by Larry Page and Sergey Brin."
)
assert isinstance(result, dict)
assert "entities" in result
assert "count" in result
assert isinstance(result["entities"], list)
def test_extract_relations_returns_dict():
result=extract_relations('Alice works at Acme Corp. Bob founded Acme Corp. ')
assert isinstance(result, dict)
assert "relations" in result
assert "count" in result
assert isinstance(result["relations"], list)
assert "error" not in result,f"Extraction failed with the error: {result.get('error')}"
assert result["count"]>0, " We Expected at least one relation to be extracted"
assert len(result["relations"]) > 0, "Relation list should not ne empty"
def test_add_to_graph_surfaces_the_real_relation_extractor_error():
"""A genuine bug in the relation extractor must surface its own message,
not a second, unrelated TypeError from a compatibility shim that can
never succeed."""
from unittest.mock import MagicMock
from integrations.google_adk.kg_tools import _add_to_graph
from semantica.context import ContextGraph
fake_extractor = MagicMock()
fake_extractor.extract_relations.side_effect = TypeError("boom: bad entities shape")
with patch(
"integrations.google_adk.kg_tools._get_relation_extractor",
return_value=fake_extractor,
):
result = _add_to_graph("Alice works at Acme Corp.", ContextGraph())
assert "error" in result
assert "boom: bad entities shape" in result["error"]
def test_semantica_kg_tools_returns_function_tools():
from semantica.context import ContextGraph
graph = ContextGraph()
tools = semantica_kg_tools(graph)
assert isinstance(tools, list)
assert len(tools) == 4
for tool in tools:
assert tool is not None
def test_semantica_kg_tools_share_graph():
from semantica.context import ContextGraph
graph = ContextGraph()
tools = semantica_kg_tools(graph)
assert len(tools) == 4
# FunctionTool should wrap our closures/functions.
names = {
getattr(tool, "name", None)
for tool in tools
}
assert "extract_entities" in names
assert "extract_relations" in names
assert "add_to_shared_graph" in names
assert "query_shared_graph" in names
def test_extract_entities_invalid_input():
result = extract_entities(None)
assert isinstance(result, dict)
assert result["entities"] == []
assert result["count"] == 0
assert "error" in result
def test_missing_adk_graceful_failure(monkeypatch):
import pytest
import integrations.google_adk.kg_tools as kg_module
# Safely mock the flag to False just for this test
monkeypatch.setattr(kg_module, "ADK_AVAILABLE", False)
assert kg_module.ADK_AVAILABLE is False
with pytest.raises(ImportError, match="Google ADK is required"):
kg_module.semantica_kg_tools(None)