mirror of
https://github.com/semantica-agi/semantica.git
synced 2026-08-29 04:26:20 +00:00
Implements the full Semantica × Agno integration stack as described in issue #249, wiring Semantica's semantic intelligence layer into Agno's agent/team primitives via five focused components. ## New components ### integrations/agno/ - `AgnoContextStore` — graph-backed MemoryDb (AgentMemory/storage) - `AgnoKnowledgeGraph` — relational AgentKnowledge with multi-hop GraphRAG - `AgnoDecisionKit` — Agno Toolkit: 6 decision-intelligence tools - `AgnoKGToolkit` — Agno Toolkit: 7 knowledge-graph tools - `AgnoSharedContext` — team-level shared ContextGraph with role scoping ### tests/integrations/agno/ - 110 tests, 0 failures - conftest.py installs comprehensive agno stubs for offline testing - Covers MemoryDb protocol, tool registration, shared memory pool, thread-safety, GraphRAG search, NER/relation extraction, and inference ### cookbook/integrations/ - agno_decision_intelligence.ipynb (finance/loan underwriting) - agno_graphrag_context.ipynb (regulatory compliance GraphRAG) - agno_multi_agent_shared_context.ipynb (multi-agent product strategy team) ### docs/integrations/agno.md - Full reference documentation with examples for all 5 components ## pyproject.toml - Added `agno = ["agno>=1.0.0"]` optional dependency - Added agno to the `all` extra ## Design notes - Zero breaking changes — fully additive - Graceful degradation when agno is not installed - Auto-creates VectorStore(backend="faiss") when none provided - _tools always populated for inspection regardless of agno install state - Works with both real agno package and offline stubs Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
22 KiB
22 KiB
In [ ]:
import sys, os, json
sys.path.insert(0, os.path.abspath("../../"))
# ── Semantica core — used directly for pipeline setup ───────────────────────
from semantica.kg import GraphBuilder
from semantica.context import ContextGraph
from semantica.semantic_extract import NERExtractor, RelationExtractor, TripletExtractor
from semantica.reasoning import Reasoner
from semantica.vector_store import VectorStore
# ── Agno integration layer ───────────────────────────────────────────────────
from integrations.agno import AgnoKnowledgeGraph, AgnoKGToolkit, AGNO_AVAILABLE
print("Semantica imports OK")
print(f"Agno installed: {AGNO_AVAILABLE}")In [ ]:
# NER — identifies organisations, regulations, dates, amounts, roles
ner = NERExtractor()
# Relation extractor — finds typed edges between entities
rel_extractor = RelationExtractor(confidence_threshold=0.60)
# Knowledge graph builder
graph_builder = GraphBuilder(merge_entities=True, temporal_support=True)
# In-memory context graph (swap to neo4j/falkordb for persistence)
context_graph = ContextGraph(advanced_analytics=True)
# Reasoner for rule inference over the graph
reasoner = Reasoner()
print("Semantica extraction pipeline assembled")In [ ]:
# Regulatory documents (representative snippets)
REGULATORY_DOCS = [
{
"title": "Basel IV — Capital Requirements",
"text": (
"Basel IV introduces a revised standardised approach for credit risk, "
"replacing internal model floors. Banks must maintain a minimum CET1 ratio "
"of 4.5% and a total capital ratio of 8%. The BCBS finalised these requirements "
"in December 2017 with a phased implementation starting January 2022. "
"National regulators including the EBA and FCA are responsible for local "
"transposition. Risk-weighted assets under Basel IV are calculated using "
"the Output Floor, capping RWA reductions at 72.5%."
),
},
{
"title": "DORA — Digital Operational Resilience Act",
"text": (
"DORA (Regulation EU 2022/2554) applies to financial entities and ICT "
"third-party service providers operating in the EU. It mandates ICT risk "
"management frameworks, incident classification, and annual operational "
"resilience testing. Supervised entities must report major ICT incidents to "
"the European Supervisory Authorities (ESAs) within 4 hours of classification. "
"Critical ICT providers are subject to direct oversight by the Joint Oversight "
"Network led by ESMA, EBA, and EIOPA. DORA became applicable on 17 January 2025."
),
},
{
"title": "AML — Anti-Money Laundering Directive VI",
"text": (
"AMLD6 strengthens the EU's anti-money laundering framework by extending "
"criminal liability to 22 predicate offences including cybercrime and "
"environmental crime. Financial institutions must apply Customer Due Diligence "
"(CDD) at onboarding and Enhanced Due Diligence (EDD) for high-risk customers. "
"Suspicious Activity Reports (SARs) are filed with the national Financial "
"Intelligence Unit (FIU). Non-compliance carries penalties up to 10% of "
"annual global turnover. AMLD6 was transposed into UK law via MLCO 2020."
),
},
]
print(f"Documents to ingest: {len(REGULATORY_DOCS)}")
for doc in REGULATORY_DOCS:
print(f" • {doc['title']}")In [ ]:
# ── Run NER directly with Semantica ─────────────────────────────────────────
all_entities = []
for doc in REGULATORY_DOCS:
entities = ner.extract_entities(doc['text']) or []
all_entities.extend(entities)
print(f"[{doc['title']}] → {len(entities)} entities")
for e in entities[:4]:
print(f" {getattr(e,'name','?'):30s} type={getattr(e,'type','?')} conf={getattr(e,'confidence',0):.2f}")
print(f"\nTotal entities extracted: {len(all_entities)}")In [ ]:
# ── Run relation extraction directly with Semantica ──────────────────────────
all_relations = []
for doc in REGULATORY_DOCS:
relations = rel_extractor.extract_relations(doc['text']) or []
all_relations.extend(relations)
print(f"[{doc['title']}] → {len(relations)} relations")
for r in relations[:3]:
src = getattr(r, 'source', '?')
rtype = getattr(r, 'type', getattr(r, 'relation', '?'))
tgt = getattr(r, 'target', '?')
conf = getattr(r, 'confidence', 0)
print(f" {src!s:20s} --[{rtype}]--> {tgt!s:20s} conf={conf:.2f}")
print(f"\nTotal relations extracted: {len(all_relations)}")In [ ]:
kg = AgnoKnowledgeGraph(
graph_builder=graph_builder,
ner_extractor=ner,
relation_extractor=rel_extractor,
context_graph=context_graph,
num_documents=5,
)
# Ingest all documents through the integration wrapper
kg.load(texts=[doc['text'] for doc in REGULATORY_DOCS])
print(f"AgnoKnowledgeGraph: {len(kg._docs)} documents indexed")In [ ]:
queries = [
"What is the minimum CET1 ratio required under Basel IV?",
"Which authorities supervise critical ICT providers under DORA?",
"What are the reporting timelines for major ICT incidents?",
"How does AMLD6 handle customer due diligence?",
]
for query in queries:
print(f"\nQ: {query}")
results = kg.search(query, num_documents=2)
print(f" Retrieved {len(results)} document(s)")
for i, doc in enumerate(results, 1):
content = getattr(doc, 'content', str(doc))
print(f" [{i}] {content[:120]}...")In [ ]:
# Get graph context for a specific entity
entity_contexts = ["BCBS", "EBA", "DORA", "Basel IV"]
for entity in entity_contexts:
ctx = kg.get_graph_context(entity)
print(f"\nGraph context for '{entity}':")
print(ctx if ctx else " (no graph nodes found — depends on NER extraction quality)")In [ ]:
toolkit = AgnoKGToolkit(
ner_extractor=ner,
relation_extractor=rel_extractor,
reasoner=reasoner,
context=context_graph, # share same graph as knowledge base
)
print(f"AgnoKGToolkit: {len(toolkit._tools)} tools")
print(" Tools:", [fn.__name__ for fn in toolkit._tools])In [ ]:
# TOOL: extract_entities
print("=" * 55)
print("TOOL: extract_entities")
print("=" * 55)
new_text = (
"The PRA published a consultation paper requiring UK banks to "
"implement DORA-equivalent resilience testing by Q3 2025, "
"with Barclays and HSBC named as systemic institutions."
)
entities_json = toolkit.extract_entities(new_text)
entities_result = json.loads(entities_json)
print(f"Found {entities_result['count']} entities:")
for e in entities_result['entities']:
print(f" {e['name']:30s} type={e['type']:15s} conf={e['confidence']:.2f}")In [ ]:
# TOOL: extract_relations
print("=" * 55)
print("TOOL: extract_relations")
print("=" * 55)
relations_json = toolkit.extract_relations(new_text)
relations_result = json.loads(relations_json)
print(f"Found {relations_result['count']} relations:")
for r in relations_result['relations']:
print(f" {r['source']:20s} --[{r['relation']}]--> {r['target']:20s}")In [ ]:
# TOOL: add_to_graph
print("=" * 55)
print("TOOL: add_to_graph")
print("=" * 55)
add_result = json.loads(toolkit.add_to_graph(
entities=json.dumps([
{"name": "PRA", "type": "REGULATOR"},
{"name": "Barclays", "type": "BANK"},
{"name": "HSBC", "type": "BANK"},
]),
relations=json.dumps([
{"source": "PRA", "relation": "SUPERVISES", "target": "Barclays"},
{"source": "PRA", "relation": "SUPERVISES", "target": "HSBC"},
{"source": "Barclays", "relation": "SUBJECT_TO", "target": "DORA"},
{"source": "HSBC", "relation": "SUBJECT_TO", "target": "DORA"},
]),
))
print(f"Added: {add_result['nodes_added']} nodes, {add_result['edges_added']} edges")In [ ]:
# TOOL: query_graph
print("=" * 55)
print("TOOL: query_graph")
print("=" * 55)
query_result = json.loads(toolkit.query_graph("PRA"))
print(f"Keyword query 'PRA' → {query_result['count']} node(s):")
for node in query_result['results']:
print(f" label={node.get('label')} type={node.get('type')}")In [ ]:
# TOOL: find_related
print("=" * 55)
print("TOOL: find_related")
print("=" * 55)
related_result = json.loads(toolkit.find_related("Barclays", hops=2))
print(f"Related to 'Barclays' (2 hops): {related_result['count']} entity/entities")
for name in related_result['related']:
print(f" → {name}")In [ ]:
# TOOL: infer_facts — Semantica's Reasoner derives new facts from graph state
print("=" * 55)
print("TOOL: infer_facts")
print("=" * 55)
# Rules: regulatory compliance inference
inference_rules = json.dumps([
"IF BANK(?x) THEN FinancialEntity(?x)",
"IF REGULATOR(?x) THEN SupervisoryAuthority(?x)",
"IF FinancialEntity(?x) THEN ComplianceSubject(?x)",
])
infer_result = json.loads(toolkit.infer_facts(rules=inference_rules))
print(f"Inferred {infer_result['count']} new fact(s):")
for fact in infer_result['inferred_facts'][:8]:
print(f" {fact}")In [ ]:
# TOOL: export_subgraph — export knowledge for downstream systems
print("=" * 55)
print("TOOL: export_subgraph (JSON-LD)")
print("=" * 55)
export_result = json.loads(toolkit.export_subgraph(entity="DORA", format="json-ld"))
print(f"Exported as format='{export_result['format']}'")
if 'data' in export_result:
preview = str(export_result['data'])[:300]
print(f"Preview: {preview}...")
elif 'nodes' in export_result:
print(f"Graph nodes exported: {len(export_result['nodes'])}")
for node in export_result['nodes'][:5]:
print(f" {node}")In [ ]:
if AGNO_AVAILABLE:
from agno.agent import Agent
from agno.models.openai import OpenAIChat
compliance_agent = Agent(
name="ComplianceAnalyst",
model=OpenAIChat(id="gpt-4o"),
knowledge=kg,
search_knowledge=True,
tools=[toolkit],
show_tool_calls=True,
description=(
"You are a regulatory compliance analyst. Use the knowledge graph "
"to answer questions about Basel IV, DORA, and AML regulations. "
"When answering, use find_related and query_graph to discover "
"connections between regulators, rules, and institutions."
),
)
compliance_agent.print_response(
"Which supervisory authorities are responsible for overseeing DORA compliance "
"for UK banks, and how does this relate to Basel IV capital requirements?"
)
else:
print("[Agno not installed — skipping live agent run]")
print()
print("Expected reasoning flow:")
print(" search_knowledge('DORA supervisory authorities UK banks')")
print(" → retrieves DORA doc with graph expansion")
print(" query_graph('PRA') → finds PRA node")
print(" find_related('PRA', hops=2) → PRA → SUPERVISES → Barclays, HSBC")
print(" find_related('Basel IV', hops=1) → capital ratio requirements")
print(" Answer: PRA supervises UK banks under DORA; Basel IV CET1 requirement is 4.5%")In [ ]:
# Use Semantica's GraphAnalyzer directly on the same ContextGraph
from semantica.kg import GraphAnalyzer, CentralityCalculator, PathFinder
try:
analyzer = GraphAnalyzer()
analysis = analyzer.analyze_graph(context_graph)
print("Graph analysis (Semantica native):")
if isinstance(analysis, dict):
for k, v in list(analysis.items())[:8]:
print(f" {k}: {v}")
else:
print(f" {analysis}")
except Exception as e:
print(f"GraphAnalyzer: {e}")In [ ]:
# Centrality — which entities are most connected / influential?
try:
centrality = CentralityCalculator()
scores = centrality.calculate_degree_centrality(context_graph)
print("Degree centrality (most connected entities):")
if isinstance(scores, dict):
top = sorted(scores.items(), key=lambda x: x[1], reverse=True)[:5]
for entity, score in top:
print(f" {entity:30s} {score:.4f}")
else:
print(f" {scores}")
except Exception as e:
print(f"CentralityCalculator: {e}")