vectorsmith 0.1.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.
- vectorsmith-0.1.0/.gitignore +44 -0
- vectorsmith-0.1.0/PKG-INFO +77 -0
- vectorsmith-0.1.0/README.md +15 -0
- vectorsmith-0.1.0/hatch_build.py +26 -0
- vectorsmith-0.1.0/pyproject.toml +65 -0
- vectorsmith-0.1.0/vectorsmith/__init__.py +31 -0
- vectorsmith-0.1.0/vectorsmith/anthropic.py +42 -0
- vectorsmith-0.1.0/vectorsmith/langchain.py +5 -0
- vectorsmith-0.1.0/vectorsmith/langchain_tools.py +110 -0
- vectorsmith-0.1.0/vectorsmith/langgraph.py +5 -0
- vectorsmith-0.1.0/vectorsmith/openai_agents.py +69 -0
- vectorsmith-0.1.0/vectorsmith/runtime.py +139 -0
- vectorsmith-0.1.0/vectorsmith_cli/__init__.py +1 -0
- vectorsmith-0.1.0/vectorsmith_cli/drafts_cmd.py +71 -0
- vectorsmith-0.1.0/vectorsmith_cli/http/__init__.py +1 -0
- vectorsmith-0.1.0/vectorsmith_cli/http/app.py +136 -0
- vectorsmith-0.1.0/vectorsmith_cli/http/builtin_oauth/__init__.py +1 -0
- vectorsmith-0.1.0/vectorsmith_cli/http/builtin_oauth/pages.py +16 -0
- vectorsmith-0.1.0/vectorsmith_cli/http/builtin_oauth/server.py +123 -0
- vectorsmith-0.1.0/vectorsmith_cli/http/builtin_oauth/store.py +198 -0
- vectorsmith-0.1.0/vectorsmith_cli/identity.py +4 -0
- vectorsmith-0.1.0/vectorsmith_cli/init_cmd.py +100 -0
- vectorsmith-0.1.0/vectorsmith_cli/introspect_cmd.py +46 -0
- vectorsmith-0.1.0/vectorsmith_cli/main.py +152 -0
- vectorsmith-0.1.0/vectorsmith_cli/serve_common.py +274 -0
- vectorsmith-0.1.0/vectorsmith_cli/serve_http.py +65 -0
- vectorsmith-0.1.0/vectorsmith_cli/serve_stdio.py +219 -0
- vectorsmith-0.1.0/vectorsmith_cli/stdio_guard.py +41 -0
- vectorsmith-0.1.0/vectorsmith_cli/test_cmd.py +68 -0
- vectorsmith-0.1.0/vectorsmith_cli/validate_cmd.py +68 -0
- vectorsmith-0.1.0/vectorsmith_core/__init__.py +26 -0
- vectorsmith-0.1.0/vectorsmith_core/adapters/__init__.py +1 -0
- vectorsmith-0.1.0/vectorsmith_core/adapters/base.py +62 -0
- vectorsmith-0.1.0/vectorsmith_core/adapters/capabilities.py +124 -0
- vectorsmith-0.1.0/vectorsmith_core/adapters/chroma.py +128 -0
- vectorsmith-0.1.0/vectorsmith_core/adapters/milvus.py +129 -0
- vectorsmith-0.1.0/vectorsmith_core/adapters/pgvector.py +186 -0
- vectorsmith-0.1.0/vectorsmith_core/adapters/pinecone.py +106 -0
- vectorsmith-0.1.0/vectorsmith_core/adapters/qdrant.py +270 -0
- vectorsmith-0.1.0/vectorsmith_core/adapters/registry.py +1 -0
- vectorsmith-0.1.0/vectorsmith_core/adapters/weaviate.py +107 -0
- vectorsmith-0.1.0/vectorsmith_core/api.py +220 -0
- vectorsmith-0.1.0/vectorsmith_core/compilepkg/__init__.py +1 -0
- vectorsmith-0.1.0/vectorsmith_core/compilepkg/builtins.py +157 -0
- vectorsmith-0.1.0/vectorsmith_core/compilepkg/compiler.py +148 -0
- vectorsmith-0.1.0/vectorsmith_core/compilepkg/drafts.py +113 -0
- vectorsmith-0.1.0/vectorsmith_core/compilepkg/validator.py +287 -0
- vectorsmith-0.1.0/vectorsmith_core/embed/__init__.py +1 -0
- vectorsmith-0.1.0/vectorsmith_core/embed/models.py +7 -0
- vectorsmith-0.1.0/vectorsmith_core/embed/provider.py +55 -0
- vectorsmith-0.1.0/vectorsmith_core/errors.py +98 -0
- vectorsmith-0.1.0/vectorsmith_core/execute/__init__.py +1 -0
- vectorsmith-0.1.0/vectorsmith_core/execute/engine.py +236 -0
- vectorsmith-0.1.0/vectorsmith_core/execute/expr/__init__.py +6 -0
- vectorsmith-0.1.0/vectorsmith_core/execute/expr/eval_polars.py +87 -0
- vectorsmith-0.1.0/vectorsmith_core/execute/expr/grammar.lark +44 -0
- vectorsmith-0.1.0/vectorsmith_core/execute/expr/parser.py +144 -0
- vectorsmith-0.1.0/vectorsmith_core/execute/pipeline.py +143 -0
- vectorsmith-0.1.0/vectorsmith_core/execute/single_step.py +146 -0
- vectorsmith-0.1.0/vectorsmith_core/introspect/__init__.py +1 -0
- vectorsmith-0.1.0/vectorsmith_core/introspect/sampling.py +115 -0
- vectorsmith-0.1.0/vectorsmith_core/introspect/schema_export.py +65 -0
- vectorsmith-0.1.0/vectorsmith_core/introspect/schema_export_v1.json +11 -0
- vectorsmith-0.1.0/vectorsmith_core/introspect/types.py +1 -0
- vectorsmith-0.1.0/vectorsmith_core/ir/__init__.py +1 -0
- vectorsmith-0.1.0/vectorsmith_core/ir/filter.py +104 -0
- vectorsmith-0.1.0/vectorsmith_core/py.typed +0 -0
- vectorsmith-0.1.0/vectorsmith_core/tds/__init__.py +1 -0
- vectorsmith-0.1.0/vectorsmith_core/tds/loader.py +214 -0
- vectorsmith-0.1.0/vectorsmith_core/tds/models.py +262 -0
- vectorsmith-0.1.0/vectorsmith_core/tds/schema.py +19 -0
- vectorsmith-0.1.0/vectorsmith_core/tds/schema_v1.json +1228 -0
- vectorsmith-0.1.0/vectorsmith_core/version.py +4 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Virtualenv and Python
|
|
2
|
+
.venv/
|
|
3
|
+
__pycache__/
|
|
4
|
+
*.py[cod]
|
|
5
|
+
*$py.class
|
|
6
|
+
*.egg-info/
|
|
7
|
+
.eggs/
|
|
8
|
+
dist/
|
|
9
|
+
build/
|
|
10
|
+
*.so
|
|
11
|
+
|
|
12
|
+
# Tooling
|
|
13
|
+
.mypy_cache/
|
|
14
|
+
.pytest_cache/
|
|
15
|
+
.ruff_cache/
|
|
16
|
+
.hypothesis/
|
|
17
|
+
.import_linter_cache/
|
|
18
|
+
.coverage
|
|
19
|
+
coverage.xml
|
|
20
|
+
htmlcov/
|
|
21
|
+
.tox/
|
|
22
|
+
.nox/
|
|
23
|
+
|
|
24
|
+
# Secrets and local env (keep committed *.example)
|
|
25
|
+
.env
|
|
26
|
+
.env.*
|
|
27
|
+
!.env.example
|
|
28
|
+
|
|
29
|
+
# Editors and OS
|
|
30
|
+
.DS_Store
|
|
31
|
+
.idea/
|
|
32
|
+
.vscode/
|
|
33
|
+
*.swp
|
|
34
|
+
.cursor/
|
|
35
|
+
|
|
36
|
+
# Unpublished working notes (design dumps, idea PDF)
|
|
37
|
+
.internal/
|
|
38
|
+
|
|
39
|
+
# Local drafts produced by serve --enable-define
|
|
40
|
+
tools.drafts.yaml
|
|
41
|
+
**/tools.drafts.yaml
|
|
42
|
+
|
|
43
|
+
# MkDocs
|
|
44
|
+
site/
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: vectorsmith
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: YAML tools for agents: load_tools in-process, or vectorsmith serve as MCP
|
|
5
|
+
Project-URL: Homepage, https://github.com/kjgpta/vectorsmith
|
|
6
|
+
Project-URL: Documentation, https://kjgpta.github.io/vectorsmith/
|
|
7
|
+
Project-URL: Issues, https://github.com/kjgpta/vectorsmith/issues
|
|
8
|
+
Project-URL: Changelog, https://github.com/kjgpta/vectorsmith/blob/main/CHANGELOG.md
|
|
9
|
+
Author: VectorSmith contributors
|
|
10
|
+
License: Apache-2.0
|
|
11
|
+
Keywords: agents,claude,codex,langchain,langgraph,mcp
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
19
|
+
Classifier: Typing :: Typed
|
|
20
|
+
Requires-Python: >=3.11
|
|
21
|
+
Requires-Dist: argon2-cffi
|
|
22
|
+
Requires-Dist: httpx
|
|
23
|
+
Requires-Dist: jsonschema
|
|
24
|
+
Requires-Dist: lark
|
|
25
|
+
Requires-Dist: mcp>=1.2
|
|
26
|
+
Requires-Dist: polars>=1.0
|
|
27
|
+
Requires-Dist: pydantic>=2.7
|
|
28
|
+
Requires-Dist: pyyaml
|
|
29
|
+
Requires-Dist: ruamel-yaml
|
|
30
|
+
Requires-Dist: starlette
|
|
31
|
+
Requires-Dist: typer
|
|
32
|
+
Requires-Dist: uvicorn
|
|
33
|
+
Requires-Dist: watchfiles
|
|
34
|
+
Provides-Extra: anthropic
|
|
35
|
+
Requires-Dist: anthropic>=0.40; extra == 'anthropic'
|
|
36
|
+
Provides-Extra: chroma
|
|
37
|
+
Requires-Dist: chromadb>=0.5; extra == 'chroma'
|
|
38
|
+
Requires-Dist: fastembed>=0.3; extra == 'chroma'
|
|
39
|
+
Provides-Extra: langchain
|
|
40
|
+
Requires-Dist: langchain-core>=0.3; extra == 'langchain'
|
|
41
|
+
Provides-Extra: langgraph
|
|
42
|
+
Requires-Dist: langchain-core>=0.3; extra == 'langgraph'
|
|
43
|
+
Requires-Dist: langgraph>=0.2; extra == 'langgraph'
|
|
44
|
+
Provides-Extra: milvus
|
|
45
|
+
Requires-Dist: fastembed>=0.3; extra == 'milvus'
|
|
46
|
+
Requires-Dist: pymilvus>=2.4; extra == 'milvus'
|
|
47
|
+
Provides-Extra: openai-agents
|
|
48
|
+
Requires-Dist: openai-agents>=0.2; extra == 'openai-agents'
|
|
49
|
+
Provides-Extra: pgvector
|
|
50
|
+
Requires-Dist: fastembed>=0.3; extra == 'pgvector'
|
|
51
|
+
Requires-Dist: psycopg[binary,pool]>=3.2; extra == 'pgvector'
|
|
52
|
+
Provides-Extra: pinecone
|
|
53
|
+
Requires-Dist: fastembed>=0.3; extra == 'pinecone'
|
|
54
|
+
Requires-Dist: pinecone>=5.0; extra == 'pinecone'
|
|
55
|
+
Provides-Extra: qdrant
|
|
56
|
+
Requires-Dist: fastembed>=0.3; extra == 'qdrant'
|
|
57
|
+
Requires-Dist: qdrant-client>=1.9; extra == 'qdrant'
|
|
58
|
+
Provides-Extra: weaviate
|
|
59
|
+
Requires-Dist: fastembed>=0.3; extra == 'weaviate'
|
|
60
|
+
Requires-Dist: weaviate-client>=4.0; extra == 'weaviate'
|
|
61
|
+
Description-Content-Type: text/markdown
|
|
62
|
+
|
|
63
|
+
# vectorsmith
|
|
64
|
+
|
|
65
|
+
Install this package. Write a `tools.yaml`. Use `load_tools` in Python or `vectorsmith serve` as an MCP server.
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
pip install "vectorsmith[qdrant,langchain]"
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
from vectorsmith import load_tools
|
|
73
|
+
|
|
74
|
+
tools = load_tools("tools.yaml")
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
See the repository `README.md` and `docs/` for integrations and the YAML reference.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# vectorsmith
|
|
2
|
+
|
|
3
|
+
Install this package. Write a `tools.yaml`. Use `load_tools` in Python or `vectorsmith serve` as an MCP server.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
pip install "vectorsmith[qdrant,langchain]"
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
```python
|
|
10
|
+
from vectorsmith import load_tools
|
|
11
|
+
|
|
12
|
+
tools = load_tools("tools.yaml")
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
See the repository `README.md` and `docs/` for integrations and the YAML reference.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""Ship ``vectorsmith_core`` inside the ``vectorsmith`` wheel (not a second PyPI project)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from hatchling.builders.hooks.plugin.interface import BuildHookInterface
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class CustomBuildHook(BuildHookInterface):
|
|
12
|
+
def initialize(self, version: str, build_data: dict[str, Any]) -> None:
|
|
13
|
+
del version
|
|
14
|
+
root = Path(self.root)
|
|
15
|
+
local = root / "vectorsmith_core"
|
|
16
|
+
sibling = root.parent / "core" / "vectorsmith_core"
|
|
17
|
+
if (local / "__init__.py").is_file():
|
|
18
|
+
src = local
|
|
19
|
+
elif (sibling / "__init__.py").is_file():
|
|
20
|
+
src = sibling
|
|
21
|
+
else:
|
|
22
|
+
raise RuntimeError(
|
|
23
|
+
"vectorsmith_core not found at ./vectorsmith_core or ../core/vectorsmith_core"
|
|
24
|
+
)
|
|
25
|
+
build_data.setdefault("force_include", {})
|
|
26
|
+
build_data["force_include"][str(src)] = "vectorsmith_core"
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "vectorsmith"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "YAML tools for agents: load_tools in-process, or vectorsmith serve as MCP"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.11"
|
|
7
|
+
license = { text = "Apache-2.0" }
|
|
8
|
+
authors = [{ name = "VectorSmith contributors" }]
|
|
9
|
+
keywords = ["mcp", "langchain", "langgraph", "claude", "codex", "agents"]
|
|
10
|
+
classifiers = [
|
|
11
|
+
"Development Status :: 4 - Beta",
|
|
12
|
+
"License :: OSI Approved :: Apache Software License",
|
|
13
|
+
"Programming Language :: Python :: 3",
|
|
14
|
+
"Programming Language :: Python :: 3.11",
|
|
15
|
+
"Programming Language :: Python :: 3.12",
|
|
16
|
+
"Programming Language :: Python :: 3.13",
|
|
17
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
18
|
+
"Typing :: Typed",
|
|
19
|
+
]
|
|
20
|
+
dependencies = [
|
|
21
|
+
"pydantic>=2.7",
|
|
22
|
+
"pyyaml",
|
|
23
|
+
"jsonschema",
|
|
24
|
+
"polars>=1.0",
|
|
25
|
+
"lark",
|
|
26
|
+
"httpx",
|
|
27
|
+
"typer",
|
|
28
|
+
"watchfiles",
|
|
29
|
+
"uvicorn",
|
|
30
|
+
"starlette",
|
|
31
|
+
"mcp>=1.2",
|
|
32
|
+
"argon2-cffi",
|
|
33
|
+
"ruamel.yaml",
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
[project.urls]
|
|
37
|
+
Homepage = "https://github.com/kjgpta/vectorsmith"
|
|
38
|
+
Documentation = "https://kjgpta.github.io/vectorsmith/"
|
|
39
|
+
Issues = "https://github.com/kjgpta/vectorsmith/issues"
|
|
40
|
+
Changelog = "https://github.com/kjgpta/vectorsmith/blob/main/CHANGELOG.md"
|
|
41
|
+
|
|
42
|
+
[project.scripts]
|
|
43
|
+
vectorsmith = "vectorsmith_cli.main:app"
|
|
44
|
+
|
|
45
|
+
[project.optional-dependencies]
|
|
46
|
+
qdrant = ["qdrant-client>=1.9", "fastembed>=0.3"]
|
|
47
|
+
pgvector = ["psycopg[binary,pool]>=3.2", "fastembed>=0.3"]
|
|
48
|
+
chroma = ["chromadb>=0.5", "fastembed>=0.3"]
|
|
49
|
+
pinecone = ["pinecone>=5.0", "fastembed>=0.3"]
|
|
50
|
+
weaviate = ["weaviate-client>=4.0", "fastembed>=0.3"]
|
|
51
|
+
milvus = ["pymilvus>=2.4", "fastembed>=0.3"]
|
|
52
|
+
langchain = ["langchain-core>=0.3"]
|
|
53
|
+
langgraph = ["langchain-core>=0.3", "langgraph>=0.2"]
|
|
54
|
+
openai-agents = ["openai-agents>=0.2"]
|
|
55
|
+
anthropic = ["anthropic>=0.40"]
|
|
56
|
+
|
|
57
|
+
[build-system]
|
|
58
|
+
requires = ["hatchling"]
|
|
59
|
+
build-backend = "hatchling.build"
|
|
60
|
+
|
|
61
|
+
[tool.hatch.build.targets.wheel]
|
|
62
|
+
packages = ["vectorsmith_cli", "vectorsmith"]
|
|
63
|
+
|
|
64
|
+
[tool.hatch.build.hooks.custom]
|
|
65
|
+
path = "hatch_build.py"
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""VectorSmith application API.
|
|
2
|
+
|
|
3
|
+
In-process (LangChain, LangGraph, OpenAI Agents, Anthropic SDK)::
|
|
4
|
+
|
|
5
|
+
from vectorsmith import load_tools # LangChain / LangGraph
|
|
6
|
+
from vectorsmith import connect # call() or .as_openai_agents() / .as_anthropic()
|
|
7
|
+
|
|
8
|
+
MCP hosts (Claude Desktop, Claude Code, Codex, Cursor)::
|
|
9
|
+
|
|
10
|
+
vectorsmith serve tools.yaml --name invoices
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from typing import TYPE_CHECKING, Any
|
|
16
|
+
|
|
17
|
+
from vectorsmith.runtime import BoundTools, connect
|
|
18
|
+
|
|
19
|
+
if TYPE_CHECKING:
|
|
20
|
+
from vectorsmith.langchain_tools import Toolset
|
|
21
|
+
from vectorsmith.langchain_tools import load_tools as load_tools
|
|
22
|
+
|
|
23
|
+
__all__ = ["BoundTools", "Toolset", "connect", "load_tools"]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def __getattr__(name: str) -> Any:
|
|
27
|
+
if name in {"load_tools", "Toolset"}:
|
|
28
|
+
from vectorsmith.langchain_tools import Toolset, load_tools
|
|
29
|
+
|
|
30
|
+
return {"load_tools": load_tools, "Toolset": Toolset}[name]
|
|
31
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""Anthropic Messages API tools from a tools.yaml.
|
|
2
|
+
|
|
3
|
+
pip install "vectorsmith[qdrant]" anthropic
|
|
4
|
+
from vectorsmith.anthropic import load_tools
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
from collections.abc import Mapping
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
from vectorsmith.runtime import BoundTools, connect
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class AnthropicToolset:
|
|
18
|
+
"""``tools`` for ``messages.create``, plus ``execute`` for ``tool_use`` blocks."""
|
|
19
|
+
|
|
20
|
+
def __init__(self, bound: BoundTools) -> None:
|
|
21
|
+
self._bound = bound
|
|
22
|
+
self.tools = bound.as_anthropic()
|
|
23
|
+
|
|
24
|
+
@property
|
|
25
|
+
def names(self) -> list[str]:
|
|
26
|
+
return self._bound.names
|
|
27
|
+
|
|
28
|
+
async def execute(self, name: str, tool_input: Mapping[str, Any] | None = None) -> str:
|
|
29
|
+
result = await self._bound.call(name, tool_input)
|
|
30
|
+
return json.dumps(result)
|
|
31
|
+
|
|
32
|
+
async def aclose(self) -> None:
|
|
33
|
+
await self._bound.aclose()
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def load_tools(
|
|
37
|
+
*sources: str | Path | dict[str, Any],
|
|
38
|
+
env: Mapping[str, str] | None = None,
|
|
39
|
+
env_file: str | Path | None = None,
|
|
40
|
+
) -> AnthropicToolset:
|
|
41
|
+
"""Compile tools.yaml into Anthropic tool defs + an in-process dispatcher."""
|
|
42
|
+
return AnthropicToolset(connect(*sources, env=env, env_file=env_file))
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"""LangChain / LangGraph tools from a tools.yaml.
|
|
2
|
+
|
|
3
|
+
Application code: write YAML, then ``from vectorsmith import load_tools``.
|
|
4
|
+
Hosts that cannot import Python (Claude Desktop, Codex, Cursor) use
|
|
5
|
+
``vectorsmith serve`` instead. Same YAML.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import asyncio
|
|
11
|
+
import uuid
|
|
12
|
+
from collections.abc import Mapping, Sequence
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Any
|
|
15
|
+
|
|
16
|
+
from langchain_core.tools import BaseTool, StructuredTool
|
|
17
|
+
from pydantic import BaseModel, Field, create_model
|
|
18
|
+
|
|
19
|
+
from vectorsmith.runtime import BoundTools, connect
|
|
20
|
+
from vectorsmith_core.api import CallContext
|
|
21
|
+
from vectorsmith_core.execute.engine import Engine
|
|
22
|
+
|
|
23
|
+
_JSON_TO_PY: dict[str, type] = {
|
|
24
|
+
"string": str,
|
|
25
|
+
"integer": int,
|
|
26
|
+
"number": float,
|
|
27
|
+
"boolean": bool,
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class Toolset(list[BaseTool]):
|
|
32
|
+
"""LangChain tools plus the engines that back them. Call ``aclose`` when done."""
|
|
33
|
+
|
|
34
|
+
def __init__(self, tools: Sequence[BaseTool], engines: Sequence[Engine]) -> None:
|
|
35
|
+
super().__init__(tools)
|
|
36
|
+
self._engines = list(engines)
|
|
37
|
+
|
|
38
|
+
async def aclose(self) -> None:
|
|
39
|
+
for engine in self._engines:
|
|
40
|
+
await engine.aclose()
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _py_type(spec: dict[str, Any]) -> type:
|
|
44
|
+
if spec.get("type") == "array":
|
|
45
|
+
item = _JSON_TO_PY.get((spec.get("items") or {}).get("type", "string"), str)
|
|
46
|
+
return list[item] # type: ignore[valid-type]
|
|
47
|
+
return _JSON_TO_PY.get(str(spec.get("type", "string")), str)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _args_model(tool_name: str, input_schema: dict[str, Any]) -> type[BaseModel]:
|
|
51
|
+
props = input_schema.get("properties") or {}
|
|
52
|
+
required = set(input_schema.get("required") or [])
|
|
53
|
+
fields: dict[str, Any] = {}
|
|
54
|
+
for key, spec in props.items():
|
|
55
|
+
typ = _py_type(spec if isinstance(spec, dict) else {})
|
|
56
|
+
desc = spec.get("description") if isinstance(spec, dict) else None
|
|
57
|
+
if key in required:
|
|
58
|
+
fields[key] = (typ, Field(..., description=desc))
|
|
59
|
+
else:
|
|
60
|
+
default = spec.get("default") if isinstance(spec, dict) else None
|
|
61
|
+
fields[key] = (typ | None, Field(default, description=desc))
|
|
62
|
+
if not fields:
|
|
63
|
+
return create_model(f"{tool_name}Args")
|
|
64
|
+
return create_model(f"{tool_name}Args", **fields)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _bind(engine: Engine, name: str, schema: dict[str, Any]) -> StructuredTool:
|
|
68
|
+
async def _arun(**kwargs: Any) -> dict[str, Any]:
|
|
69
|
+
clean = {k: v for k, v in kwargs.items() if v is not None}
|
|
70
|
+
result = await engine.call(
|
|
71
|
+
name, clean, ctx=CallContext(request_id=str(uuid.uuid4()))
|
|
72
|
+
)
|
|
73
|
+
return result.model_dump()
|
|
74
|
+
|
|
75
|
+
def _run(**kwargs: Any) -> dict[str, Any]:
|
|
76
|
+
try:
|
|
77
|
+
asyncio.get_running_loop()
|
|
78
|
+
except RuntimeError:
|
|
79
|
+
return asyncio.run(_arun(**kwargs))
|
|
80
|
+
raise RuntimeError(f"tool {name!r} must be awaited in an async context")
|
|
81
|
+
|
|
82
|
+
return StructuredTool(
|
|
83
|
+
name=name,
|
|
84
|
+
description=schema.get("description") or name,
|
|
85
|
+
args_schema=_args_model(name, schema.get("inputSchema") or {}),
|
|
86
|
+
coroutine=_arun,
|
|
87
|
+
func=_run,
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def toolset_from_bound(bound: BoundTools) -> Toolset:
|
|
92
|
+
tools: list[BaseTool] = []
|
|
93
|
+
for engine in bound._engines:
|
|
94
|
+
for schema in engine.project.mcp_tool_schemas():
|
|
95
|
+
tools.append(_bind(engine, str(schema["name"]), schema))
|
|
96
|
+
return Toolset(tools, bound._engines)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def load_tools(
|
|
100
|
+
*sources: str | Path | dict[str, Any],
|
|
101
|
+
env: Mapping[str, str] | None = None,
|
|
102
|
+
env_file: str | Path | None = None,
|
|
103
|
+
) -> Toolset:
|
|
104
|
+
"""Compile one or more tools.yaml files into LangChain / LangGraph tools.
|
|
105
|
+
|
|
106
|
+
>>> from vectorsmith import load_tools
|
|
107
|
+
>>> tools = load_tools("tools.invoices.yaml", "tools.tickets.yaml")
|
|
108
|
+
>>> # pass ``tools`` into create_agent / create_react_agent; await tools.aclose()
|
|
109
|
+
"""
|
|
110
|
+
return connect(*sources, env=env, env_file=env_file).as_langchain()
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""OpenAI Agents SDK tools from a tools.yaml.
|
|
2
|
+
|
|
3
|
+
pip install "vectorsmith[qdrant,openai-agents]"
|
|
4
|
+
from vectorsmith.openai_agents import load_tools
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
from collections.abc import Mapping, Sequence
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
from vectorsmith.runtime import BoundTools, connect
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class OpenAIAgentToolset(list[Any]):
|
|
18
|
+
"""FunctionTool list plus ``aclose`` for the backing engines."""
|
|
19
|
+
|
|
20
|
+
def __init__(self, tools: Sequence[Any], bound: BoundTools) -> None:
|
|
21
|
+
super().__init__(tools)
|
|
22
|
+
self._bound = bound
|
|
23
|
+
|
|
24
|
+
async def aclose(self) -> None:
|
|
25
|
+
await self._bound.aclose()
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _function_tool(bound: BoundTools, schema: dict[str, Any]) -> Any:
|
|
29
|
+
try:
|
|
30
|
+
from agents import FunctionTool
|
|
31
|
+
except ImportError as exc:
|
|
32
|
+
raise ImportError(
|
|
33
|
+
"OpenAI Agents SDK is required. Install with: "
|
|
34
|
+
'pip install "vectorsmith[openai-agents]"'
|
|
35
|
+
) from exc
|
|
36
|
+
|
|
37
|
+
name = str(schema["name"])
|
|
38
|
+
params = schema.get("inputSchema") or {"type": "object", "properties": {}}
|
|
39
|
+
|
|
40
|
+
async def _on_invoke(_ctx: Any, raw: str) -> str:
|
|
41
|
+
try:
|
|
42
|
+
args = json.loads(raw) if raw else {}
|
|
43
|
+
except json.JSONDecodeError:
|
|
44
|
+
args = {}
|
|
45
|
+
if not isinstance(args, dict):
|
|
46
|
+
return json.dumps({"error": "tool arguments must be a JSON object"})
|
|
47
|
+
result = await bound.call(name, args)
|
|
48
|
+
return json.dumps(result)
|
|
49
|
+
|
|
50
|
+
return FunctionTool(
|
|
51
|
+
name=name,
|
|
52
|
+
description=schema.get("description") or name,
|
|
53
|
+
params_json_schema=params,
|
|
54
|
+
on_invoke_tool=_on_invoke,
|
|
55
|
+
strict_json_schema=False,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def toolset_from_bound(bound: BoundTools) -> OpenAIAgentToolset:
|
|
60
|
+
return OpenAIAgentToolset([_function_tool(bound, s) for s in bound.schemas], bound)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def load_tools(
|
|
64
|
+
*sources: str | Path | dict[str, Any],
|
|
65
|
+
env: Mapping[str, str] | None = None,
|
|
66
|
+
env_file: str | Path | None = None,
|
|
67
|
+
) -> OpenAIAgentToolset:
|
|
68
|
+
"""Compile tools.yaml into OpenAI Agents SDK ``FunctionTool``s."""
|
|
69
|
+
return connect(*sources, env=env, env_file=env_file).as_openai_agents()
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"""Compile tools.yaml in-process. Framework adapters wrap this; MCP hosts use ``serve``."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import uuid
|
|
7
|
+
from collections.abc import Mapping, Sequence
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
from vectorsmith_core.api import CallContext, EnvCredentialResolver, load_project
|
|
12
|
+
from vectorsmith_core.execute.engine import Engine
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _read_env_file(path: Path | None) -> dict[str, str]:
|
|
16
|
+
if path is None or not path.is_file():
|
|
17
|
+
return {}
|
|
18
|
+
out: dict[str, str] = {}
|
|
19
|
+
for line in path.read_text().splitlines():
|
|
20
|
+
line = line.strip()
|
|
21
|
+
if not line or line.startswith("#") or "=" not in line:
|
|
22
|
+
continue
|
|
23
|
+
key, value = line.split("=", 1)
|
|
24
|
+
out[key.strip()] = value.strip()
|
|
25
|
+
return out
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _merge_env(
|
|
29
|
+
env: Mapping[str, str] | None,
|
|
30
|
+
env_file: str | Path | None,
|
|
31
|
+
) -> dict[str, str]:
|
|
32
|
+
merged = dict(os.environ)
|
|
33
|
+
if env:
|
|
34
|
+
merged.update(env)
|
|
35
|
+
merged.update(_read_env_file(Path(env_file) if env_file else None))
|
|
36
|
+
return merged
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _one_engine(source: str | Path | dict[str, Any], env: Mapping[str, str]) -> Engine:
|
|
40
|
+
project = load_project(source, env=env)
|
|
41
|
+
errors = [i for i in project.issues if i.severity == "error"]
|
|
42
|
+
if errors:
|
|
43
|
+
msgs = "; ".join(f"{i.code} {i.message}" for i in errors)
|
|
44
|
+
raise ValueError(f"tools.yaml failed validation: {msgs}")
|
|
45
|
+
try:
|
|
46
|
+
from vectorsmith_core.embed.provider import FastEmbedProvider
|
|
47
|
+
|
|
48
|
+
embed: FastEmbedProvider | None = FastEmbedProvider()
|
|
49
|
+
except Exception:
|
|
50
|
+
embed = None
|
|
51
|
+
return Engine(
|
|
52
|
+
project,
|
|
53
|
+
credential_resolver=EnvCredentialResolver(env),
|
|
54
|
+
embed_provider=embed,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class BoundTools:
|
|
59
|
+
"""Compiled YAML tools. Call them directly, or adapt to an agent framework.
|
|
60
|
+
|
|
61
|
+
>>> from vectorsmith import connect
|
|
62
|
+
>>> vs = connect("tools.yaml")
|
|
63
|
+
>>> await vs.call("search_invoices", {"query": "Globex", "limit": 3})
|
|
64
|
+
>>> tools = vs.as_langchain() # pip install 'vectorsmith[langchain]'
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
def __init__(self, engines: Sequence[Engine]) -> None:
|
|
68
|
+
self._engines = list(engines)
|
|
69
|
+
self._by_name: dict[str, Engine] = {}
|
|
70
|
+
schemas: list[dict[str, Any]] = []
|
|
71
|
+
for engine in self._engines:
|
|
72
|
+
for schema in engine.project.mcp_tool_schemas():
|
|
73
|
+
name = str(schema["name"])
|
|
74
|
+
self._by_name[name] = engine
|
|
75
|
+
schemas.append(schema)
|
|
76
|
+
self._schemas = schemas
|
|
77
|
+
|
|
78
|
+
@property
|
|
79
|
+
def names(self) -> list[str]:
|
|
80
|
+
return [str(s["name"]) for s in self._schemas]
|
|
81
|
+
|
|
82
|
+
@property
|
|
83
|
+
def schemas(self) -> list[dict[str, Any]]:
|
|
84
|
+
"""MCP tool schemas (``name``, ``description``, ``inputSchema``)."""
|
|
85
|
+
return list(self._schemas)
|
|
86
|
+
|
|
87
|
+
def as_anthropic(self) -> list[dict[str, Any]]:
|
|
88
|
+
"""Tool defs for ``anthropic.Anthropic().messages.create(tools=...)``."""
|
|
89
|
+
return [
|
|
90
|
+
{
|
|
91
|
+
"name": s["name"],
|
|
92
|
+
"description": s.get("description") or s["name"],
|
|
93
|
+
"input_schema": s.get("inputSchema") or {"type": "object", "properties": {}},
|
|
94
|
+
}
|
|
95
|
+
for s in self._schemas
|
|
96
|
+
]
|
|
97
|
+
|
|
98
|
+
def as_langchain(self) -> Any:
|
|
99
|
+
"""LangChain ``StructuredTool`` list. Requires ``vectorsmith[langchain]``."""
|
|
100
|
+
from vectorsmith.langchain_tools import toolset_from_bound
|
|
101
|
+
|
|
102
|
+
return toolset_from_bound(self)
|
|
103
|
+
|
|
104
|
+
def as_openai_agents(self) -> Any:
|
|
105
|
+
"""OpenAI Agents SDK ``FunctionTool`` list. Requires ``vectorsmith[openai-agents]``."""
|
|
106
|
+
from vectorsmith.openai_agents import toolset_from_bound
|
|
107
|
+
|
|
108
|
+
return toolset_from_bound(self)
|
|
109
|
+
|
|
110
|
+
async def call(self, name: str, args: Mapping[str, Any] | None = None) -> dict[str, Any]:
|
|
111
|
+
engine = self._by_name.get(name)
|
|
112
|
+
if engine is None:
|
|
113
|
+
known = ", ".join(self.names) or "(none)"
|
|
114
|
+
raise KeyError(f"unknown tool {name!r}; loaded: {known}")
|
|
115
|
+
clean = {k: v for k, v in dict(args or {}).items() if v is not None}
|
|
116
|
+
result = await engine.call(
|
|
117
|
+
name, clean, ctx=CallContext(request_id=str(uuid.uuid4()))
|
|
118
|
+
)
|
|
119
|
+
return result.model_dump()
|
|
120
|
+
|
|
121
|
+
async def aclose(self) -> None:
|
|
122
|
+
for engine in self._engines:
|
|
123
|
+
await engine.aclose()
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def connect(
|
|
127
|
+
*sources: str | Path | dict[str, Any],
|
|
128
|
+
env: Mapping[str, str] | None = None,
|
|
129
|
+
env_file: str | Path | None = None,
|
|
130
|
+
) -> BoundTools:
|
|
131
|
+
"""Compile one or more tools.yaml files. No MCP subprocess.
|
|
132
|
+
|
|
133
|
+
Use this when you want ``await vs.call(...)`` or a non-LangChain adapter.
|
|
134
|
+
LangChain / LangGraph apps can keep using ``load_tools``.
|
|
135
|
+
"""
|
|
136
|
+
if not sources:
|
|
137
|
+
raise ValueError("connect requires at least one tools.yaml path")
|
|
138
|
+
merged = _merge_env(env, env_file)
|
|
139
|
+
return BoundTools([_one_engine(source, merged) for source in sources])
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""VectorSmith CLI."""
|