hyperagents 1.2.3rc5.post3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- agents/__init__.py +31 -0
- agents/background_monitor.py +453 -0
- agents/learning.py +460 -0
- agents/planner.py +421 -0
- agents/transcriber.py +341 -0
- civilization/__init__.py +51 -0
- civilization/api/__init__.py +7 -0
- civilization/api/civilization_api.py +361 -0
- civilization/graph/__init__.py +19 -0
- civilization/graph/knowledge_linker.py +348 -0
- civilization/graph/org_graph.py +348 -0
- civilization/graph/process_graph.py +386 -0
- civilization/ingestion/__init__.py +20 -0
- civilization/ingestion/chunker.py +273 -0
- civilization/ingestion/document_ingestor.py +358 -0
- civilization/ingestion/embedder.py +262 -0
- civilization/ingestion/extractor.py +224 -0
- civilization/interview/__init__.py +19 -0
- civilization/interview/gap_detector.py +380 -0
- civilization/interview/interviewer.py +323 -0
- civilization/interview/questioner.py +363 -0
- civilization/retrieval/__init__.py +17 -0
- civilization/retrieval/civilization_rag.py +316 -0
- civilization/retrieval/context_injector.py +342 -0
- civilization/retrieval/relevance_ranker.py +287 -0
- civilization/schema.py +299 -0
- civilization/store.py +177 -0
- civilization/sync/__init__.py +16 -0
- civilization/sync/confluence_sync.py +317 -0
- civilization/sync/gdrive_sync.py +262 -0
- civilization/sync/notion_sync.py +237 -0
- civilization/versioning/__init__.py +19 -0
- civilization/versioning/diff_engine.py +351 -0
- civilization/versioning/staleness_detector.py +303 -0
- civilization/versioning/version_manager.py +293 -0
- cli/__init__.py +1 -0
- cli/doctor.py +334 -0
- cli/hyperclaw.py +1357 -0
- cli/onboarding.py +469 -0
- core/__init__.py +1 -0
- core/connectors/__init__.py +119 -0
- core/connectors/infrastructure.py +772 -0
- core/connectors/messaging.py +586 -0
- core/connectors/productivity.py +764 -0
- core/connectors/smarthome.py +528 -0
- core/connectors/social.py +730 -0
- core/hyperrouter/__init__.py +23 -0
- core/hyperrouter/bandit.py +218 -0
- core/hyperrouter/fast_loop.py +68 -0
- core/hyperrouter/slow_loop.py +143 -0
- core/hyperstate/__init__.py +24 -0
- core/hyperstate/certifier.py +154 -0
- core/hyperstate/schema.py +160 -0
- core/hyperstate/state_manager.py +103 -0
- core/hyperstate/store.py +163 -0
- core/providers/__init__.py +121 -0
- core/providers/embeddings.py +389 -0
- core/providers/image.py +534 -0
- core/providers/llm.py +1935 -0
- core/providers/search.py +608 -0
- core/providers/voice.py +474 -0
- hyperagents-1.2.3rc5.post3.dist-info/METADATA +469 -0
- hyperagents-1.2.3rc5.post3.dist-info/RECORD +361 -0
- hyperagents-1.2.3rc5.post3.dist-info/WHEEL +5 -0
- hyperagents-1.2.3rc5.post3.dist-info/entry_points.txt +6 -0
- hyperagents-1.2.3rc5.post3.dist-info/licenses/LICENSE +21 -0
- hyperagents-1.2.3rc5.post3.dist-info/top_level.txt +13 -0
- hyperclaw/__init__.py +67 -0
- hyperclaw/__main__.py +220 -0
- hyperclaw/agent.py +775 -0
- hyperclaw/agent_coordinator.py +577 -0
- hyperclaw/anthropic_params.py +225 -0
- hyperclaw/api_utils.py +85 -0
- hyperclaw/approval_telegram.py +209 -0
- hyperclaw/approval_terminal.py +80 -0
- hyperclaw/attachments.py +177 -0
- hyperclaw/branding.py +55 -0
- hyperclaw/brief/__init__.py +11 -0
- hyperclaw/brief/cli.py +204 -0
- hyperclaw/brief/collect.py +146 -0
- hyperclaw/brief/compose.py +333 -0
- hyperclaw/brief/diff.py +110 -0
- hyperclaw/brief/errors.py +99 -0
- hyperclaw/brief/google.py +269 -0
- hyperclaw/brief/google_auth.py +294 -0
- hyperclaw/brief/links.py +124 -0
- hyperclaw/brief/service.py +424 -0
- hyperclaw/brief/store.py +205 -0
- hyperclaw/brief/tasks.py +200 -0
- hyperclaw/chat_scope.py +115 -0
- hyperclaw/cli_tui.py +497 -0
- hyperclaw/client_guard.py +717 -0
- hyperclaw/connection_check.py +113 -0
- hyperclaw/cost_guard.py +1728 -0
- hyperclaw/default_config/agents.yaml +534 -0
- hyperclaw/default_config/hyperclaw.yaml +27 -0
- hyperclaw/default_config/models.yaml +161 -0
- hyperclaw/deployment.py +39 -0
- hyperclaw/digest/__init__.py +9 -0
- hyperclaw/digest/cli.py +117 -0
- hyperclaw/digest/errors.py +100 -0
- hyperclaw/digest/gitlab.py +187 -0
- hyperclaw/digest/http.py +113 -0
- hyperclaw/digest/redact.py +32 -0
- hyperclaw/digest/service.py +271 -0
- hyperclaw/digest/slack.py +223 -0
- hyperclaw/digest/state.py +124 -0
- hyperclaw/digest/summarize.py +198 -0
- hyperclaw/digest/tokens.py +143 -0
- hyperclaw/distribution.py +46 -0
- hyperclaw/documents.py +351 -0
- hyperclaw/feeds.py +339 -0
- hyperclaw/google_workspace/__init__.py +5 -0
- hyperclaw/google_workspace/api.py +151 -0
- hyperclaw/google_workspace/auth.py +397 -0
- hyperclaw/google_workspace/calendar.py +157 -0
- hyperclaw/google_workspace/cli.py +72 -0
- hyperclaw/google_workspace/drive.py +236 -0
- hyperclaw/google_workspace/errors.py +58 -0
- hyperclaw/google_workspace/gmail.py +295 -0
- hyperclaw/google_workspace/meet.py +122 -0
- hyperclaw/google_workspace/tools.py +319 -0
- hyperclaw/hyperspeed.py +57 -0
- hyperclaw/integrations_layer.py +1427 -0
- hyperclaw/live_state.py +188 -0
- hyperclaw/managed_config.py +245 -0
- hyperclaw/media_hub.py +179 -0
- hyperclaw/memory_manager.py +584 -0
- hyperclaw/model_catalog.py +551 -0
- hyperclaw/model_policy.py +211 -0
- hyperclaw/model_router.py +595 -0
- hyperclaw/model_selector.py +72 -0
- hyperclaw/naming.py +284 -0
- hyperclaw/notices.py +65 -0
- hyperclaw/oai_stream.py +106 -0
- hyperclaw/orchestrator.py +591 -0
- hyperclaw/outbox.py +82 -0
- hyperclaw/persona.py +367 -0
- hyperclaw/prometheus.py +219 -0
- hyperclaw/prompt_assembly.py +157 -0
- hyperclaw/provider_setup.py +146 -0
- hyperclaw/providers.py +340 -0
- hyperclaw/qualification.py +341 -0
- hyperclaw/qualification_keys/hyper-d292fd127cef5a3e.pub +1 -0
- hyperclaw/qualification_records/anthropic--claude-sonnet-5-5.json +14 -0
- hyperclaw/reasoning_display.py +35 -0
- hyperclaw/scheduler.py +454 -0
- hyperclaw/server.py +847 -0
- hyperclaw/setup.py +627 -0
- hyperclaw/skills.py +717 -0
- hyperclaw/solomon.py +442 -0
- hyperclaw/swarm.py +533 -0
- hyperclaw/taint.py +832 -0
- hyperclaw/team/__init__.py +28 -0
- hyperclaw/team/dispatcher.py +431 -0
- hyperclaw/team/genesis.py +106 -0
- hyperclaw/team/lead.py +110 -0
- hyperclaw/team/model.py +135 -0
- hyperclaw/team/morning.py +238 -0
- hyperclaw/team/repos.py +66 -0
- hyperclaw/team/roster.py +264 -0
- hyperclaw/team/specialist.py +235 -0
- hyperclaw/team/status.py +163 -0
- hyperclaw/team/watchers.py +112 -0
- hyperclaw/teamsetup/__init__.py +17 -0
- hyperclaw/teamsetup/checks.py +208 -0
- hyperclaw/teamsetup/item.py +391 -0
- hyperclaw/teamsetup/oauth_loopback.py +84 -0
- hyperclaw/teamsetup/onboard.py +550 -0
- hyperclaw/teamsetup/profile.py +82 -0
- hyperclaw/teamsetup/secrets.py +313 -0
- hyperclaw/teamsetup/selfcheck.py +295 -0
- hyperclaw/teamsetup/service.py +495 -0
- hyperclaw/teamsetup/slack_oauth.py +442 -0
- hyperclaw/teamsetup/tty.py +62 -0
- hyperclaw/telegram_bot.py +1106 -0
- hyperclaw/telegram_intake.py +126 -0
- hyperclaw/telegram_pairing.py +360 -0
- hyperclaw/terminal_provider.py +323 -0
- hyperclaw/tools/__init__.py +1 -0
- hyperclaw/tools/approval.py +949 -0
- hyperclaw/tools/args.py +212 -0
- hyperclaw/tools/catalog.py +244 -0
- hyperclaw/tools/classify.py +266 -0
- hyperclaw/tools/effects.py +724 -0
- hyperclaw/tools/egress.py +210 -0
- hyperclaw/tools/envelope.py +188 -0
- hyperclaw/tools/guard.py +367 -0
- hyperclaw/tools/ledger.py +339 -0
- hyperclaw/tools/receipts.py +70 -0
- hyperclaw/tools/registry.py +318 -0
- hyperclaw/tools/service_guard.py +151 -0
- hyperclaw/tools/surfaces.py +54 -0
- hyperclaw/tools/workspace.py +87 -0
- hyperclaw/trading/__init__.py +77 -0
- hyperclaw/truth_guard.py +791 -0
- hyperclaw/tts.py +124 -0
- hyperclaw/tui.py +4255 -0
- hyperclaw/tui_bridge.py +938 -0
- hyperclaw/unattended.py +60 -0
- hyperclaw/updates/__init__.py +54 -0
- hyperclaw/updates/check.py +131 -0
- hyperclaw/updates/index.py +178 -0
- hyperclaw/updates/install.py +110 -0
- hyperclaw/updates/managed.py +161 -0
- hyperclaw/updates/manifest.py +138 -0
- hyperclaw/updates/notices.py +86 -0
- hyperclaw/updates/paths.py +53 -0
- hyperclaw/updates/release.py +146 -0
- hyperclaw/updates/upgrade.py +253 -0
- hyperclaw/updates/versioning.py +104 -0
- hyperclaw/userconfig.py +119 -0
- hyperclaw/xai.py +83 -0
- integrations/__init__.py +31 -0
- integrations/automation/__init__.py +1 -0
- integrations/automation/make.py +87 -0
- integrations/automation/zapier.py +87 -0
- integrations/base.py +476 -0
- integrations/communication/__init__.py +1 -0
- integrations/communication/twilio.py +279 -0
- integrations/communication/zoom.py +290 -0
- integrations/data/__init__.py +1 -0
- integrations/data/google_analytics.py +135 -0
- integrations/data/postgres.py +182 -0
- integrations/data/supabase_connector.py +202 -0
- integrations/data/weather_feeds.py +450 -0
- integrations/developer/__init__.py +9 -0
- integrations/developer/github.py +406 -0
- integrations/developer/gitlab.py +298 -0
- integrations/enterprise/__init__.py +13 -0
- integrations/enterprise/confluence.py +266 -0
- integrations/enterprise/hubspot.py +293 -0
- integrations/enterprise/jira.py +314 -0
- integrations/enterprise/salesforce.py +286 -0
- integrations/finance/__init__.py +1 -0
- integrations/finance/polymarket_client.py +247 -0
- integrations/finance/quickbooks.py +281 -0
- integrations/finance/stripe.py +261 -0
- integrations/gateway.py +238 -0
- integrations/messaging/__init__.py +32 -0
- integrations/messaging/discord.py +159 -0
- integrations/messaging/email.py +401 -0
- integrations/messaging/imessage.py +443 -0
- integrations/messaging/signal.py +175 -0
- integrations/messaging/slack.py +217 -0
- integrations/messaging/sms.py +175 -0
- integrations/messaging/teams.py +208 -0
- integrations/messaging/telegram.py +260 -0
- integrations/messaging/whatsapp.py +203 -0
- integrations/productivity/__init__.py +17 -0
- integrations/productivity/airtable.py +217 -0
- integrations/productivity/asana.py +248 -0
- integrations/productivity/google/__init__.py +19 -0
- integrations/productivity/google/base.py +89 -0
- integrations/productivity/google/calendar.py +272 -0
- integrations/productivity/google/docs.py +202 -0
- integrations/productivity/google/drive.py +268 -0
- integrations/productivity/google/gmail.py +279 -0
- integrations/productivity/google/meet.py +218 -0
- integrations/productivity/google/sheets.py +222 -0
- integrations/productivity/google/tasks.py +199 -0
- integrations/productivity/linear.py +279 -0
- integrations/productivity/microsoft/__init__.py +13 -0
- integrations/productivity/microsoft/base.py +93 -0
- integrations/productivity/microsoft/calendar.py +207 -0
- integrations/productivity/microsoft/onedrive.py +222 -0
- integrations/productivity/microsoft/outlook.py +241 -0
- integrations/productivity/microsoft/sharepoint.py +211 -0
- integrations/productivity/notion.py +243 -0
- integrations/productivity/todoist.py +250 -0
- integrations/productivity/trello.py +244 -0
- integrations/storage/__init__.py +1 -0
- integrations/storage/box.py +279 -0
- integrations/storage/dropbox.py +215 -0
- integrations/telegram_inbound.py +179 -0
- integrations/telegram_poller.py +151 -0
- memory/__init__.py +1 -0
- memory/causal_graph.py +125 -0
- memory/impact_tracker.py +88 -0
- memory/vector_memory.py +436 -0
- models/__init__.py +12 -0
- models/chatjimmy_client.py +210 -0
- models/claude_client.py +235 -0
- models/claude_code_subagent.py +192 -0
- models/openai_client.py +120 -0
- models/router.py +162 -0
- recursive/discovery_loop.py +118 -0
- recursive/optimization_loop.py +135 -0
- recursive/validation_loop.py +38 -0
- security/__init__.py +1 -0
- security/audit_logger.py +106 -0
- security/filesystem_guard.py +100 -0
- security/hypershield.py +99 -0
- security/network_guard.py +97 -0
- security/policy_engine.py +198 -0
- swarm/__init__.py +1 -0
- swarm/agents/__init__.py +0 -0
- swarm/agents/base.py +230 -0
- swarm/agents/business/__init__.py +0 -0
- swarm/agents/business/counsel.py +31 -0
- swarm/agents/business/herald.py +32 -0
- swarm/agents/business/ledger.py +26 -0
- swarm/agents/business/nexus.py +30 -0
- swarm/agents/business/ops.py +30 -0
- swarm/agents/business/pipeline.py +28 -0
- swarm/agents/business/revenue.py +30 -0
- swarm/agents/business/sovereign.py +31 -0
- swarm/agents/business/strategos.py +27 -0
- swarm/agents/business/talent.py +27 -0
- swarm/agents/business/venture.py +32 -0
- swarm/agents/comms/__init__.py +1 -0
- swarm/agents/comms/cipher.py +32 -0
- swarm/agents/comms/echo_comms.py +31 -0
- swarm/agents/comms/envoy.py +31 -0
- swarm/agents/comms/herald_scheduler.py +33 -0
- swarm/agents/comms/pulse.py +29 -0
- swarm/agents/creative/__init__.py +0 -0
- swarm/agents/creative/author.py +27 -0
- swarm/agents/creative/lens.py +25 -0
- swarm/agents/executive/__init__.py +1 -0
- swarm/agents/intelligence/__init__.py +1 -0
- swarm/agents/intelligence/arbiter.py +30 -0
- swarm/agents/intelligence/sentinel.py +30 -0
- swarm/agents/media/__init__.py +0 -0
- swarm/agents/personal/__init__.py +0 -0
- swarm/agents/personal/atlas.py +27 -0
- swarm/agents/personal/hearth.py +26 -0
- swarm/agents/personal/midas.py +27 -0
- swarm/agents/personal/navigator.py +27 -0
- swarm/agents/personal/nourish.py +31 -0
- swarm/agents/personal/vitals.py +33 -0
- swarm/agents/recursive/__init__.py +0 -0
- swarm/agents/recursive/alchemist.py +73 -0
- swarm/agents/recursive/calibrator.py +45 -0
- swarm/agents/recursive/scout.py +97 -0
- swarm/agents/scientific/__init__.py +0 -0
- swarm/agents/scientific/cosmos.py +36 -0
- swarm/agents/scientific/gaia.py +26 -0
- swarm/agents/scientific/medicus.py +33 -0
- swarm/agents/scientific/oracle_agent.py +39 -0
- swarm/agents/scientific/scribe.py +39 -0
- swarm/agents/talent/__init__.py +0 -0
- swarm/agents/talent/deal.py +28 -0
- swarm/agents/talent/roster.py +29 -0
- swarm/agents/talent/scout.py +29 -0
- swarm/agents/talent/stage.py +27 -0
- swarm/agents/tech/__init__.py +0 -0
- swarm/agents/tech/aegis.py +28 -0
- swarm/agents/tech/bridge.py +28 -0
- swarm/agents/tech/forge.py +28 -0
- swarm/agents/trading/__init__.py +1 -0
- swarm/agents/trading/global_prediction_engine.py +611 -0
- swarm/agents/trading/polymarket_trader.py +165 -0
- swarm/agents/trading/prediction_strategist.py +218 -0
- swarm/autogen_bridge.py +68 -0
- swarm/bid_protocol.py +196 -0
- swarm/dispatcher.py +527 -0
- swarm/nexus.py +181 -0
- swarm/registry.py +112 -0
- telegram_listener.py +403 -0
- ui/tui/app.py +214 -0
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Document ingestion pipeline for Civilization Knowledge Layer.
|
|
3
|
+
Orchestrates parsing, extraction, chunking, and embedding.
|
|
4
|
+
"""
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
import logging
|
|
7
|
+
from dataclasses import dataclass, field
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Any, BinaryIO
|
|
10
|
+
from uuid import UUID, uuid4
|
|
11
|
+
|
|
12
|
+
from ..schema import (
|
|
13
|
+
CivilizationNode, NodeType, NodeStatus,
|
|
14
|
+
SOP, SOPStep, Checklist, ChecklistItem, Runbook, RunbookStep,
|
|
15
|
+
JobDescription, Role, Workflow, WorkflowNode, WorkflowEdge,
|
|
16
|
+
KnowledgeArticle, Policy,
|
|
17
|
+
)
|
|
18
|
+
from .chunker import ProceduralChunker, SOPChunk, ChecklistChunk, RunbookChunk, GenericChunk
|
|
19
|
+
from .extractor import MetadataExtractor, EntityExtractor, ExtractedMetadata
|
|
20
|
+
from .embedder import CivilizationEmbedder
|
|
21
|
+
from hyperclaw.taint import training_sink
|
|
22
|
+
|
|
23
|
+
logger = logging.getLogger(__name__)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass
|
|
27
|
+
class IngestionResult:
|
|
28
|
+
"""Result of document ingestion."""
|
|
29
|
+
success: bool
|
|
30
|
+
node_id: UUID | None = None
|
|
31
|
+
node_type: NodeType | None = None
|
|
32
|
+
chunks_created: int = 0
|
|
33
|
+
entities_extracted: int = 0
|
|
34
|
+
warnings: list[str] = field(default_factory=list)
|
|
35
|
+
errors: list[str] = field(default_factory=list)
|
|
36
|
+
metadata: dict = field(default_factory=dict)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class DocumentParser:
|
|
40
|
+
"""Parse various document formats into plain text."""
|
|
41
|
+
|
|
42
|
+
SUPPORTED_EXTENSIONS = {".txt", ".md", ".markdown", ".rst", ".json", ".yaml", ".yml"}
|
|
43
|
+
|
|
44
|
+
def parse(self, content: str | bytes, filename: str | None = None) -> str:
|
|
45
|
+
"""Parse document content to plain text."""
|
|
46
|
+
if isinstance(content, bytes):
|
|
47
|
+
content = content.decode("utf-8", errors="replace")
|
|
48
|
+
|
|
49
|
+
if filename:
|
|
50
|
+
ext = Path(filename).suffix.lower()
|
|
51
|
+
if ext in {".json"}:
|
|
52
|
+
return self._parse_json(content)
|
|
53
|
+
if ext in {".yaml", ".yml"}:
|
|
54
|
+
return self._parse_yaml(content)
|
|
55
|
+
|
|
56
|
+
return content
|
|
57
|
+
|
|
58
|
+
def _parse_json(self, content: str) -> str:
|
|
59
|
+
"""Parse JSON to readable text."""
|
|
60
|
+
import json
|
|
61
|
+
try:
|
|
62
|
+
data = json.loads(content)
|
|
63
|
+
return self._dict_to_text(data)
|
|
64
|
+
except json.JSONDecodeError:
|
|
65
|
+
return content
|
|
66
|
+
|
|
67
|
+
def _parse_yaml(self, content: str) -> str:
|
|
68
|
+
"""Parse YAML to readable text."""
|
|
69
|
+
try:
|
|
70
|
+
import yaml
|
|
71
|
+
data = yaml.safe_load(content)
|
|
72
|
+
return self._dict_to_text(data)
|
|
73
|
+
except Exception:
|
|
74
|
+
return content
|
|
75
|
+
|
|
76
|
+
def _dict_to_text(self, data: Any, indent: int = 0) -> str:
|
|
77
|
+
"""Convert dict/list to readable text format."""
|
|
78
|
+
lines = []
|
|
79
|
+
prefix = " " * indent
|
|
80
|
+
|
|
81
|
+
if isinstance(data, dict):
|
|
82
|
+
for key, value in data.items():
|
|
83
|
+
if isinstance(value, (dict, list)):
|
|
84
|
+
lines.append(f"{prefix}{key}:")
|
|
85
|
+
lines.append(self._dict_to_text(value, indent + 1))
|
|
86
|
+
else:
|
|
87
|
+
lines.append(f"{prefix}{key}: {value}")
|
|
88
|
+
elif isinstance(data, list):
|
|
89
|
+
for item in data:
|
|
90
|
+
if isinstance(item, (dict, list)):
|
|
91
|
+
lines.append(f"{prefix}-")
|
|
92
|
+
lines.append(self._dict_to_text(item, indent + 1))
|
|
93
|
+
else:
|
|
94
|
+
lines.append(f"{prefix}- {item}")
|
|
95
|
+
else:
|
|
96
|
+
lines.append(f"{prefix}{data}")
|
|
97
|
+
|
|
98
|
+
return "\n".join(lines)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class DocumentIngestor:
|
|
102
|
+
"""
|
|
103
|
+
Main ingestion pipeline for Civilization Knowledge Layer.
|
|
104
|
+
Coordinates parsing, extraction, chunking, and embedding.
|
|
105
|
+
"""
|
|
106
|
+
|
|
107
|
+
def __init__(
|
|
108
|
+
self,
|
|
109
|
+
org_id: str,
|
|
110
|
+
embedder: CivilizationEmbedder | None = None,
|
|
111
|
+
chunker: ProceduralChunker | None = None,
|
|
112
|
+
store=None, # CivilizationStore
|
|
113
|
+
):
|
|
114
|
+
self.org_id = org_id
|
|
115
|
+
self.embedder = embedder or CivilizationEmbedder()
|
|
116
|
+
self.chunker = chunker or ProceduralChunker()
|
|
117
|
+
self.store = store
|
|
118
|
+
self.parser = DocumentParser()
|
|
119
|
+
self.metadata_extractor = MetadataExtractor()
|
|
120
|
+
self.entity_extractor = EntityExtractor()
|
|
121
|
+
|
|
122
|
+
@training_sink("civilization.ingest")
|
|
123
|
+
async def ingest(
|
|
124
|
+
self,
|
|
125
|
+
content: str | bytes,
|
|
126
|
+
filename: str | None = None,
|
|
127
|
+
source: str | None = None,
|
|
128
|
+
source_url: str | None = None,
|
|
129
|
+
node_type_hint: NodeType | None = None,
|
|
130
|
+
owner_id: str | None = None,
|
|
131
|
+
tags: list[str] | None = None,
|
|
132
|
+
) -> IngestionResult:
|
|
133
|
+
"""
|
|
134
|
+
Ingest a document into the Civilization Knowledge Layer.
|
|
135
|
+
|
|
136
|
+
Args:
|
|
137
|
+
content: Raw document content (string or bytes)
|
|
138
|
+
filename: Original filename (helps with parsing)
|
|
139
|
+
source: Source system (e.g., "notion", "confluence", "upload")
|
|
140
|
+
source_url: URL of the source document
|
|
141
|
+
node_type_hint: Override auto-detection of node type
|
|
142
|
+
owner_id: ID of the document owner
|
|
143
|
+
tags: Tags to apply to the node
|
|
144
|
+
|
|
145
|
+
Returns:
|
|
146
|
+
IngestionResult with details of ingestion
|
|
147
|
+
|
|
148
|
+
Raises hyperclaw.taint.TaintedInput for connector content (a tainted
|
|
149
|
+
argument, or `source` naming a connector such as gdrive/notion).
|
|
150
|
+
"""
|
|
151
|
+
from hyperclaw.taint import refuse_connector_source
|
|
152
|
+
refuse_connector_source("civilization.ingest", source)
|
|
153
|
+
result = IngestionResult(success=False)
|
|
154
|
+
|
|
155
|
+
try:
|
|
156
|
+
# Parse document
|
|
157
|
+
text = self.parser.parse(content, filename)
|
|
158
|
+
if not text.strip():
|
|
159
|
+
result.errors.append("Document is empty after parsing")
|
|
160
|
+
return result
|
|
161
|
+
|
|
162
|
+
# Extract metadata
|
|
163
|
+
metadata = self.metadata_extractor.extract(text)
|
|
164
|
+
result.metadata = {
|
|
165
|
+
"title": metadata.title,
|
|
166
|
+
"purpose": metadata.purpose,
|
|
167
|
+
"detected_type": metadata.document_type,
|
|
168
|
+
"complexity": metadata.estimated_complexity,
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
# Determine node type
|
|
172
|
+
node_type = node_type_hint
|
|
173
|
+
if not node_type:
|
|
174
|
+
node_type = self._detect_node_type(metadata)
|
|
175
|
+
result.node_type = node_type
|
|
176
|
+
|
|
177
|
+
# Extract entities
|
|
178
|
+
entities = self.entity_extractor.extract(text)
|
|
179
|
+
result.entities_extracted = len(entities)
|
|
180
|
+
|
|
181
|
+
# Create node based on type
|
|
182
|
+
node = await self._create_node(
|
|
183
|
+
text=text,
|
|
184
|
+
metadata=metadata,
|
|
185
|
+
node_type=node_type,
|
|
186
|
+
source=source,
|
|
187
|
+
source_url=source_url,
|
|
188
|
+
owner_id=owner_id,
|
|
189
|
+
tags=tags or [],
|
|
190
|
+
)
|
|
191
|
+
result.node_id = node.id
|
|
192
|
+
|
|
193
|
+
# Generate chunks and embeddings
|
|
194
|
+
chunks = await self._chunk_and_embed(node, text)
|
|
195
|
+
result.chunks_created = len(chunks)
|
|
196
|
+
|
|
197
|
+
# Save to store if available
|
|
198
|
+
if self.store:
|
|
199
|
+
await self.store.save(node)
|
|
200
|
+
|
|
201
|
+
result.success = True
|
|
202
|
+
|
|
203
|
+
except Exception as e:
|
|
204
|
+
logger.exception("Ingestion failed")
|
|
205
|
+
result.errors.append(str(e))
|
|
206
|
+
|
|
207
|
+
return result
|
|
208
|
+
|
|
209
|
+
def _detect_node_type(self, metadata: ExtractedMetadata) -> NodeType:
|
|
210
|
+
"""Detect node type from extracted metadata."""
|
|
211
|
+
type_map = {
|
|
212
|
+
"sop": NodeType.SOP,
|
|
213
|
+
"checklist": NodeType.CHECKLIST,
|
|
214
|
+
"runbook": NodeType.RUNBOOK,
|
|
215
|
+
"job_description": NodeType.JOB_DESCRIPTION,
|
|
216
|
+
"org_chart": NodeType.ORG_CHART,
|
|
217
|
+
"workflow": NodeType.WORKFLOW,
|
|
218
|
+
"policy": NodeType.POLICY,
|
|
219
|
+
"knowledge_article": NodeType.KNOWLEDGE_ARTICLE,
|
|
220
|
+
}
|
|
221
|
+
return type_map.get(metadata.document_type or "", NodeType.KNOWLEDGE_ARTICLE)
|
|
222
|
+
|
|
223
|
+
async def _create_node(
|
|
224
|
+
self,
|
|
225
|
+
text: str,
|
|
226
|
+
metadata: ExtractedMetadata,
|
|
227
|
+
node_type: NodeType,
|
|
228
|
+
source: str | None,
|
|
229
|
+
source_url: str | None,
|
|
230
|
+
owner_id: str | None,
|
|
231
|
+
tags: list[str],
|
|
232
|
+
) -> CivilizationNode:
|
|
233
|
+
"""Create the appropriate node type."""
|
|
234
|
+
base_kwargs = {
|
|
235
|
+
"org_id": self.org_id,
|
|
236
|
+
"title": metadata.title or "Untitled Document",
|
|
237
|
+
"source": source,
|
|
238
|
+
"source_url": source_url,
|
|
239
|
+
"owner_id": owner_id,
|
|
240
|
+
"tags": tags + metadata.tags,
|
|
241
|
+
"status": NodeStatus.DRAFT,
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
# Generate embedding for the full document (or summary)
|
|
245
|
+
summary_text = text[:2000] if len(text) > 2000 else text
|
|
246
|
+
embedding = await self.embedder.embed(summary_text)
|
|
247
|
+
base_kwargs["embedding"] = embedding
|
|
248
|
+
|
|
249
|
+
if node_type == NodeType.SOP:
|
|
250
|
+
return SOP(
|
|
251
|
+
**base_kwargs,
|
|
252
|
+
purpose=metadata.purpose or "",
|
|
253
|
+
scope="",
|
|
254
|
+
steps=[SOPStep(step_number=1, title="Step 1", description=text[:500])],
|
|
255
|
+
roles_involved=metadata.roles_mentioned,
|
|
256
|
+
tools_required=metadata.tools_mentioned,
|
|
257
|
+
)
|
|
258
|
+
elif node_type == NodeType.CHECKLIST:
|
|
259
|
+
return Checklist(
|
|
260
|
+
**base_kwargs,
|
|
261
|
+
purpose=metadata.purpose or "",
|
|
262
|
+
items=[ChecklistItem(item_number=1, description=text[:500])],
|
|
263
|
+
roles_involved=metadata.roles_mentioned,
|
|
264
|
+
)
|
|
265
|
+
elif node_type == NodeType.RUNBOOK:
|
|
266
|
+
return Runbook(
|
|
267
|
+
**base_kwargs,
|
|
268
|
+
system=metadata.systems_mentioned[0] if metadata.systems_mentioned else "Unknown",
|
|
269
|
+
scenario="",
|
|
270
|
+
steps=[RunbookStep(step_number=1, action=text[:500])],
|
|
271
|
+
)
|
|
272
|
+
elif node_type == NodeType.JOB_DESCRIPTION:
|
|
273
|
+
return JobDescription(
|
|
274
|
+
**base_kwargs,
|
|
275
|
+
role_title=metadata.title or "Unknown Role",
|
|
276
|
+
department=metadata.department or "",
|
|
277
|
+
summary=metadata.purpose or "",
|
|
278
|
+
responsibilities=[],
|
|
279
|
+
required_skills=[],
|
|
280
|
+
tools_used=metadata.tools_mentioned,
|
|
281
|
+
)
|
|
282
|
+
elif node_type == NodeType.WORKFLOW:
|
|
283
|
+
return Workflow(
|
|
284
|
+
**base_kwargs,
|
|
285
|
+
description=metadata.purpose or "",
|
|
286
|
+
nodes=[WorkflowNode(node_id="start", label="Start", node_type="start")],
|
|
287
|
+
edges=[],
|
|
288
|
+
)
|
|
289
|
+
elif node_type == NodeType.POLICY:
|
|
290
|
+
return Policy(
|
|
291
|
+
**base_kwargs,
|
|
292
|
+
policy_name=metadata.title or "Untitled Policy",
|
|
293
|
+
category="general",
|
|
294
|
+
summary=metadata.purpose or "",
|
|
295
|
+
full_text=text,
|
|
296
|
+
)
|
|
297
|
+
else:
|
|
298
|
+
return KnowledgeArticle(
|
|
299
|
+
**base_kwargs,
|
|
300
|
+
topic=metadata.title or "Unknown Topic",
|
|
301
|
+
category="general",
|
|
302
|
+
summary=metadata.purpose or text[:300],
|
|
303
|
+
content=text,
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
async def _chunk_and_embed(
|
|
307
|
+
self,
|
|
308
|
+
node: CivilizationNode,
|
|
309
|
+
text: str,
|
|
310
|
+
) -> list[Any]:
|
|
311
|
+
"""Create chunks and generate embeddings for each."""
|
|
312
|
+
chunks: list[Any] = []
|
|
313
|
+
|
|
314
|
+
if isinstance(node, SOP):
|
|
315
|
+
chunks = self.chunker.chunk_sop(node)
|
|
316
|
+
elif isinstance(node, Checklist):
|
|
317
|
+
chunks = self.chunker.chunk_checklist(node)
|
|
318
|
+
elif isinstance(node, Runbook):
|
|
319
|
+
chunks = self.chunker.chunk_runbook(node)
|
|
320
|
+
else:
|
|
321
|
+
chunks = self.chunker.chunk_generic(node, text)
|
|
322
|
+
|
|
323
|
+
# Generate embeddings for all chunks
|
|
324
|
+
if chunks:
|
|
325
|
+
contents = [c.content for c in chunks]
|
|
326
|
+
embeddings = await self.embedder.embed_batch(contents)
|
|
327
|
+
for chunk, embedding in zip(chunks, embeddings):
|
|
328
|
+
chunk.embedding = embedding
|
|
329
|
+
|
|
330
|
+
return chunks
|
|
331
|
+
|
|
332
|
+
@training_sink("civilization.ingest_batch")
|
|
333
|
+
async def ingest_batch(
|
|
334
|
+
self,
|
|
335
|
+
documents: list[dict],
|
|
336
|
+
) -> list[IngestionResult]:
|
|
337
|
+
"""
|
|
338
|
+
Ingest multiple documents.
|
|
339
|
+
|
|
340
|
+
Args:
|
|
341
|
+
documents: List of dicts with keys: content, filename, source, source_url, etc.
|
|
342
|
+
|
|
343
|
+
Returns:
|
|
344
|
+
List of IngestionResult for each document
|
|
345
|
+
"""
|
|
346
|
+
results = []
|
|
347
|
+
for doc in documents:
|
|
348
|
+
result = await self.ingest(
|
|
349
|
+
content=doc.get("content", ""),
|
|
350
|
+
filename=doc.get("filename"),
|
|
351
|
+
source=doc.get("source"),
|
|
352
|
+
source_url=doc.get("source_url"),
|
|
353
|
+
node_type_hint=doc.get("node_type_hint"),
|
|
354
|
+
owner_id=doc.get("owner_id"),
|
|
355
|
+
tags=doc.get("tags"),
|
|
356
|
+
)
|
|
357
|
+
results.append(result)
|
|
358
|
+
return results
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Embedding generation for Civilization Knowledge Layer.
|
|
3
|
+
Supports multiple embedding providers with a unified interface.
|
|
4
|
+
"""
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
import os
|
|
7
|
+
import logging
|
|
8
|
+
from abc import ABC, abstractmethod
|
|
9
|
+
from typing import Any
|
|
10
|
+
import hashlib
|
|
11
|
+
import json
|
|
12
|
+
|
|
13
|
+
from hyperclaw.taint import training_sink
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class EmbeddingProvider(ABC):
|
|
19
|
+
"""Abstract base for embedding providers."""
|
|
20
|
+
|
|
21
|
+
@abstractmethod
|
|
22
|
+
async def embed(self, text: str) -> list[float]:
|
|
23
|
+
"""Generate embedding for a single text."""
|
|
24
|
+
pass
|
|
25
|
+
|
|
26
|
+
@abstractmethod
|
|
27
|
+
async def embed_batch(self, texts: list[str]) -> list[list[float]]:
|
|
28
|
+
"""Generate embeddings for multiple texts."""
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
@property
|
|
32
|
+
@abstractmethod
|
|
33
|
+
def dimension(self) -> int:
|
|
34
|
+
"""Return the embedding dimension."""
|
|
35
|
+
pass
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class OpenAIEmbedder(EmbeddingProvider):
|
|
39
|
+
"""OpenAI embedding provider using text-embedding-3-small."""
|
|
40
|
+
|
|
41
|
+
def __init__(self, api_key: str | None = None, model: str = "text-embedding-3-small"):
|
|
42
|
+
self.api_key = api_key or os.environ.get("OPENAI_API_KEY")
|
|
43
|
+
self.model = model
|
|
44
|
+
self._dimension = 1536 if "small" in model else 3072
|
|
45
|
+
|
|
46
|
+
async def embed(self, text: str) -> list[float]:
|
|
47
|
+
"""Generate embedding for a single text using OpenAI."""
|
|
48
|
+
import httpx
|
|
49
|
+
|
|
50
|
+
async with httpx.AsyncClient() as client:
|
|
51
|
+
response = await client.post(
|
|
52
|
+
"https://api.openai.com/v1/embeddings",
|
|
53
|
+
headers={"Authorization": f"Bearer {self.api_key}"},
|
|
54
|
+
json={"input": text, "model": self.model},
|
|
55
|
+
timeout=30.0,
|
|
56
|
+
)
|
|
57
|
+
response.raise_for_status()
|
|
58
|
+
data = response.json()
|
|
59
|
+
return data["data"][0]["embedding"]
|
|
60
|
+
|
|
61
|
+
async def embed_batch(self, texts: list[str]) -> list[list[float]]:
|
|
62
|
+
"""Generate embeddings for multiple texts."""
|
|
63
|
+
import httpx
|
|
64
|
+
|
|
65
|
+
# OpenAI supports batches of up to 2048 inputs
|
|
66
|
+
async with httpx.AsyncClient() as client:
|
|
67
|
+
response = await client.post(
|
|
68
|
+
"https://api.openai.com/v1/embeddings",
|
|
69
|
+
headers={"Authorization": f"Bearer {self.api_key}"},
|
|
70
|
+
json={"input": texts, "model": self.model},
|
|
71
|
+
timeout=60.0,
|
|
72
|
+
)
|
|
73
|
+
response.raise_for_status()
|
|
74
|
+
data = response.json()
|
|
75
|
+
# Sort by index to maintain order
|
|
76
|
+
sorted_data = sorted(data["data"], key=lambda x: x["index"])
|
|
77
|
+
return [item["embedding"] for item in sorted_data]
|
|
78
|
+
|
|
79
|
+
@property
|
|
80
|
+
def dimension(self) -> int:
|
|
81
|
+
return self._dimension
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class VoyageEmbedder(EmbeddingProvider):
|
|
85
|
+
"""Voyage AI embedding provider."""
|
|
86
|
+
|
|
87
|
+
def __init__(self, api_key: str | None = None, model: str = "voyage-2"):
|
|
88
|
+
self.api_key = api_key or os.environ.get("VOYAGE_API_KEY")
|
|
89
|
+
self.model = model
|
|
90
|
+
self._dimension = 1024
|
|
91
|
+
|
|
92
|
+
async def embed(self, text: str) -> list[float]:
|
|
93
|
+
import httpx
|
|
94
|
+
|
|
95
|
+
async with httpx.AsyncClient() as client:
|
|
96
|
+
response = await client.post(
|
|
97
|
+
"https://api.voyageai.com/v1/embeddings",
|
|
98
|
+
headers={"Authorization": f"Bearer {self.api_key}"},
|
|
99
|
+
json={"input": text, "model": self.model},
|
|
100
|
+
timeout=30.0,
|
|
101
|
+
)
|
|
102
|
+
response.raise_for_status()
|
|
103
|
+
data = response.json()
|
|
104
|
+
return data["data"][0]["embedding"]
|
|
105
|
+
|
|
106
|
+
async def embed_batch(self, texts: list[str]) -> list[list[float]]:
|
|
107
|
+
import httpx
|
|
108
|
+
|
|
109
|
+
async with httpx.AsyncClient() as client:
|
|
110
|
+
response = await client.post(
|
|
111
|
+
"https://api.voyageai.com/v1/embeddings",
|
|
112
|
+
headers={"Authorization": f"Bearer {self.api_key}"},
|
|
113
|
+
json={"input": texts, "model": self.model},
|
|
114
|
+
timeout=60.0,
|
|
115
|
+
)
|
|
116
|
+
response.raise_for_status()
|
|
117
|
+
data = response.json()
|
|
118
|
+
sorted_data = sorted(data["data"], key=lambda x: x["index"])
|
|
119
|
+
return [item["embedding"] for item in sorted_data]
|
|
120
|
+
|
|
121
|
+
@property
|
|
122
|
+
def dimension(self) -> int:
|
|
123
|
+
return self._dimension
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class BedrockEmbedder(EmbeddingProvider):
|
|
127
|
+
"""Amazon Bedrock Titan Embeddings V2."""
|
|
128
|
+
|
|
129
|
+
def __init__(self, model: str = "amazon.titan-embed-text-v2:0"):
|
|
130
|
+
import boto3
|
|
131
|
+
self.model = model
|
|
132
|
+
self._dimension = 1024
|
|
133
|
+
self._client = boto3.client("bedrock-runtime")
|
|
134
|
+
|
|
135
|
+
async def embed(self, text: str) -> list[float]:
|
|
136
|
+
response = self._client.invoke_model(
|
|
137
|
+
modelId=self.model,
|
|
138
|
+
body=json.dumps({"inputText": text, "dimensions": self._dimension}),
|
|
139
|
+
contentType="application/json",
|
|
140
|
+
accept="application/json",
|
|
141
|
+
)
|
|
142
|
+
data = json.loads(response["body"].read())
|
|
143
|
+
return data["embedding"]
|
|
144
|
+
|
|
145
|
+
async def embed_batch(self, texts: list[str]) -> list[list[float]]:
|
|
146
|
+
# Titan V2 invoke_model is single-text only — no batch endpoint exists for this model
|
|
147
|
+
return [await self.embed(text) for text in texts]
|
|
148
|
+
|
|
149
|
+
@property
|
|
150
|
+
def dimension(self) -> int:
|
|
151
|
+
return self._dimension
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
class MockEmbedder(EmbeddingProvider):
|
|
155
|
+
"""Mock embedder for testing. Generates deterministic embeddings based on text hash."""
|
|
156
|
+
|
|
157
|
+
def __init__(self, dimension: int = 1536):
|
|
158
|
+
self._dimension = dimension
|
|
159
|
+
|
|
160
|
+
async def embed(self, text: str) -> list[float]:
|
|
161
|
+
"""Generate deterministic mock embedding from text hash."""
|
|
162
|
+
hash_bytes = hashlib.sha256(text.encode()).digest()
|
|
163
|
+
# Expand hash to fill dimension
|
|
164
|
+
embedding = []
|
|
165
|
+
for i in range(self._dimension):
|
|
166
|
+
byte_idx = i % len(hash_bytes)
|
|
167
|
+
# Normalize to [-1, 1]
|
|
168
|
+
value = (hash_bytes[byte_idx] / 127.5) - 1.0
|
|
169
|
+
embedding.append(value)
|
|
170
|
+
return embedding
|
|
171
|
+
|
|
172
|
+
async def embed_batch(self, texts: list[str]) -> list[list[float]]:
|
|
173
|
+
return [await self.embed(text) for text in texts]
|
|
174
|
+
|
|
175
|
+
@property
|
|
176
|
+
def dimension(self) -> int:
|
|
177
|
+
return self._dimension
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
class CivilizationEmbedder:
|
|
181
|
+
"""
|
|
182
|
+
Main embedder class for Civilization Knowledge Layer.
|
|
183
|
+
Handles caching, batching, and provider selection.
|
|
184
|
+
"""
|
|
185
|
+
|
|
186
|
+
def __init__(
|
|
187
|
+
self,
|
|
188
|
+
provider: EmbeddingProvider | None = None,
|
|
189
|
+
cache_enabled: bool = True,
|
|
190
|
+
):
|
|
191
|
+
self.provider = provider or MockEmbedder()
|
|
192
|
+
self.cache_enabled = cache_enabled
|
|
193
|
+
self._cache: dict[str, list[float]] = {}
|
|
194
|
+
|
|
195
|
+
def _cache_key(self, text: str) -> str:
|
|
196
|
+
"""Generate cache key for text."""
|
|
197
|
+
return hashlib.sha256(text.encode()).hexdigest()[:32]
|
|
198
|
+
|
|
199
|
+
@training_sink("civilization.embedder.embed", own_words=True)
|
|
200
|
+
async def embed(self, text: str) -> list[float]:
|
|
201
|
+
"""Generate embedding for text with caching."""
|
|
202
|
+
if self.cache_enabled:
|
|
203
|
+
key = self._cache_key(text)
|
|
204
|
+
if key in self._cache:
|
|
205
|
+
return self._cache[key]
|
|
206
|
+
|
|
207
|
+
embedding = await self.provider.embed(text)
|
|
208
|
+
|
|
209
|
+
if self.cache_enabled:
|
|
210
|
+
self._cache[key] = embedding
|
|
211
|
+
|
|
212
|
+
return embedding
|
|
213
|
+
|
|
214
|
+
@training_sink("civilization.embedder.embed_batch", own_words=True)
|
|
215
|
+
async def embed_batch(self, texts: list[str]) -> list[list[float]]:
|
|
216
|
+
"""Generate embeddings for multiple texts with caching."""
|
|
217
|
+
if not self.cache_enabled:
|
|
218
|
+
return await self.provider.embed_batch(texts)
|
|
219
|
+
|
|
220
|
+
# Check cache for existing embeddings
|
|
221
|
+
results: list[list[float] | None] = [None] * len(texts)
|
|
222
|
+
texts_to_embed: list[tuple[int, str]] = []
|
|
223
|
+
|
|
224
|
+
for i, text in enumerate(texts):
|
|
225
|
+
key = self._cache_key(text)
|
|
226
|
+
if key in self._cache:
|
|
227
|
+
results[i] = self._cache[key]
|
|
228
|
+
else:
|
|
229
|
+
texts_to_embed.append((i, text))
|
|
230
|
+
|
|
231
|
+
# Embed uncached texts
|
|
232
|
+
if texts_to_embed:
|
|
233
|
+
indices, uncached_texts = zip(*texts_to_embed)
|
|
234
|
+
new_embeddings = await self.provider.embed_batch(list(uncached_texts))
|
|
235
|
+
|
|
236
|
+
for idx, embedding, text in zip(indices, new_embeddings, uncached_texts):
|
|
237
|
+
results[idx] = embedding
|
|
238
|
+
self._cache[self._cache_key(text)] = embedding
|
|
239
|
+
|
|
240
|
+
return results # type: ignore
|
|
241
|
+
|
|
242
|
+
@property
|
|
243
|
+
def dimension(self) -> int:
|
|
244
|
+
"""Return embedding dimension from provider."""
|
|
245
|
+
return self.provider.dimension
|
|
246
|
+
|
|
247
|
+
def clear_cache(self) -> None:
|
|
248
|
+
"""Clear the embedding cache."""
|
|
249
|
+
self._cache.clear()
|
|
250
|
+
|
|
251
|
+
@classmethod
|
|
252
|
+
def create(cls, provider_name: str = "mock", **kwargs) -> "CivilizationEmbedder":
|
|
253
|
+
"""Factory method to create embedder with specified provider."""
|
|
254
|
+
providers = {
|
|
255
|
+
"openai": OpenAIEmbedder,
|
|
256
|
+
"voyage": VoyageEmbedder,
|
|
257
|
+
"mock": MockEmbedder,
|
|
258
|
+
"bedrock": BedrockEmbedder,
|
|
259
|
+
}
|
|
260
|
+
provider_cls = providers.get(provider_name, MockEmbedder)
|
|
261
|
+
provider = provider_cls(**kwargs)
|
|
262
|
+
return cls(provider=provider)
|