synapse-framework 0.2.4__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.
- synapse/__init__.py +207 -0
- synapse/app.py +942 -0
- synapse/cli.py +88 -0
- synapse/config.py +103 -0
- synapse/connectors/__init__.py +29 -0
- synapse/connectors/base.py +222 -0
- synapse/connectors/factory.py +202 -0
- synapse/connectors/gmail.py +292 -0
- synapse/connectors/mcp_wrapper.py +153 -0
- synapse/connectors/notion.py +161 -0
- synapse/connectors/photos.py +141 -0
- synapse/database/__init__.py +19 -0
- synapse/database/base.py +63 -0
- synapse/database/chroma.py +175 -0
- synapse/database/memory.py +132 -0
- synapse/database/pgvector.py +99 -0
- synapse/database/qdrant.py +138 -0
- synapse/embeddings/__init__.py +21 -0
- synapse/embeddings/base.py +262 -0
- synapse/export/__init__.py +14 -0
- synapse/export/mcp_exporter.py +202 -0
- synapse/graph/__init__.py +21 -0
- synapse/graph/base.py +130 -0
- synapse/graph/memory.py +20 -0
- synapse/graph/neo4j_store.py +176 -0
- synapse/graph/networkx_store.py +221 -0
- synapse/inspector/__init__.py +5 -0
- synapse/inspector/template.py +530 -0
- synapse/models/__init__.py +73 -0
- synapse/models/base.py +95 -0
- synapse/models/gemini.py +328 -0
- synapse/models/ollama.py +322 -0
- synapse/models/openai.py +289 -0
- synapse/oauth/__init__.py +7 -0
- synapse/oauth/google.py +194 -0
- synapse/oauth/notion.py +35 -0
- synapse/rag/__init__.py +8 -0
- synapse/rag/chunker.py +219 -0
- synapse/rag/hybrid_rag.py +122 -0
- synapse/resource/__init__.py +5 -0
- synapse/resource/resource.py +304 -0
- synapse/resources/__init__.py +5 -0
- synapse/skills/__init__.py +5 -0
- synapse/skills/skill.py +321 -0
- synapse/storage/__init__.py +24 -0
- synapse/storage/graph_exporter.py +624 -0
- synapse/storage/graph_store.py +162 -0
- synapse/storage/vector_store.py +103 -0
- synapse_framework-0.2.4.dist-info/METADATA +415 -0
- synapse_framework-0.2.4.dist-info/RECORD +54 -0
- synapse_framework-0.2.4.dist-info/WHEEL +5 -0
- synapse_framework-0.2.4.dist-info/entry_points.txt +2 -0
- synapse_framework-0.2.4.dist-info/licenses/LICENSE +21 -0
- synapse_framework-0.2.4.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"""Qdrant vector database provider for Synapse."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any, Union
|
|
6
|
+
|
|
7
|
+
from langchain_core.documents import Document
|
|
8
|
+
|
|
9
|
+
from synapse.database.base import BaseVectorDB
|
|
10
|
+
from synapse.embeddings import resolve_embedding
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class QdrantDB(BaseVectorDB):
|
|
14
|
+
"""Qdrant vector database adapter supporting local memory, disk, or remote cloud cluster."""
|
|
15
|
+
|
|
16
|
+
def __init__(
|
|
17
|
+
self,
|
|
18
|
+
location: str | None = ":memory:",
|
|
19
|
+
url: str | None = None,
|
|
20
|
+
api_key: str | None = None,
|
|
21
|
+
path: str | None = None,
|
|
22
|
+
collection_name: str = "synapse_collection",
|
|
23
|
+
embedding: Union[str, Any] | None = None,
|
|
24
|
+
embedding_model: str | None = None,
|
|
25
|
+
embedding_function: Any | None = None,
|
|
26
|
+
**kwargs: Any,
|
|
27
|
+
) -> None:
|
|
28
|
+
self.collection_name = collection_name
|
|
29
|
+
self.embedding_function = resolve_embedding(
|
|
30
|
+
embedding=embedding or embedding_function or embedding_model,
|
|
31
|
+
**kwargs,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
try:
|
|
35
|
+
from langchain_community.vectorstores import Qdrant
|
|
36
|
+
from qdrant_client import QdrantClient
|
|
37
|
+
|
|
38
|
+
if url is not None:
|
|
39
|
+
self.client = QdrantClient(url=url, api_key=api_key)
|
|
40
|
+
elif path is not None:
|
|
41
|
+
self.client = QdrantClient(path=path)
|
|
42
|
+
else:
|
|
43
|
+
self.client = QdrantClient(location=location or ":memory:")
|
|
44
|
+
|
|
45
|
+
self.store = Qdrant(
|
|
46
|
+
client=self.client,
|
|
47
|
+
collection_name=collection_name,
|
|
48
|
+
embeddings=self.embedding_function,
|
|
49
|
+
)
|
|
50
|
+
except ImportError:
|
|
51
|
+
# Fallback wrapper if qdrant-client is not installed
|
|
52
|
+
from synapse.database.memory import InMemoryDB
|
|
53
|
+
self._fallback = InMemoryDB(
|
|
54
|
+
embedding_model=embedding_model,
|
|
55
|
+
embedding_function=self.embedding_function,
|
|
56
|
+
)
|
|
57
|
+
self.store = None
|
|
58
|
+
|
|
59
|
+
def add_documents(
|
|
60
|
+
self,
|
|
61
|
+
documents: list[Document],
|
|
62
|
+
embeddings: list[list[float]] | None = None,
|
|
63
|
+
) -> list[str]:
|
|
64
|
+
if self.store:
|
|
65
|
+
return self.store.add_documents(documents)
|
|
66
|
+
return self._fallback.add_documents(documents, embeddings)
|
|
67
|
+
|
|
68
|
+
def similarity_search(
|
|
69
|
+
self,
|
|
70
|
+
query: str,
|
|
71
|
+
k: int = 4,
|
|
72
|
+
filter_dict: dict[str, Any] | None = None,
|
|
73
|
+
query_embedding: list[float] | None = None,
|
|
74
|
+
) -> list[Document]:
|
|
75
|
+
if self.store:
|
|
76
|
+
return self.store.similarity_search(query=query, k=k)
|
|
77
|
+
return self._fallback.similarity_search(query=query, k=k, filter_dict=filter_dict)
|
|
78
|
+
|
|
79
|
+
def count(self) -> int:
|
|
80
|
+
if self.store:
|
|
81
|
+
try:
|
|
82
|
+
res = self.client.get_collection(self.collection_name)
|
|
83
|
+
return getattr(res, "points_count", 0) or 0
|
|
84
|
+
except Exception:
|
|
85
|
+
return 0
|
|
86
|
+
return self._fallback.count()
|
|
87
|
+
|
|
88
|
+
def clear(self) -> None:
|
|
89
|
+
if self.store:
|
|
90
|
+
try:
|
|
91
|
+
self.client.delete_collection(self.collection_name)
|
|
92
|
+
except Exception:
|
|
93
|
+
pass # nosec B110
|
|
94
|
+
else:
|
|
95
|
+
self._fallback.clear()
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def qdrant(
|
|
99
|
+
location: str | None = ":memory:",
|
|
100
|
+
url: str | None = None,
|
|
101
|
+
api_key: str | None = None,
|
|
102
|
+
path: str | None = None,
|
|
103
|
+
collection_name: str = "synapse_collection",
|
|
104
|
+
embedding: Union[str, Any] | None = None,
|
|
105
|
+
embedding_model: str | None = None,
|
|
106
|
+
embedding_function: Any | None = None,
|
|
107
|
+
**kwargs: Any,
|
|
108
|
+
) -> QdrantDB:
|
|
109
|
+
"""Factory helper to configure a Qdrant vector database instance.
|
|
110
|
+
|
|
111
|
+
Example:
|
|
112
|
+
```python
|
|
113
|
+
from synapse.database import qdrant
|
|
114
|
+
|
|
115
|
+
# In-memory with MiniLM:
|
|
116
|
+
db = qdrant(embedding="minilm")
|
|
117
|
+
|
|
118
|
+
# Local disk with FastEmbed BGE:
|
|
119
|
+
db = qdrant(path="data/qdrant_db", embedding="fastembed")
|
|
120
|
+
|
|
121
|
+
# Remote cluster:
|
|
122
|
+
db = qdrant(url="https://xyz.cloud.qdrant.io", api_key="...", embedding="minilm")
|
|
123
|
+
```
|
|
124
|
+
"""
|
|
125
|
+
return QdrantDB(
|
|
126
|
+
location=location,
|
|
127
|
+
url=url,
|
|
128
|
+
api_key=api_key,
|
|
129
|
+
path=path,
|
|
130
|
+
collection_name=collection_name,
|
|
131
|
+
embedding=embedding,
|
|
132
|
+
embedding_model=embedding_model,
|
|
133
|
+
embedding_function=embedding_function,
|
|
134
|
+
**kwargs,
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
__all__ = ["QdrantDB", "qdrant"]
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""Embedding model providers and resolution utilities for Synapse."""
|
|
2
|
+
|
|
3
|
+
from synapse.embeddings.base import (
|
|
4
|
+
EmbeddingModelRegistry,
|
|
5
|
+
fastembed,
|
|
6
|
+
huggingface_embeddings,
|
|
7
|
+
minilm,
|
|
8
|
+
ollama_embeddings,
|
|
9
|
+
openai_embeddings,
|
|
10
|
+
resolve_embedding,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"EmbeddingModelRegistry",
|
|
15
|
+
"fastembed",
|
|
16
|
+
"huggingface_embeddings",
|
|
17
|
+
"minilm",
|
|
18
|
+
"ollama_embeddings",
|
|
19
|
+
"openai_embeddings",
|
|
20
|
+
"resolve_embedding",
|
|
21
|
+
]
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
"""Pluggable embedding providers and model resolution engine for Synapse."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from synapse.config import DEFAULT_EMBEDDING_MODEL
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class EmbeddingModelRegistry:
|
|
11
|
+
"""Friendly shorthand aliases for popular embedding models."""
|
|
12
|
+
|
|
13
|
+
ALIASES: dict[str, str] = {
|
|
14
|
+
# MiniLM variants
|
|
15
|
+
"minilm": "sentence-transformers/all-MiniLM-L6-v2",
|
|
16
|
+
"all-minilm": "sentence-transformers/all-MiniLM-L6-v2",
|
|
17
|
+
"all-minilm-l6-v2": "sentence-transformers/all-MiniLM-L6-v2",
|
|
18
|
+
"minilm-l6": "sentence-transformers/all-MiniLM-L6-v2",
|
|
19
|
+
"paraphrase-minilm": "sentence-transformers/paraphrase-MiniLM-L6-v2",
|
|
20
|
+
# BGE variants (Default FastEmbed models)
|
|
21
|
+
"fastembed": DEFAULT_EMBEDDING_MODEL,
|
|
22
|
+
"bge": DEFAULT_EMBEDDING_MODEL,
|
|
23
|
+
"bge-small": "BAAI/bge-small-en-v1.5",
|
|
24
|
+
"bge-base": "BAAI/bge-base-en-v1.5",
|
|
25
|
+
"bge-large": "BAAI/bge-large-en-v1.5",
|
|
26
|
+
# Nomic
|
|
27
|
+
"nomic": "nomic-ai/nomic-embed-text-v1.5",
|
|
28
|
+
"nomic-embed": "nomic-ai/nomic-embed-text-v1.5",
|
|
29
|
+
# Multilingual E5
|
|
30
|
+
"e5": "intfloat/multilingual-e5-large",
|
|
31
|
+
"multilingual-e5": "intfloat/multilingual-e5-large",
|
|
32
|
+
# OpenAI models
|
|
33
|
+
"openai": "text-embedding-3-small",
|
|
34
|
+
"text-embedding-3-small": "text-embedding-3-small",
|
|
35
|
+
"text-embedding-3-large": "text-embedding-3-large",
|
|
36
|
+
"ada": "text-embedding-ada-002",
|
|
37
|
+
"text-embedding-ada-002": "text-embedding-ada-002",
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def fastembed(
|
|
42
|
+
model_name: str = "BAAI/bge-small-en-v1.5",
|
|
43
|
+
max_length: int | None = None,
|
|
44
|
+
**kwargs: Any,
|
|
45
|
+
) -> Any:
|
|
46
|
+
"""Initializes FastEmbed ONNX local embeddings (ultra-fast, zero-PyTorch).
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
model_name: Model name or HuggingFace ID (e.g. 'BAAI/bge-small-en-v1.5',
|
|
50
|
+
'sentence-transformers/all-MiniLM-L6-v2', 'nomic-ai/nomic-embed-text-v1.5').
|
|
51
|
+
max_length: Optional maximum sequence length.
|
|
52
|
+
**kwargs: Extra arguments forwarded to FastEmbedEmbeddings.
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
FastEmbedEmbeddings instance.
|
|
56
|
+
"""
|
|
57
|
+
resolved_model = EmbeddingModelRegistry.ALIASES.get(model_name.lower(), model_name)
|
|
58
|
+
try:
|
|
59
|
+
from langchain_community.embeddings.fastembed import FastEmbedEmbeddings
|
|
60
|
+
|
|
61
|
+
extra_args: dict[str, Any] = dict(kwargs)
|
|
62
|
+
if max_length is not None:
|
|
63
|
+
extra_args["max_length"] = max_length
|
|
64
|
+
return FastEmbedEmbeddings(model_name=resolved_model, **extra_args)
|
|
65
|
+
except Exception as e:
|
|
66
|
+
raise RuntimeError(
|
|
67
|
+
f"Failed to load FastEmbed model '{resolved_model}': {e}. "
|
|
68
|
+
"Ensure 'fastembed' is installed."
|
|
69
|
+
) from e
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def minilm(
|
|
73
|
+
model_name: str = "sentence-transformers/all-MiniLM-L6-v2",
|
|
74
|
+
**kwargs: Any,
|
|
75
|
+
) -> Any:
|
|
76
|
+
"""Convenience factory for MiniLM embeddings via FastEmbed ONNX runtime.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
model_name: MiniLM model variant. Defaults to 'sentence-transformers/all-MiniLM-L6-v2'.
|
|
80
|
+
**kwargs: Extra parameters passed to FastEmbed.
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
FastEmbedEmbeddings configured with all-MiniLM-L6-v2.
|
|
84
|
+
"""
|
|
85
|
+
return fastembed(model_name=model_name, **kwargs)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def openai_embeddings(
|
|
89
|
+
model: str = "text-embedding-3-small",
|
|
90
|
+
api_key: str | None = None,
|
|
91
|
+
**kwargs: Any,
|
|
92
|
+
) -> Any:
|
|
93
|
+
"""Initializes OpenAI cloud vector embeddings.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
model: OpenAI embedding model name. Defaults to 'text-embedding-3-small'.
|
|
97
|
+
api_key: Optional OpenAI API key (reads OPENAI_API_KEY env if omitted).
|
|
98
|
+
**kwargs: Extra parameters passed to OpenAIEmbeddings.
|
|
99
|
+
|
|
100
|
+
Returns:
|
|
101
|
+
OpenAIEmbeddings instance.
|
|
102
|
+
"""
|
|
103
|
+
try:
|
|
104
|
+
from langchain_openai import OpenAIEmbeddings
|
|
105
|
+
|
|
106
|
+
return OpenAIEmbeddings(model=model, api_key=api_key, **kwargs)
|
|
107
|
+
except ImportError:
|
|
108
|
+
try:
|
|
109
|
+
from langchain_community.embeddings import OpenAIEmbeddings
|
|
110
|
+
|
|
111
|
+
return OpenAIEmbeddings(model=model, openai_api_key=api_key, **kwargs)
|
|
112
|
+
except Exception as e:
|
|
113
|
+
raise ImportError(
|
|
114
|
+
"OpenAI embeddings require 'langchain-openai'. "
|
|
115
|
+
"Install it via 'pip install langchain-openai'."
|
|
116
|
+
) from e
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def ollama_embeddings(
|
|
120
|
+
model: str = "nomic-embed-text",
|
|
121
|
+
base_url: str = "http://localhost:11434",
|
|
122
|
+
**kwargs: Any,
|
|
123
|
+
) -> Any:
|
|
124
|
+
"""Initializes Ollama local embeddings.
|
|
125
|
+
|
|
126
|
+
Args:
|
|
127
|
+
model: Model tag installed in Ollama (e.g. 'nomic-embed-text', 'all-minilm').
|
|
128
|
+
base_url: Ollama daemon URL. Defaults to 'http://localhost:11434'.
|
|
129
|
+
**kwargs: Extra parameters.
|
|
130
|
+
|
|
131
|
+
Returns:
|
|
132
|
+
OllamaEmbeddings instance.
|
|
133
|
+
"""
|
|
134
|
+
try:
|
|
135
|
+
from langchain_community.embeddings import OllamaEmbeddings
|
|
136
|
+
|
|
137
|
+
return OllamaEmbeddings(model=model, base_url=base_url, **kwargs)
|
|
138
|
+
except Exception as e:
|
|
139
|
+
raise ImportError(
|
|
140
|
+
f"Failed to load Ollama embeddings: {e}. "
|
|
141
|
+
"Ensure 'langchain-community' and Ollama are available."
|
|
142
|
+
) from e
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def huggingface_embeddings(
|
|
146
|
+
model_name: str = "sentence-transformers/all-MiniLM-L6-v2",
|
|
147
|
+
**kwargs: Any,
|
|
148
|
+
) -> Any:
|
|
149
|
+
"""Initializes PyTorch HuggingFace sentence-transformers embeddings.
|
|
150
|
+
|
|
151
|
+
Args:
|
|
152
|
+
model_name: HuggingFace model repo name.
|
|
153
|
+
**kwargs: Extra parameters.
|
|
154
|
+
|
|
155
|
+
Returns:
|
|
156
|
+
HuggingFaceEmbeddings instance.
|
|
157
|
+
"""
|
|
158
|
+
try:
|
|
159
|
+
from langchain_community.embeddings import HuggingFaceEmbeddings
|
|
160
|
+
|
|
161
|
+
return HuggingFaceEmbeddings(model_name=model_name, **kwargs)
|
|
162
|
+
except Exception:
|
|
163
|
+
# Fall back to FastEmbed for identical weights with 0 PyTorch
|
|
164
|
+
return fastembed(model_name=model_name, **kwargs)
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def resolve_embedding(
|
|
168
|
+
embedding: str | Any | None = None,
|
|
169
|
+
embedding_model: str | None = None,
|
|
170
|
+
embedding_function: Any | None = None,
|
|
171
|
+
**kwargs: Any,
|
|
172
|
+
) -> Any:
|
|
173
|
+
"""Flexible resolver converting strings, aliases, or objects into an embedding instance.
|
|
174
|
+
|
|
175
|
+
Supported patterns:
|
|
176
|
+
- None: Defaults to 'BAAI/bge-small-en-v1.5' via FastEmbed
|
|
177
|
+
- 'minilm' or 'all-minilm-l6-v2': MiniLM ONNX embedding
|
|
178
|
+
- 'fastembed' or 'bge-small': BGE Small ONNX embedding
|
|
179
|
+
- 'openai' or 'text-embedding-3-small': OpenAI cloud embedding
|
|
180
|
+
- 'ollama' or 'nomic-embed-text': Local Ollama embedding
|
|
181
|
+
- Custom Embeddings object: Returned as-is
|
|
182
|
+
|
|
183
|
+
Args:
|
|
184
|
+
embedding: Model alias string, HuggingFace ID, or Embeddings instance.
|
|
185
|
+
embedding_model: Backward-compatible alias for embedding string.
|
|
186
|
+
embedding_function: Backward-compatible alias for custom object.
|
|
187
|
+
**kwargs: Extra keyword arguments forwarded to the provider.
|
|
188
|
+
|
|
189
|
+
Returns:
|
|
190
|
+
An initialized embedding function satisfying the LangChain Embeddings protocol.
|
|
191
|
+
"""
|
|
192
|
+
# 1. Custom pre-instantiated object passed
|
|
193
|
+
target = embedding if embedding is not None else (embedding_function or embedding_model)
|
|
194
|
+
|
|
195
|
+
if target is None:
|
|
196
|
+
return fastembed(DEFAULT_EMBEDDING_MODEL, **kwargs)
|
|
197
|
+
|
|
198
|
+
# Check if target is already an embedding instance (has embed_documents / embed_query)
|
|
199
|
+
if hasattr(target, "embed_documents") and hasattr(target, "embed_query"):
|
|
200
|
+
return target
|
|
201
|
+
|
|
202
|
+
# Callable function wrapper
|
|
203
|
+
if callable(target) and not isinstance(target, str):
|
|
204
|
+
return target
|
|
205
|
+
|
|
206
|
+
# 2. String alias resolution
|
|
207
|
+
if isinstance(target, str):
|
|
208
|
+
clean_str = target.strip()
|
|
209
|
+
lower_str = clean_str.lower()
|
|
210
|
+
|
|
211
|
+
# MiniLM shorthand
|
|
212
|
+
if lower_str in ("minilm", "all-minilm", "all-minilm-l6-v2", "minilm-l6", "sentence-transformers/all-minilm-l6-v2"):
|
|
213
|
+
return minilm(**kwargs)
|
|
214
|
+
|
|
215
|
+
# FastEmbed / BGE shorthands
|
|
216
|
+
if lower_str in ("fastembed", "bge", "bge-small", "baai/bge-small-en-v1.5"):
|
|
217
|
+
return fastembed("BAAI/bge-small-en-v1.5", **kwargs)
|
|
218
|
+
|
|
219
|
+
if lower_str in ("bge-base", "baai/bge-base-en-v1.5"):
|
|
220
|
+
return fastembed("BAAI/bge-base-en-v1.5", **kwargs)
|
|
221
|
+
|
|
222
|
+
if lower_str in ("bge-large", "baai/bge-large-en-v1.5"):
|
|
223
|
+
return fastembed("BAAI/bge-large-en-v1.5", **kwargs)
|
|
224
|
+
|
|
225
|
+
if lower_str in ("nomic", "nomic-embed", "nomic-ai/nomic-embed-text-v1.5"):
|
|
226
|
+
return fastembed("nomic-ai/nomic-embed-text-v1.5", **kwargs)
|
|
227
|
+
|
|
228
|
+
if lower_str in ("e5", "multilingual-e5", "intfloat/multilingual-e5-large"):
|
|
229
|
+
return fastembed("intfloat/multilingual-e5-large", **kwargs)
|
|
230
|
+
|
|
231
|
+
# OpenAI shorthands
|
|
232
|
+
if lower_str in ("openai", "text-embedding-3-small", "text-embedding-3-large", "ada", "text-embedding-ada-002"):
|
|
233
|
+
model_name = "text-embedding-3-small" if lower_str == "openai" else clean_str
|
|
234
|
+
return openai_embeddings(model=model_name, **kwargs)
|
|
235
|
+
|
|
236
|
+
# Ollama shorthands
|
|
237
|
+
if lower_str.startswith("ollama:"):
|
|
238
|
+
ollama_m = clean_str.split(":", 1)[1]
|
|
239
|
+
return ollama_embeddings(model=ollama_m, **kwargs)
|
|
240
|
+
|
|
241
|
+
if lower_str == "ollama":
|
|
242
|
+
return ollama_embeddings(**kwargs)
|
|
243
|
+
|
|
244
|
+
# Try FastEmbed with the arbitrary model name (supports HuggingFace repo names)
|
|
245
|
+
try:
|
|
246
|
+
return fastembed(clean_str, **kwargs)
|
|
247
|
+
except Exception:
|
|
248
|
+
# Fall back to HuggingFace Embeddings
|
|
249
|
+
return huggingface_embeddings(clean_str, **kwargs)
|
|
250
|
+
|
|
251
|
+
raise ValueError(f"Unsupported embedding specification: {target!r}")
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
__all__ = [
|
|
255
|
+
"EmbeddingModelRegistry",
|
|
256
|
+
"fastembed",
|
|
257
|
+
"huggingface_embeddings",
|
|
258
|
+
"minilm",
|
|
259
|
+
"ollama_embeddings",
|
|
260
|
+
"openai_embeddings",
|
|
261
|
+
"resolve_embedding",
|
|
262
|
+
]
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""Synapse exporters for Model Context Protocol (MCP) schemas and 3D knowledge graphs."""
|
|
2
|
+
|
|
3
|
+
from synapse.export.mcp_exporter import MCPExporter, mcpexporter
|
|
4
|
+
from synapse.storage.graph_exporter import GraphExporter
|
|
5
|
+
|
|
6
|
+
# Developer-friendly alias
|
|
7
|
+
graphexporter = GraphExporter
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
"GraphExporter",
|
|
11
|
+
"MCPExporter",
|
|
12
|
+
"graphexporter",
|
|
13
|
+
"mcpexporter",
|
|
14
|
+
]
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
"""MCP (Model Context Protocol) tool schema, server runner, and configuration exporter."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import sys
|
|
7
|
+
from collections.abc import Callable
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
try:
|
|
12
|
+
from mcp.server.mcpserver import MCPServer
|
|
13
|
+
except ImportError:
|
|
14
|
+
MCPServer = None # type: ignore
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class MCPExporter:
|
|
18
|
+
"""Exports Synapse connectors and tools to standard MCP schemas, client configs, and stdio servers."""
|
|
19
|
+
|
|
20
|
+
@classmethod
|
|
21
|
+
def to_tools_list(cls, source: Any) -> list[dict[str, Any]]:
|
|
22
|
+
"""Extracts standard MCP tool definitions from BaseApp or list of connectors."""
|
|
23
|
+
connectors = cls._resolve_connectors(source)
|
|
24
|
+
tools_list: list[dict[str, Any]] = []
|
|
25
|
+
|
|
26
|
+
for conn in connectors:
|
|
27
|
+
c_name = getattr(conn, "name", "custom_mcp")
|
|
28
|
+
c_desc = getattr(conn, "description", "")
|
|
29
|
+
raw_tools = (
|
|
30
|
+
getattr(conn, "tools", None)
|
|
31
|
+
or getattr(conn, "tools_dict", None)
|
|
32
|
+
or {}
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
if isinstance(raw_tools, dict) and raw_tools:
|
|
36
|
+
for t_name, t_func in raw_tools.items():
|
|
37
|
+
doc = getattr(t_func, "__doc__", "") or f"{t_name} tool provided by {c_name}"
|
|
38
|
+
tools_list.append({
|
|
39
|
+
"name": f"{c_name}_{t_name}" if c_name not in t_name else t_name,
|
|
40
|
+
"description": doc.strip(),
|
|
41
|
+
"server": c_name,
|
|
42
|
+
"inputSchema": {
|
|
43
|
+
"type": "object",
|
|
44
|
+
"properties": {
|
|
45
|
+
"query": {"type": "string", "description": "Search query or input parameter"}
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
})
|
|
49
|
+
elif hasattr(conn, "default_tool") and conn.default_tool:
|
|
50
|
+
tools_list.append({
|
|
51
|
+
"name": f"{c_name}_{conn.default_tool}",
|
|
52
|
+
"description": c_desc or f"Tool for {c_name}",
|
|
53
|
+
"server": c_name,
|
|
54
|
+
"inputSchema": {
|
|
55
|
+
"type": "object",
|
|
56
|
+
"properties": {
|
|
57
|
+
"query": {"type": "string", "description": "Search query or input parameter"}
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
return tools_list
|
|
63
|
+
|
|
64
|
+
@classmethod
|
|
65
|
+
def create_mcp_server(
|
|
66
|
+
cls,
|
|
67
|
+
source: Any,
|
|
68
|
+
name: str | None = None,
|
|
69
|
+
) -> Any:
|
|
70
|
+
"""Instantiates an official `mcp.server.mcpserver.MCPServer` wired with connector tools."""
|
|
71
|
+
if MCPServer is None:
|
|
72
|
+
raise ImportError(
|
|
73
|
+
"The 'mcp' package is required to create an MCP server. "
|
|
74
|
+
"Install it with: pip install 'mcp>=2.3.0'"
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
server_name = name or getattr(source, "title", getattr(source, "name", "synapse"))
|
|
78
|
+
server = MCPServer(name=server_name)
|
|
79
|
+
|
|
80
|
+
connectors = cls._resolve_connectors(source)
|
|
81
|
+
for conn in connectors:
|
|
82
|
+
c_name = getattr(conn, "name", "tool")
|
|
83
|
+
raw_tools = (
|
|
84
|
+
getattr(conn, "tools", None)
|
|
85
|
+
or getattr(conn, "tools_dict", None)
|
|
86
|
+
or {}
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
if isinstance(raw_tools, dict) and raw_tools:
|
|
90
|
+
for t_name, t_func in raw_tools.items():
|
|
91
|
+
doc = getattr(t_func, "__doc__", "") or f"Execute {t_name} via {c_name}"
|
|
92
|
+
t_alias = f"{c_name}_{t_name}" if c_name not in t_name else t_name
|
|
93
|
+
server.add_tool(t_func, name=t_name, description=doc.strip())
|
|
94
|
+
if t_alias != t_name:
|
|
95
|
+
try:
|
|
96
|
+
server.add_tool(t_func, name=t_alias, description=doc.strip())
|
|
97
|
+
except Exception:
|
|
98
|
+
pass # nosec B110
|
|
99
|
+
elif hasattr(conn, "call_tool"):
|
|
100
|
+
default_t = getattr(conn, "default_tool", "search")
|
|
101
|
+
doc = getattr(conn, "description", f"Search {c_name}")
|
|
102
|
+
|
|
103
|
+
def _make_adapter(c_instance: Any, t_action: str, doc_str: str) -> Callable[..., Any]:
|
|
104
|
+
async def dynamic_tool(query: str = "", **kwargs: Any) -> str:
|
|
105
|
+
args = {"query": query, **kwargs} if query else kwargs
|
|
106
|
+
res = await c_instance.call_tool(t_action, args)
|
|
107
|
+
if isinstance(res, (dict, list)):
|
|
108
|
+
return json.dumps(res, indent=2)
|
|
109
|
+
return str(res)
|
|
110
|
+
|
|
111
|
+
dynamic_tool.__name__ = f"{c_instance.name}_{t_action}"
|
|
112
|
+
dynamic_tool.__doc__ = doc_str
|
|
113
|
+
return dynamic_tool
|
|
114
|
+
|
|
115
|
+
adapter = _make_adapter(conn, default_t, doc)
|
|
116
|
+
server.add_tool(adapter, name=f"{c_name}_{default_t}", description=doc)
|
|
117
|
+
|
|
118
|
+
return server
|
|
119
|
+
|
|
120
|
+
@classmethod
|
|
121
|
+
def run_stdio(
|
|
122
|
+
cls,
|
|
123
|
+
source: Any,
|
|
124
|
+
name: str | None = None,
|
|
125
|
+
) -> None:
|
|
126
|
+
"""Runs the MCP server over standard I/O (stdio) for Claude Desktop or Cursor."""
|
|
127
|
+
server = cls.create_mcp_server(source, name=name)
|
|
128
|
+
server.run(transport="stdio")
|
|
129
|
+
|
|
130
|
+
@classmethod
|
|
131
|
+
def to_claude_desktop_config(
|
|
132
|
+
cls,
|
|
133
|
+
source: Any,
|
|
134
|
+
server_name: str = "synapse",
|
|
135
|
+
python_path: str | None = None,
|
|
136
|
+
args: list[str] | None = None,
|
|
137
|
+
host: str = "127.0.0.1",
|
|
138
|
+
port: int = 8000,
|
|
139
|
+
) -> dict[str, Any]:
|
|
140
|
+
"""Generates standard claude_desktop_config.json configuration for Claude Desktop."""
|
|
141
|
+
cmd = python_path or sys.executable or "python3"
|
|
142
|
+
run_args = args or ["-m", "synapse.cli", "mcp"]
|
|
143
|
+
return {
|
|
144
|
+
"mcpServers": {
|
|
145
|
+
server_name: {
|
|
146
|
+
"command": cmd,
|
|
147
|
+
"args": run_args,
|
|
148
|
+
"env": {
|
|
149
|
+
"SYNAPSE_HOST": host,
|
|
150
|
+
"SYNAPSE_PORT": str(port),
|
|
151
|
+
},
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
@classmethod
|
|
157
|
+
def to_json(cls, source: Any, indent: int = 2) -> str:
|
|
158
|
+
"""Serializes all tool schemas and server definitions into a JSON string."""
|
|
159
|
+
payload = {
|
|
160
|
+
"mcpServers": cls.to_claude_desktop_config(source)["mcpServers"],
|
|
161
|
+
"tools": cls.to_tools_list(source),
|
|
162
|
+
}
|
|
163
|
+
return json.dumps(payload, indent=indent)
|
|
164
|
+
|
|
165
|
+
@classmethod
|
|
166
|
+
def save_claude_desktop_config(
|
|
167
|
+
cls,
|
|
168
|
+
source: Any,
|
|
169
|
+
filepath: str | Path = "claude_desktop_config.json",
|
|
170
|
+
server_name: str = "synapse",
|
|
171
|
+
) -> Path:
|
|
172
|
+
"""Writes Claude Desktop configuration file to disk."""
|
|
173
|
+
target = Path(filepath)
|
|
174
|
+
target.parent.mkdir(parents=True, exist_ok=True)
|
|
175
|
+
config = cls.to_claude_desktop_config(source, server_name=server_name)
|
|
176
|
+
target.write_text(json.dumps(config, indent=2), encoding="utf-8")
|
|
177
|
+
return target
|
|
178
|
+
|
|
179
|
+
@staticmethod
|
|
180
|
+
def _resolve_connectors(source: Any) -> list[Any]:
|
|
181
|
+
"""Extracts connector objects from BaseApp, lists, or single connector instances."""
|
|
182
|
+
if hasattr(source, "list_connectors"):
|
|
183
|
+
return source.list_connectors()
|
|
184
|
+
if hasattr(source, "_connectors"):
|
|
185
|
+
return list(source._connectors.values())
|
|
186
|
+
if isinstance(source, (list, tuple)):
|
|
187
|
+
return list(source)
|
|
188
|
+
if isinstance(source, dict):
|
|
189
|
+
return list(source.values())
|
|
190
|
+
if (
|
|
191
|
+
hasattr(source, "call_tool")
|
|
192
|
+
or hasattr(source, "tools_dict")
|
|
193
|
+
or hasattr(source, "default_tool")
|
|
194
|
+
):
|
|
195
|
+
return [source]
|
|
196
|
+
return []
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
# Developer-friendly alias
|
|
200
|
+
mcpexporter = MCPExporter
|
|
201
|
+
|
|
202
|
+
__all__ = ["MCPExporter", "mcpexporter"]
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""Pluggable knowledge graph backends for Synapse."""
|
|
2
|
+
|
|
3
|
+
from synapse.graph.base import BaseGraphStore
|
|
4
|
+
from synapse.graph.memory import InMemoryGraph, in_memory_graph
|
|
5
|
+
from synapse.graph.neo4j_store import Neo4jGraph, neo4j
|
|
6
|
+
from synapse.graph.networkx_store import PALETTE, NetworkXGraph, networkx
|
|
7
|
+
|
|
8
|
+
# Backwards compatible alias
|
|
9
|
+
PersistentGraphStore = NetworkXGraph
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"PALETTE",
|
|
13
|
+
"BaseGraphStore",
|
|
14
|
+
"InMemoryGraph",
|
|
15
|
+
"Neo4jGraph",
|
|
16
|
+
"NetworkXGraph",
|
|
17
|
+
"PersistentGraphStore",
|
|
18
|
+
"in_memory_graph",
|
|
19
|
+
"neo4j",
|
|
20
|
+
"networkx",
|
|
21
|
+
]
|