vouchstone-sdk 1.0.0__tar.gz
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.
- vouchstone_sdk-1.0.0/PKG-INFO +125 -0
- vouchstone_sdk-1.0.0/README.md +82 -0
- vouchstone_sdk-1.0.0/pyproject.toml +47 -0
- vouchstone_sdk-1.0.0/setup.cfg +4 -0
- vouchstone_sdk-1.0.0/setup.py +22 -0
- vouchstone_sdk-1.0.0/vouchstone_sdk/__init__.py +20 -0
- vouchstone_sdk-1.0.0/vouchstone_sdk/agent.py +156 -0
- vouchstone_sdk-1.0.0/vouchstone_sdk/client.py +126 -0
- vouchstone_sdk-1.0.0/vouchstone_sdk/memory.py +243 -0
- vouchstone_sdk-1.0.0/vouchstone_sdk/types.py +78 -0
- vouchstone_sdk-1.0.0/vouchstone_sdk.egg-info/PKG-INFO +125 -0
- vouchstone_sdk-1.0.0/vouchstone_sdk.egg-info/SOURCES.txt +13 -0
- vouchstone_sdk-1.0.0/vouchstone_sdk.egg-info/dependency_links.txt +1 -0
- vouchstone_sdk-1.0.0/vouchstone_sdk.egg-info/requires.txt +24 -0
- vouchstone_sdk-1.0.0/vouchstone_sdk.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: vouchstone-sdk
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Python SDK for Vouchstone — AI engineers you can vouch for
|
|
5
|
+
Author: Vouchstone
|
|
6
|
+
Author-email: "Vouchstone Inc." <gaurav@datapitcher.com>
|
|
7
|
+
License: Proprietary
|
|
8
|
+
Project-URL: Homepage, https://vouchstone.ai
|
|
9
|
+
Project-URL: Repository, https://github.com/GGChamp85/Vouchstone
|
|
10
|
+
Project-URL: Documentation, https://vouchstone.ai/docs
|
|
11
|
+
Keywords: ai,agents,memory,enterprise,vouchstone
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
19
|
+
Requires-Python: >=3.10
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
Requires-Dist: httpx>=0.25.0
|
|
22
|
+
Requires-Dist: pydantic>=2.0.0
|
|
23
|
+
Requires-Dist: openai>=1.0.0
|
|
24
|
+
Requires-Dist: anthropic>=0.18.0
|
|
25
|
+
Requires-Dist: numpy>=1.24.0
|
|
26
|
+
Requires-Dist: aiofiles>=23.0.0
|
|
27
|
+
Provides-Extra: vector
|
|
28
|
+
Requires-Dist: chromadb>=0.4.0; extra == "vector"
|
|
29
|
+
Requires-Dist: qdrant-client>=1.7.0; extra == "vector"
|
|
30
|
+
Provides-Extra: graph
|
|
31
|
+
Requires-Dist: neo4j>=5.0.0; extra == "graph"
|
|
32
|
+
Provides-Extra: all
|
|
33
|
+
Requires-Dist: chromadb>=0.4.0; extra == "all"
|
|
34
|
+
Requires-Dist: qdrant-client>=1.7.0; extra == "all"
|
|
35
|
+
Requires-Dist: neo4j>=5.0.0; extra == "all"
|
|
36
|
+
Provides-Extra: dev
|
|
37
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
38
|
+
Requires-Dist: pytest-asyncio>=0.21; extra == "dev"
|
|
39
|
+
Requires-Dist: black; extra == "dev"
|
|
40
|
+
Requires-Dist: mypy; extra == "dev"
|
|
41
|
+
Dynamic: author
|
|
42
|
+
Dynamic: requires-python
|
|
43
|
+
|
|
44
|
+
# Vouchstone SDK
|
|
45
|
+
|
|
46
|
+
Python SDK for building AI agents on the Vouchstone platform — the first accountable AI engineering firm.
|
|
47
|
+
|
|
48
|
+
## Install
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
pip install vouchstone-sdk
|
|
52
|
+
|
|
53
|
+
# With vector memory (ChromaDB)
|
|
54
|
+
pip install vouchstone-sdk[vector]
|
|
55
|
+
|
|
56
|
+
# With graph memory (Neo4j)
|
|
57
|
+
pip install vouchstone-sdk[graph]
|
|
58
|
+
|
|
59
|
+
# Everything
|
|
60
|
+
pip install vouchstone-sdk[all]
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Quick Start
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
from vouchstone_sdk import Agent, AgentConfig, Message
|
|
67
|
+
|
|
68
|
+
class MyAgent(Agent):
|
|
69
|
+
async def run(self, message: Message, context: dict):
|
|
70
|
+
relevant_docs = context.get("semantic", [])
|
|
71
|
+
history = context.get("episodic", [])
|
|
72
|
+
|
|
73
|
+
response = await self.llm.complete(
|
|
74
|
+
system=f"You are {self.config.name}. Use the context to help the user.",
|
|
75
|
+
messages=[{"role": "user", "content": message.content}],
|
|
76
|
+
context=relevant_docs,
|
|
77
|
+
)
|
|
78
|
+
return AgentResponse(content=response)
|
|
79
|
+
|
|
80
|
+
config = AgentConfig(
|
|
81
|
+
name="Data Migration Agent",
|
|
82
|
+
model="claude-sonnet-4-20250514",
|
|
83
|
+
semantic_memory=True,
|
|
84
|
+
episodic_memory=True,
|
|
85
|
+
procedural_memory=True,
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
agent = MyAgent(config)
|
|
89
|
+
await agent.initialize(vector_db_url="http://chromadb:8000")
|
|
90
|
+
response = await agent.process(Message(content="Migrate our PostgreSQL to Snowflake"))
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## 5-Layer Memory Stack
|
|
94
|
+
|
|
95
|
+
Each agent has access to a biologically-inspired memory architecture:
|
|
96
|
+
|
|
97
|
+
| Layer | Name | Storage | Purpose |
|
|
98
|
+
|-------|------|---------|---------|
|
|
99
|
+
| 1 | Working | Redis | Current turn context (resets per session) |
|
|
100
|
+
| 2 | Episodic | PostgreSQL | Cross-session traces (append-only) |
|
|
101
|
+
| 3 | Semantic | ChromaDB | Entity store (vector search) |
|
|
102
|
+
| 4 | Procedural | Neo4j | Learned skills (versioned DAG) |
|
|
103
|
+
| 5 | Meta | Control Plane | Decay, dedup, compress, forget |
|
|
104
|
+
|
|
105
|
+
## Control Plane Client
|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
from vouchstone_sdk import VouchstoneClient
|
|
109
|
+
|
|
110
|
+
client = VouchstoneClient(
|
|
111
|
+
api_url="https://api.vouchstone.ai",
|
|
112
|
+
api_key="your-api-key",
|
|
113
|
+
tenant_id="your-tenant-id",
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
# List agents
|
|
117
|
+
agents = await client.list_agents()
|
|
118
|
+
|
|
119
|
+
# Execute an agent
|
|
120
|
+
result = await client.execute(agent_id="...", input={"content": "Hello"})
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## License
|
|
124
|
+
|
|
125
|
+
Proprietary — Copyright (c) 2026 Vouchstone Inc. All Rights Reserved.
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# Vouchstone SDK
|
|
2
|
+
|
|
3
|
+
Python SDK for building AI agents on the Vouchstone platform — the first accountable AI engineering firm.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install vouchstone-sdk
|
|
9
|
+
|
|
10
|
+
# With vector memory (ChromaDB)
|
|
11
|
+
pip install vouchstone-sdk[vector]
|
|
12
|
+
|
|
13
|
+
# With graph memory (Neo4j)
|
|
14
|
+
pip install vouchstone-sdk[graph]
|
|
15
|
+
|
|
16
|
+
# Everything
|
|
17
|
+
pip install vouchstone-sdk[all]
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Quick Start
|
|
21
|
+
|
|
22
|
+
```python
|
|
23
|
+
from vouchstone_sdk import Agent, AgentConfig, Message
|
|
24
|
+
|
|
25
|
+
class MyAgent(Agent):
|
|
26
|
+
async def run(self, message: Message, context: dict):
|
|
27
|
+
relevant_docs = context.get("semantic", [])
|
|
28
|
+
history = context.get("episodic", [])
|
|
29
|
+
|
|
30
|
+
response = await self.llm.complete(
|
|
31
|
+
system=f"You are {self.config.name}. Use the context to help the user.",
|
|
32
|
+
messages=[{"role": "user", "content": message.content}],
|
|
33
|
+
context=relevant_docs,
|
|
34
|
+
)
|
|
35
|
+
return AgentResponse(content=response)
|
|
36
|
+
|
|
37
|
+
config = AgentConfig(
|
|
38
|
+
name="Data Migration Agent",
|
|
39
|
+
model="claude-sonnet-4-20250514",
|
|
40
|
+
semantic_memory=True,
|
|
41
|
+
episodic_memory=True,
|
|
42
|
+
procedural_memory=True,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
agent = MyAgent(config)
|
|
46
|
+
await agent.initialize(vector_db_url="http://chromadb:8000")
|
|
47
|
+
response = await agent.process(Message(content="Migrate our PostgreSQL to Snowflake"))
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## 5-Layer Memory Stack
|
|
51
|
+
|
|
52
|
+
Each agent has access to a biologically-inspired memory architecture:
|
|
53
|
+
|
|
54
|
+
| Layer | Name | Storage | Purpose |
|
|
55
|
+
|-------|------|---------|---------|
|
|
56
|
+
| 1 | Working | Redis | Current turn context (resets per session) |
|
|
57
|
+
| 2 | Episodic | PostgreSQL | Cross-session traces (append-only) |
|
|
58
|
+
| 3 | Semantic | ChromaDB | Entity store (vector search) |
|
|
59
|
+
| 4 | Procedural | Neo4j | Learned skills (versioned DAG) |
|
|
60
|
+
| 5 | Meta | Control Plane | Decay, dedup, compress, forget |
|
|
61
|
+
|
|
62
|
+
## Control Plane Client
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
from vouchstone_sdk import VouchstoneClient
|
|
66
|
+
|
|
67
|
+
client = VouchstoneClient(
|
|
68
|
+
api_url="https://api.vouchstone.ai",
|
|
69
|
+
api_key="your-api-key",
|
|
70
|
+
tenant_id="your-tenant-id",
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
# List agents
|
|
74
|
+
agents = await client.list_agents()
|
|
75
|
+
|
|
76
|
+
# Execute an agent
|
|
77
|
+
result = await client.execute(agent_id="...", input={"content": "Hello"})
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## License
|
|
81
|
+
|
|
82
|
+
Proprietary — Copyright (c) 2026 Vouchstone Inc. All Rights Reserved.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "vouchstone-sdk"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
description = "Python SDK for Vouchstone — AI engineers you can vouch for"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = {text = "Proprietary"}
|
|
11
|
+
requires-python = ">=3.10"
|
|
12
|
+
authors = [
|
|
13
|
+
{name = "Vouchstone Inc.", email = "gaurav@datapitcher.com"},
|
|
14
|
+
]
|
|
15
|
+
keywords = ["ai", "agents", "memory", "enterprise", "vouchstone"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 4 - Beta",
|
|
18
|
+
"Intended Audience :: Developers",
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"Programming Language :: Python :: 3.10",
|
|
21
|
+
"Programming Language :: Python :: 3.11",
|
|
22
|
+
"Programming Language :: Python :: 3.12",
|
|
23
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
dependencies = [
|
|
27
|
+
"httpx>=0.25.0",
|
|
28
|
+
"pydantic>=2.0.0",
|
|
29
|
+
"openai>=1.0.0",
|
|
30
|
+
"anthropic>=0.18.0",
|
|
31
|
+
"numpy>=1.24.0",
|
|
32
|
+
"aiofiles>=23.0.0",
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
[project.optional-dependencies]
|
|
36
|
+
vector = ["chromadb>=0.4.0", "qdrant-client>=1.7.0"]
|
|
37
|
+
graph = ["neo4j>=5.0.0"]
|
|
38
|
+
all = ["chromadb>=0.4.0", "qdrant-client>=1.7.0", "neo4j>=5.0.0"]
|
|
39
|
+
dev = ["pytest>=7.0", "pytest-asyncio>=0.21", "black", "mypy"]
|
|
40
|
+
|
|
41
|
+
[project.urls]
|
|
42
|
+
Homepage = "https://vouchstone.ai"
|
|
43
|
+
Repository = "https://github.com/GGChamp85/Vouchstone"
|
|
44
|
+
Documentation = "https://vouchstone.ai/docs"
|
|
45
|
+
|
|
46
|
+
[tool.setuptools.packages.find]
|
|
47
|
+
include = ["vouchstone_sdk*"]
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from setuptools import setup, find_packages
|
|
2
|
+
|
|
3
|
+
setup(
|
|
4
|
+
name="vouchstone-sdk",
|
|
5
|
+
version="1.0.0",
|
|
6
|
+
packages=find_packages(),
|
|
7
|
+
install_requires=[
|
|
8
|
+
"httpx>=0.25.0",
|
|
9
|
+
"pydantic>=2.0.0",
|
|
10
|
+
"openai>=1.0.0",
|
|
11
|
+
"anthropic>=0.18.0",
|
|
12
|
+
"numpy>=1.24.0",
|
|
13
|
+
"aiofiles>=23.0.0",
|
|
14
|
+
],
|
|
15
|
+
extras_require={
|
|
16
|
+
"vector": ["chromadb>=0.4.0", "qdrant-client>=1.7.0"],
|
|
17
|
+
"graph": ["neo4j>=5.0.0"],
|
|
18
|
+
},
|
|
19
|
+
python_requires=">=3.10",
|
|
20
|
+
author="Vouchstone",
|
|
21
|
+
description="Python SDK for Vouchstone AI Agent Platform",
|
|
22
|
+
)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""Vouchstone Python SDK - Build AI Agents with Long-Term Memory"""
|
|
2
|
+
|
|
3
|
+
from .client import VouchstoneClient
|
|
4
|
+
from .agent import Agent, AgentConfig
|
|
5
|
+
from .memory import SemanticMemory, EpisodicMemory, ProceduralMemory, DecisionGraph
|
|
6
|
+
from .types import Message, Decision, MemoryEntry
|
|
7
|
+
|
|
8
|
+
__version__ = "1.0.0"
|
|
9
|
+
__all__ = [
|
|
10
|
+
"VouchstoneClient",
|
|
11
|
+
"Agent",
|
|
12
|
+
"AgentConfig",
|
|
13
|
+
"SemanticMemory",
|
|
14
|
+
"EpisodicMemory",
|
|
15
|
+
"ProceduralMemory",
|
|
16
|
+
"DecisionGraph",
|
|
17
|
+
"Message",
|
|
18
|
+
"Decision",
|
|
19
|
+
"MemoryEntry",
|
|
20
|
+
]
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
"""Agent Base Class with Memory Integration"""
|
|
2
|
+
|
|
3
|
+
from typing import Optional, Dict, Any, List, Callable
|
|
4
|
+
from dataclasses import dataclass, field
|
|
5
|
+
from abc import ABC, abstractmethod
|
|
6
|
+
import asyncio
|
|
7
|
+
|
|
8
|
+
from .memory import SemanticMemory, EpisodicMemory, ProceduralMemory, DecisionGraph
|
|
9
|
+
from .types import Message, AgentResponse
|
|
10
|
+
|
|
11
|
+
@dataclass
|
|
12
|
+
class AgentConfig:
|
|
13
|
+
"""Configuration for an Vouchstone Agent"""
|
|
14
|
+
name: str
|
|
15
|
+
model: str = "gpt-4-turbo-preview"
|
|
16
|
+
temperature: float = 0.7
|
|
17
|
+
max_tokens: int = 4096
|
|
18
|
+
system_prompt: Optional[str] = None
|
|
19
|
+
|
|
20
|
+
# Memory configuration
|
|
21
|
+
semantic_memory: bool = True
|
|
22
|
+
episodic_memory: bool = True
|
|
23
|
+
procedural_memory: bool = True
|
|
24
|
+
decision_graph: bool = True
|
|
25
|
+
|
|
26
|
+
# Memory settings
|
|
27
|
+
embedding_model: str = "text-embedding-3-small"
|
|
28
|
+
memory_retention_days: int = 90
|
|
29
|
+
|
|
30
|
+
# Additional config
|
|
31
|
+
tools: List[Dict[str, Any]] = field(default_factory=list)
|
|
32
|
+
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class Agent(ABC):
|
|
36
|
+
"""Base class for Vouchstone Agents with Long-Term Memory"""
|
|
37
|
+
|
|
38
|
+
def __init__(self, config: AgentConfig):
|
|
39
|
+
self.config = config
|
|
40
|
+
self.semantic_memory: Optional[SemanticMemory] = None
|
|
41
|
+
self.episodic_memory: Optional[EpisodicMemory] = None
|
|
42
|
+
self.procedural_memory: Optional[ProceduralMemory] = None
|
|
43
|
+
self.decision_graph: Optional[DecisionGraph] = None
|
|
44
|
+
|
|
45
|
+
self._tools: Dict[str, Callable] = {}
|
|
46
|
+
self._initialized = False
|
|
47
|
+
|
|
48
|
+
async def initialize(
|
|
49
|
+
self,
|
|
50
|
+
vector_db_url: Optional[str] = None,
|
|
51
|
+
graph_db_url: Optional[str] = None
|
|
52
|
+
):
|
|
53
|
+
"""Initialize memory systems"""
|
|
54
|
+
if self.config.semantic_memory:
|
|
55
|
+
self.semantic_memory = SemanticMemory(
|
|
56
|
+
embedding_model=self.config.embedding_model,
|
|
57
|
+
vector_db_url=vector_db_url
|
|
58
|
+
)
|
|
59
|
+
await self.semantic_memory.initialize()
|
|
60
|
+
|
|
61
|
+
if self.config.episodic_memory:
|
|
62
|
+
self.episodic_memory = EpisodicMemory(
|
|
63
|
+
retention_days=self.config.memory_retention_days
|
|
64
|
+
)
|
|
65
|
+
await self.episodic_memory.initialize()
|
|
66
|
+
|
|
67
|
+
if self.config.procedural_memory:
|
|
68
|
+
self.procedural_memory = ProceduralMemory()
|
|
69
|
+
await self.procedural_memory.initialize()
|
|
70
|
+
|
|
71
|
+
if self.config.decision_graph:
|
|
72
|
+
self.decision_graph = DecisionGraph(graph_db_url=graph_db_url)
|
|
73
|
+
await self.decision_graph.initialize()
|
|
74
|
+
|
|
75
|
+
self._initialized = True
|
|
76
|
+
|
|
77
|
+
def register_tool(self, name: str, func: Callable, description: str):
|
|
78
|
+
"""Register a tool for the agent to use"""
|
|
79
|
+
self._tools[name] = func
|
|
80
|
+
self.config.tools.append({
|
|
81
|
+
"type": "function",
|
|
82
|
+
"function": {
|
|
83
|
+
"name": name,
|
|
84
|
+
"description": description,
|
|
85
|
+
"parameters": {"type": "object", "properties": {}}
|
|
86
|
+
}
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
async def process(self, message: Message) -> AgentResponse:
|
|
90
|
+
"""Process a message and return response"""
|
|
91
|
+
if not self._initialized:
|
|
92
|
+
raise RuntimeError("Agent not initialized. Call initialize() first.")
|
|
93
|
+
|
|
94
|
+
# Retrieve relevant memories
|
|
95
|
+
context = await self._build_context(message)
|
|
96
|
+
|
|
97
|
+
# Run the agent logic
|
|
98
|
+
response = await self.run(message, context)
|
|
99
|
+
|
|
100
|
+
# Store in episodic memory
|
|
101
|
+
if self.episodic_memory:
|
|
102
|
+
await self.episodic_memory.store(message, response)
|
|
103
|
+
|
|
104
|
+
# Update decision graph if enabled
|
|
105
|
+
if self.decision_graph and response.decisions:
|
|
106
|
+
for decision in response.decisions:
|
|
107
|
+
await self.decision_graph.record_decision(decision)
|
|
108
|
+
|
|
109
|
+
return response
|
|
110
|
+
|
|
111
|
+
async def _build_context(self, message: Message) -> Dict[str, Any]:
|
|
112
|
+
"""Build context from memory systems"""
|
|
113
|
+
context = {}
|
|
114
|
+
|
|
115
|
+
if self.semantic_memory:
|
|
116
|
+
relevant_docs = await self.semantic_memory.search(
|
|
117
|
+
message.content,
|
|
118
|
+
top_k=5
|
|
119
|
+
)
|
|
120
|
+
context["semantic"] = relevant_docs
|
|
121
|
+
|
|
122
|
+
if self.episodic_memory:
|
|
123
|
+
recent_episodes = await self.episodic_memory.get_recent(
|
|
124
|
+
limit=10
|
|
125
|
+
)
|
|
126
|
+
context["episodic"] = recent_episodes
|
|
127
|
+
|
|
128
|
+
if self.procedural_memory:
|
|
129
|
+
relevant_procedures = await self.procedural_memory.get_relevant(
|
|
130
|
+
message.content
|
|
131
|
+
)
|
|
132
|
+
context["procedural"] = relevant_procedures
|
|
133
|
+
|
|
134
|
+
if self.decision_graph:
|
|
135
|
+
decision_context = await self.decision_graph.get_context(
|
|
136
|
+
message.content
|
|
137
|
+
)
|
|
138
|
+
context["decision_graph"] = decision_context
|
|
139
|
+
|
|
140
|
+
return context
|
|
141
|
+
|
|
142
|
+
@abstractmethod
|
|
143
|
+
async def run(self, message: Message, context: Dict[str, Any]) -> AgentResponse:
|
|
144
|
+
"""Implement agent logic - must be overridden"""
|
|
145
|
+
pass
|
|
146
|
+
|
|
147
|
+
async def close(self):
|
|
148
|
+
"""Cleanup resources"""
|
|
149
|
+
if self.semantic_memory:
|
|
150
|
+
await self.semantic_memory.close()
|
|
151
|
+
if self.episodic_memory:
|
|
152
|
+
await self.episodic_memory.close()
|
|
153
|
+
if self.procedural_memory:
|
|
154
|
+
await self.procedural_memory.close()
|
|
155
|
+
if self.decision_graph:
|
|
156
|
+
await self.decision_graph.close()
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"""Vouchstone SDK Client - Connection to Control Plane"""
|
|
2
|
+
|
|
3
|
+
import httpx
|
|
4
|
+
from typing import Optional, Dict, Any, List
|
|
5
|
+
from .types import AgentDefinition
|
|
6
|
+
|
|
7
|
+
class VouchstoneClient:
|
|
8
|
+
"""Client for communicating with Vouchstone Control Plane"""
|
|
9
|
+
|
|
10
|
+
def __init__(
|
|
11
|
+
self,
|
|
12
|
+
api_key: str,
|
|
13
|
+
control_plane_url: str = "https://api.vouchstone.ai",
|
|
14
|
+
tenant_id: Optional[str] = None
|
|
15
|
+
):
|
|
16
|
+
self.api_key = api_key
|
|
17
|
+
self.base_url = control_plane_url.rstrip("/")
|
|
18
|
+
self.tenant_id = tenant_id
|
|
19
|
+
self._client = httpx.AsyncClient(
|
|
20
|
+
base_url=self.base_url,
|
|
21
|
+
headers={
|
|
22
|
+
"Authorization": f"Bearer {api_key}",
|
|
23
|
+
"X-Tenant-ID": tenant_id or "",
|
|
24
|
+
},
|
|
25
|
+
timeout=30.0
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
async def get_agent(self, agent_id: str) -> AgentDefinition:
|
|
29
|
+
"""Fetch agent definition from control plane"""
|
|
30
|
+
response = await self._client.get(f"/api/v1/agents/{agent_id}")
|
|
31
|
+
response.raise_for_status()
|
|
32
|
+
return AgentDefinition(**response.json())
|
|
33
|
+
|
|
34
|
+
async def list_agents(self) -> List[AgentDefinition]:
|
|
35
|
+
"""List all agents for tenant"""
|
|
36
|
+
response = await self._client.get("/api/v1/agents")
|
|
37
|
+
response.raise_for_status()
|
|
38
|
+
return [AgentDefinition(**a) for a in response.json()["agents"]]
|
|
39
|
+
|
|
40
|
+
async def report_metrics(self, metrics: Dict[str, Any]) -> None:
|
|
41
|
+
"""Report metrics back to control plane"""
|
|
42
|
+
await self._client.post("/api/v1/webhooks/data-plane/metrics", json=metrics)
|
|
43
|
+
|
|
44
|
+
async def report_status(self, agent_id: str, status: Dict[str, Any]) -> None:
|
|
45
|
+
"""Report agent status to control plane"""
|
|
46
|
+
await self._client.post(
|
|
47
|
+
"/api/v1/webhooks/data-plane/status",
|
|
48
|
+
json={"agent_id": agent_id, **status}
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
# ============================================================
|
|
52
|
+
# Slice 1 — data plane sync helpers
|
|
53
|
+
# ============================================================
|
|
54
|
+
|
|
55
|
+
async def heartbeat(
|
|
56
|
+
self,
|
|
57
|
+
runtime_version: str,
|
|
58
|
+
pod_count: int,
|
|
59
|
+
queue_depth: int,
|
|
60
|
+
last_seq: int,
|
|
61
|
+
runtime_token: Optional[str] = None,
|
|
62
|
+
) -> Dict[str, Any]:
|
|
63
|
+
"""POST /api/v1/data-plane/heartbeat — returns heartbeat response.
|
|
64
|
+
|
|
65
|
+
``runtime_token`` is required (provide either here or via
|
|
66
|
+
``X-Runtime-Token`` on the client).
|
|
67
|
+
"""
|
|
68
|
+
headers = {"X-Runtime-Token": runtime_token or ""}
|
|
69
|
+
body = {
|
|
70
|
+
"tenant_id": self.tenant_id,
|
|
71
|
+
"runtime_version": runtime_version,
|
|
72
|
+
"pod_count": pod_count,
|
|
73
|
+
"queue_depth": queue_depth,
|
|
74
|
+
"last_ledger_seq_forwarded": last_seq,
|
|
75
|
+
}
|
|
76
|
+
resp = await self._client.post(
|
|
77
|
+
"/api/v1/data-plane/heartbeat", json=body, headers=headers,
|
|
78
|
+
)
|
|
79
|
+
resp.raise_for_status()
|
|
80
|
+
return resp.json()
|
|
81
|
+
|
|
82
|
+
async def replay_ledger(
|
|
83
|
+
self,
|
|
84
|
+
entries: List[Dict[str, Any]],
|
|
85
|
+
runtime_token: Optional[str] = None,
|
|
86
|
+
) -> Dict[str, Any]:
|
|
87
|
+
"""POST /api/v1/data-plane/ledger/replay — bulk replay."""
|
|
88
|
+
headers = {"X-Runtime-Token": runtime_token or ""}
|
|
89
|
+
body = {"tenant_id": self.tenant_id, "entries": entries}
|
|
90
|
+
resp = await self._client.post(
|
|
91
|
+
"/api/v1/data-plane/ledger/replay", json=body, headers=headers,
|
|
92
|
+
)
|
|
93
|
+
resp.raise_for_status()
|
|
94
|
+
return resp.json()
|
|
95
|
+
|
|
96
|
+
async def fetch_agent_spec(
|
|
97
|
+
self,
|
|
98
|
+
agent_id: Optional[str] = None,
|
|
99
|
+
since: float = 0.0,
|
|
100
|
+
runtime_token: Optional[str] = None,
|
|
101
|
+
) -> Dict[str, Any]:
|
|
102
|
+
"""GET /api/v1/data-plane/agents/spec?since=&tenant_id=.
|
|
103
|
+
|
|
104
|
+
``agent_id`` is optional — when supplied the response is filtered
|
|
105
|
+
to that agent locally.
|
|
106
|
+
"""
|
|
107
|
+
headers = {"X-Runtime-Token": runtime_token or ""}
|
|
108
|
+
params = {"tenant_id": self.tenant_id or "", "since": since}
|
|
109
|
+
resp = await self._client.get(
|
|
110
|
+
"/api/v1/data-plane/agents/spec", params=params, headers=headers,
|
|
111
|
+
)
|
|
112
|
+
resp.raise_for_status()
|
|
113
|
+
data = resp.json()
|
|
114
|
+
if agent_id:
|
|
115
|
+
data["agents"] = [a for a in data.get("agents", []) if str(a.get("id")) == str(agent_id)]
|
|
116
|
+
return data
|
|
117
|
+
|
|
118
|
+
async def close(self):
|
|
119
|
+
"""Close the HTTP client"""
|
|
120
|
+
await self._client.aclose()
|
|
121
|
+
|
|
122
|
+
async def __aenter__(self):
|
|
123
|
+
return self
|
|
124
|
+
|
|
125
|
+
async def __aexit__(self, *args):
|
|
126
|
+
await self.close()
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
"""Memory Systems for Vouchstone Agents"""
|
|
2
|
+
|
|
3
|
+
from typing import Optional, List, Dict, Any
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from datetime import datetime, timedelta
|
|
6
|
+
import json
|
|
7
|
+
import hashlib
|
|
8
|
+
|
|
9
|
+
from .types import MemoryEntry, Decision, Episode
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class SemanticMemory:
|
|
13
|
+
"""Vector-based semantic memory for knowledge retrieval"""
|
|
14
|
+
|
|
15
|
+
def __init__(
|
|
16
|
+
self,
|
|
17
|
+
embedding_model: str = "text-embedding-3-small",
|
|
18
|
+
vector_db_url: Optional[str] = None,
|
|
19
|
+
collection_name: str = "semantic_memory"
|
|
20
|
+
):
|
|
21
|
+
self.embedding_model = embedding_model
|
|
22
|
+
self.vector_db_url = vector_db_url
|
|
23
|
+
self.collection_name = collection_name
|
|
24
|
+
self._client = None
|
|
25
|
+
self._embedder = None
|
|
26
|
+
|
|
27
|
+
async def initialize(self):
|
|
28
|
+
"""Initialize vector DB connection"""
|
|
29
|
+
if self.vector_db_url:
|
|
30
|
+
# Use Qdrant or ChromaDB based on URL
|
|
31
|
+
if "qdrant" in self.vector_db_url:
|
|
32
|
+
from qdrant_client import QdrantClient
|
|
33
|
+
self._client = QdrantClient(url=self.vector_db_url)
|
|
34
|
+
else:
|
|
35
|
+
import chromadb
|
|
36
|
+
self._client = chromadb.HttpClient(host=self.vector_db_url)
|
|
37
|
+
else:
|
|
38
|
+
# Use in-memory ChromaDB for development
|
|
39
|
+
import chromadb
|
|
40
|
+
self._client = chromadb.Client()
|
|
41
|
+
|
|
42
|
+
async def store(self, text: str, metadata: Dict[str, Any] = None) -> str:
|
|
43
|
+
"""Store text with embeddings"""
|
|
44
|
+
embedding = await self._get_embedding(text)
|
|
45
|
+
doc_id = hashlib.md5(text.encode()).hexdigest()
|
|
46
|
+
|
|
47
|
+
# Store in vector DB
|
|
48
|
+
collection = self._client.get_or_create_collection(self.collection_name)
|
|
49
|
+
collection.add(
|
|
50
|
+
ids=[doc_id],
|
|
51
|
+
embeddings=[embedding],
|
|
52
|
+
documents=[text],
|
|
53
|
+
metadatas=[metadata or {}]
|
|
54
|
+
)
|
|
55
|
+
return doc_id
|
|
56
|
+
|
|
57
|
+
async def search(self, query: str, top_k: int = 5) -> List[MemoryEntry]:
|
|
58
|
+
"""Search for relevant memories"""
|
|
59
|
+
embedding = await self._get_embedding(query)
|
|
60
|
+
|
|
61
|
+
collection = self._client.get_or_create_collection(self.collection_name)
|
|
62
|
+
results = collection.query(
|
|
63
|
+
query_embeddings=[embedding],
|
|
64
|
+
n_results=top_k
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
entries = []
|
|
68
|
+
for i, doc in enumerate(results["documents"][0]):
|
|
69
|
+
entries.append(MemoryEntry(
|
|
70
|
+
id=results["ids"][0][i],
|
|
71
|
+
content=doc,
|
|
72
|
+
metadata=results["metadatas"][0][i] if results["metadatas"] else {},
|
|
73
|
+
score=results["distances"][0][i] if results.get("distances") else 0.0
|
|
74
|
+
))
|
|
75
|
+
return entries
|
|
76
|
+
|
|
77
|
+
async def _get_embedding(self, text: str) -> List[float]:
|
|
78
|
+
"""Get embedding for text"""
|
|
79
|
+
import openai
|
|
80
|
+
client = openai.AsyncOpenAI()
|
|
81
|
+
response = await client.embeddings.create(
|
|
82
|
+
model=self.embedding_model,
|
|
83
|
+
input=text
|
|
84
|
+
)
|
|
85
|
+
return response.data[0].embedding
|
|
86
|
+
|
|
87
|
+
async def close(self):
|
|
88
|
+
pass
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class EpisodicMemory:
|
|
92
|
+
"""Time-based episodic memory for conversation history"""
|
|
93
|
+
|
|
94
|
+
def __init__(self, retention_days: int = 90):
|
|
95
|
+
self.retention_days = retention_days
|
|
96
|
+
self._episodes: List[Episode] = []
|
|
97
|
+
|
|
98
|
+
async def initialize(self):
|
|
99
|
+
"""Initialize episodic memory storage"""
|
|
100
|
+
pass
|
|
101
|
+
|
|
102
|
+
async def store(self, message: Any, response: Any) -> str:
|
|
103
|
+
"""Store an episode (message-response pair)"""
|
|
104
|
+
episode = Episode(
|
|
105
|
+
id=f"ep_{datetime.utcnow().timestamp()}",
|
|
106
|
+
timestamp=datetime.utcnow(),
|
|
107
|
+
message=str(message),
|
|
108
|
+
response=str(response),
|
|
109
|
+
metadata={}
|
|
110
|
+
)
|
|
111
|
+
self._episodes.append(episode)
|
|
112
|
+
return episode.id
|
|
113
|
+
|
|
114
|
+
async def get_recent(self, limit: int = 10) -> List[Episode]:
|
|
115
|
+
"""Get recent episodes"""
|
|
116
|
+
cutoff = datetime.utcnow() - timedelta(days=self.retention_days)
|
|
117
|
+
recent = [e for e in self._episodes if e.timestamp > cutoff]
|
|
118
|
+
return sorted(recent, key=lambda x: x.timestamp, reverse=True)[:limit]
|
|
119
|
+
|
|
120
|
+
async def search(self, query: str, limit: int = 5) -> List[Episode]:
|
|
121
|
+
"""Search episodes by content"""
|
|
122
|
+
matches = []
|
|
123
|
+
query_lower = query.lower()
|
|
124
|
+
for episode in self._episodes:
|
|
125
|
+
if query_lower in episode.message.lower() or query_lower in episode.response.lower():
|
|
126
|
+
matches.append(episode)
|
|
127
|
+
return matches[:limit]
|
|
128
|
+
|
|
129
|
+
async def close(self):
|
|
130
|
+
pass
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
class ProceduralMemory:
|
|
134
|
+
"""Memory for learned procedures and skills"""
|
|
135
|
+
|
|
136
|
+
def __init__(self):
|
|
137
|
+
self._procedures: Dict[str, Dict[str, Any]] = {}
|
|
138
|
+
|
|
139
|
+
async def initialize(self):
|
|
140
|
+
pass
|
|
141
|
+
|
|
142
|
+
async def store_procedure(
|
|
143
|
+
self,
|
|
144
|
+
name: str,
|
|
145
|
+
steps: List[str],
|
|
146
|
+
conditions: Dict[str, Any] = None
|
|
147
|
+
) -> str:
|
|
148
|
+
"""Store a procedure"""
|
|
149
|
+
self._procedures[name] = {
|
|
150
|
+
"name": name,
|
|
151
|
+
"steps": steps,
|
|
152
|
+
"conditions": conditions or {},
|
|
153
|
+
"created_at": datetime.utcnow().isoformat()
|
|
154
|
+
}
|
|
155
|
+
return name
|
|
156
|
+
|
|
157
|
+
async def get_relevant(self, context: str) -> List[Dict[str, Any]]:
|
|
158
|
+
"""Get procedures relevant to context"""
|
|
159
|
+
# Simple keyword matching - could use embeddings
|
|
160
|
+
relevant = []
|
|
161
|
+
context_lower = context.lower()
|
|
162
|
+
for name, proc in self._procedures.items():
|
|
163
|
+
if name.lower() in context_lower:
|
|
164
|
+
relevant.append(proc)
|
|
165
|
+
return relevant
|
|
166
|
+
|
|
167
|
+
async def execute_procedure(self, name: str, inputs: Dict[str, Any]) -> Any:
|
|
168
|
+
"""Execute a stored procedure"""
|
|
169
|
+
if name not in self._procedures:
|
|
170
|
+
raise ValueError(f"Procedure {name} not found")
|
|
171
|
+
|
|
172
|
+
proc = self._procedures[name]
|
|
173
|
+
# Return steps for now - actual execution would depend on step types
|
|
174
|
+
return {"procedure": name, "steps": proc["steps"], "inputs": inputs}
|
|
175
|
+
|
|
176
|
+
async def close(self):
|
|
177
|
+
pass
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
class DecisionGraph:
|
|
181
|
+
"""Graph-based decision memory with reasoning chains"""
|
|
182
|
+
|
|
183
|
+
def __init__(self, graph_db_url: Optional[str] = None):
|
|
184
|
+
self.graph_db_url = graph_db_url
|
|
185
|
+
self._driver = None
|
|
186
|
+
self._decisions: List[Decision] = []
|
|
187
|
+
|
|
188
|
+
async def initialize(self):
|
|
189
|
+
"""Initialize graph database connection"""
|
|
190
|
+
if self.graph_db_url:
|
|
191
|
+
from neo4j import AsyncGraphDatabase
|
|
192
|
+
self._driver = AsyncGraphDatabase.driver(self.graph_db_url)
|
|
193
|
+
|
|
194
|
+
async def record_decision(self, decision: Decision) -> str:
|
|
195
|
+
"""Record a decision in the graph"""
|
|
196
|
+
self._decisions.append(decision)
|
|
197
|
+
|
|
198
|
+
if self._driver:
|
|
199
|
+
async with self._driver.session() as session:
|
|
200
|
+
await session.run(
|
|
201
|
+
"""
|
|
202
|
+
CREATE (d:Decision {
|
|
203
|
+
id: $id,
|
|
204
|
+
question: $question,
|
|
205
|
+
answer: $answer,
|
|
206
|
+
confidence: $confidence,
|
|
207
|
+
reasoning: $reasoning,
|
|
208
|
+
timestamp: datetime()
|
|
209
|
+
})
|
|
210
|
+
""",
|
|
211
|
+
id=decision.id,
|
|
212
|
+
question=decision.question,
|
|
213
|
+
answer=decision.answer,
|
|
214
|
+
confidence=decision.confidence,
|
|
215
|
+
reasoning=json.dumps(decision.reasoning)
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
return decision.id
|
|
219
|
+
|
|
220
|
+
async def get_context(self, query: str) -> Dict[str, Any]:
|
|
221
|
+
"""Get decision context for a query"""
|
|
222
|
+
# Find related decisions
|
|
223
|
+
related = []
|
|
224
|
+
query_lower = query.lower()
|
|
225
|
+
for decision in self._decisions:
|
|
226
|
+
if query_lower in decision.question.lower():
|
|
227
|
+
related.append(decision)
|
|
228
|
+
|
|
229
|
+
return {
|
|
230
|
+
"related_decisions": related[-5:],
|
|
231
|
+
"total_decisions": len(self._decisions)
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
async def get_reasoning_chain(self, decision_id: str) -> List[Dict[str, Any]]:
|
|
235
|
+
"""Get the reasoning chain for a decision"""
|
|
236
|
+
for decision in self._decisions:
|
|
237
|
+
if decision.id == decision_id:
|
|
238
|
+
return decision.reasoning
|
|
239
|
+
return []
|
|
240
|
+
|
|
241
|
+
async def close(self):
|
|
242
|
+
if self._driver:
|
|
243
|
+
await self._driver.close()
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"""Type definitions for Vouchstone SDK"""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from typing import Optional, List, Dict, Any
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
from enum import Enum
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass
|
|
10
|
+
class Message:
|
|
11
|
+
"""Input message to an agent"""
|
|
12
|
+
content: str
|
|
13
|
+
role: str = "user"
|
|
14
|
+
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
15
|
+
timestamp: datetime = field(default_factory=datetime.utcnow)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass
|
|
19
|
+
class Decision:
|
|
20
|
+
"""A decision made by an agent"""
|
|
21
|
+
id: str
|
|
22
|
+
question: str
|
|
23
|
+
answer: str
|
|
24
|
+
confidence: float
|
|
25
|
+
reasoning: List[Dict[str, Any]] = field(default_factory=list)
|
|
26
|
+
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
27
|
+
timestamp: datetime = field(default_factory=datetime.utcnow)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass
|
|
31
|
+
class AgentResponse:
|
|
32
|
+
"""Response from an agent"""
|
|
33
|
+
content: str
|
|
34
|
+
decisions: List[Decision] = field(default_factory=list)
|
|
35
|
+
tool_calls: List[Dict[str, Any]] = field(default_factory=list)
|
|
36
|
+
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
37
|
+
usage: Dict[str, int] = field(default_factory=dict)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@dataclass
|
|
41
|
+
class MemoryEntry:
|
|
42
|
+
"""An entry from memory search"""
|
|
43
|
+
id: str
|
|
44
|
+
content: str
|
|
45
|
+
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
46
|
+
score: float = 0.0
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@dataclass
|
|
50
|
+
class Episode:
|
|
51
|
+
"""An episodic memory entry"""
|
|
52
|
+
id: str
|
|
53
|
+
timestamp: datetime
|
|
54
|
+
message: str
|
|
55
|
+
response: str
|
|
56
|
+
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@dataclass
|
|
60
|
+
class AgentDefinition:
|
|
61
|
+
"""Agent definition from control plane"""
|
|
62
|
+
id: str
|
|
63
|
+
name: str
|
|
64
|
+
slug: str
|
|
65
|
+
agent_type: str
|
|
66
|
+
status: str
|
|
67
|
+
memory_config: Dict[str, Any]
|
|
68
|
+
config_schema: Dict[str, Any]
|
|
69
|
+
code_s3_key: Optional[str] = None
|
|
70
|
+
description: Optional[str] = None
|
|
71
|
+
tags: List[str] = field(default_factory=list)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class AgentStatus(str, Enum):
|
|
75
|
+
DRAFT = "draft"
|
|
76
|
+
ACTIVE = "active"
|
|
77
|
+
PAUSED = "paused"
|
|
78
|
+
ARCHIVED = "archived"
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: vouchstone-sdk
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Python SDK for Vouchstone — AI engineers you can vouch for
|
|
5
|
+
Author: Vouchstone
|
|
6
|
+
Author-email: "Vouchstone Inc." <gaurav@datapitcher.com>
|
|
7
|
+
License: Proprietary
|
|
8
|
+
Project-URL: Homepage, https://vouchstone.ai
|
|
9
|
+
Project-URL: Repository, https://github.com/GGChamp85/Vouchstone
|
|
10
|
+
Project-URL: Documentation, https://vouchstone.ai/docs
|
|
11
|
+
Keywords: ai,agents,memory,enterprise,vouchstone
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
19
|
+
Requires-Python: >=3.10
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
Requires-Dist: httpx>=0.25.0
|
|
22
|
+
Requires-Dist: pydantic>=2.0.0
|
|
23
|
+
Requires-Dist: openai>=1.0.0
|
|
24
|
+
Requires-Dist: anthropic>=0.18.0
|
|
25
|
+
Requires-Dist: numpy>=1.24.0
|
|
26
|
+
Requires-Dist: aiofiles>=23.0.0
|
|
27
|
+
Provides-Extra: vector
|
|
28
|
+
Requires-Dist: chromadb>=0.4.0; extra == "vector"
|
|
29
|
+
Requires-Dist: qdrant-client>=1.7.0; extra == "vector"
|
|
30
|
+
Provides-Extra: graph
|
|
31
|
+
Requires-Dist: neo4j>=5.0.0; extra == "graph"
|
|
32
|
+
Provides-Extra: all
|
|
33
|
+
Requires-Dist: chromadb>=0.4.0; extra == "all"
|
|
34
|
+
Requires-Dist: qdrant-client>=1.7.0; extra == "all"
|
|
35
|
+
Requires-Dist: neo4j>=5.0.0; extra == "all"
|
|
36
|
+
Provides-Extra: dev
|
|
37
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
38
|
+
Requires-Dist: pytest-asyncio>=0.21; extra == "dev"
|
|
39
|
+
Requires-Dist: black; extra == "dev"
|
|
40
|
+
Requires-Dist: mypy; extra == "dev"
|
|
41
|
+
Dynamic: author
|
|
42
|
+
Dynamic: requires-python
|
|
43
|
+
|
|
44
|
+
# Vouchstone SDK
|
|
45
|
+
|
|
46
|
+
Python SDK for building AI agents on the Vouchstone platform — the first accountable AI engineering firm.
|
|
47
|
+
|
|
48
|
+
## Install
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
pip install vouchstone-sdk
|
|
52
|
+
|
|
53
|
+
# With vector memory (ChromaDB)
|
|
54
|
+
pip install vouchstone-sdk[vector]
|
|
55
|
+
|
|
56
|
+
# With graph memory (Neo4j)
|
|
57
|
+
pip install vouchstone-sdk[graph]
|
|
58
|
+
|
|
59
|
+
# Everything
|
|
60
|
+
pip install vouchstone-sdk[all]
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Quick Start
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
from vouchstone_sdk import Agent, AgentConfig, Message
|
|
67
|
+
|
|
68
|
+
class MyAgent(Agent):
|
|
69
|
+
async def run(self, message: Message, context: dict):
|
|
70
|
+
relevant_docs = context.get("semantic", [])
|
|
71
|
+
history = context.get("episodic", [])
|
|
72
|
+
|
|
73
|
+
response = await self.llm.complete(
|
|
74
|
+
system=f"You are {self.config.name}. Use the context to help the user.",
|
|
75
|
+
messages=[{"role": "user", "content": message.content}],
|
|
76
|
+
context=relevant_docs,
|
|
77
|
+
)
|
|
78
|
+
return AgentResponse(content=response)
|
|
79
|
+
|
|
80
|
+
config = AgentConfig(
|
|
81
|
+
name="Data Migration Agent",
|
|
82
|
+
model="claude-sonnet-4-20250514",
|
|
83
|
+
semantic_memory=True,
|
|
84
|
+
episodic_memory=True,
|
|
85
|
+
procedural_memory=True,
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
agent = MyAgent(config)
|
|
89
|
+
await agent.initialize(vector_db_url="http://chromadb:8000")
|
|
90
|
+
response = await agent.process(Message(content="Migrate our PostgreSQL to Snowflake"))
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## 5-Layer Memory Stack
|
|
94
|
+
|
|
95
|
+
Each agent has access to a biologically-inspired memory architecture:
|
|
96
|
+
|
|
97
|
+
| Layer | Name | Storage | Purpose |
|
|
98
|
+
|-------|------|---------|---------|
|
|
99
|
+
| 1 | Working | Redis | Current turn context (resets per session) |
|
|
100
|
+
| 2 | Episodic | PostgreSQL | Cross-session traces (append-only) |
|
|
101
|
+
| 3 | Semantic | ChromaDB | Entity store (vector search) |
|
|
102
|
+
| 4 | Procedural | Neo4j | Learned skills (versioned DAG) |
|
|
103
|
+
| 5 | Meta | Control Plane | Decay, dedup, compress, forget |
|
|
104
|
+
|
|
105
|
+
## Control Plane Client
|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
from vouchstone_sdk import VouchstoneClient
|
|
109
|
+
|
|
110
|
+
client = VouchstoneClient(
|
|
111
|
+
api_url="https://api.vouchstone.ai",
|
|
112
|
+
api_key="your-api-key",
|
|
113
|
+
tenant_id="your-tenant-id",
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
# List agents
|
|
117
|
+
agents = await client.list_agents()
|
|
118
|
+
|
|
119
|
+
# Execute an agent
|
|
120
|
+
result = await client.execute(agent_id="...", input={"content": "Hello"})
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## License
|
|
124
|
+
|
|
125
|
+
Proprietary — Copyright (c) 2026 Vouchstone Inc. All Rights Reserved.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
setup.py
|
|
4
|
+
vouchstone_sdk/__init__.py
|
|
5
|
+
vouchstone_sdk/agent.py
|
|
6
|
+
vouchstone_sdk/client.py
|
|
7
|
+
vouchstone_sdk/memory.py
|
|
8
|
+
vouchstone_sdk/types.py
|
|
9
|
+
vouchstone_sdk.egg-info/PKG-INFO
|
|
10
|
+
vouchstone_sdk.egg-info/SOURCES.txt
|
|
11
|
+
vouchstone_sdk.egg-info/dependency_links.txt
|
|
12
|
+
vouchstone_sdk.egg-info/requires.txt
|
|
13
|
+
vouchstone_sdk.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
httpx>=0.25.0
|
|
2
|
+
pydantic>=2.0.0
|
|
3
|
+
openai>=1.0.0
|
|
4
|
+
anthropic>=0.18.0
|
|
5
|
+
numpy>=1.24.0
|
|
6
|
+
aiofiles>=23.0.0
|
|
7
|
+
|
|
8
|
+
[all]
|
|
9
|
+
chromadb>=0.4.0
|
|
10
|
+
qdrant-client>=1.7.0
|
|
11
|
+
neo4j>=5.0.0
|
|
12
|
+
|
|
13
|
+
[dev]
|
|
14
|
+
pytest>=7.0
|
|
15
|
+
pytest-asyncio>=0.21
|
|
16
|
+
black
|
|
17
|
+
mypy
|
|
18
|
+
|
|
19
|
+
[graph]
|
|
20
|
+
neo4j>=5.0.0
|
|
21
|
+
|
|
22
|
+
[vector]
|
|
23
|
+
chromadb>=0.4.0
|
|
24
|
+
qdrant-client>=1.7.0
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
vouchstone_sdk
|