agentified-langchain 0.0.6__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.
@@ -0,0 +1,43 @@
1
+ # Rust
2
+ target/
3
+ **/*.rs.bk
4
+
5
+ # Environment
6
+ .env
7
+ .env.local
8
+ .env.*.local
9
+
10
+ # IDE
11
+ .idea/
12
+ .vscode/
13
+ *.swp
14
+ *.swo
15
+
16
+ # OS
17
+ .DS_Store
18
+ Thumbs.db
19
+
20
+ # Node
21
+ node_modules/
22
+
23
+ # Python
24
+ __pycache__/
25
+ *.py[cod]
26
+ *.egg-info/
27
+ *.egg
28
+ dist/
29
+ build/
30
+ .pytest_cache/
31
+ .mypy_cache/
32
+ .ruff_cache/
33
+ *.whl
34
+
35
+ # TypeScript
36
+ *.tsbuildinfo
37
+ src/ts-packages/**/src/**/*.js
38
+ src/ts-packages/**/src/**/*.js.map
39
+ src/ts-packages/**/src/**/*.d.ts
40
+ src/ts-packages/**/src/**/*.d.ts.map
41
+
42
+ # Logs
43
+ *.log
@@ -0,0 +1,11 @@
1
+ Metadata-Version: 2.4
2
+ Name: agentified-langchain
3
+ Version: 0.0.6
4
+ Summary: LangChain adapter for Agentified — tool injection for LangChain/LangGraph agents
5
+ License-Expression: MIT
6
+ Requires-Python: >=3.10
7
+ Requires-Dist: agentified>=0.0.6
8
+ Requires-Dist: langchain-core>=0.3
9
+ Provides-Extra: dev
10
+ Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
11
+ Requires-Dist: pytest>=8; extra == 'dev'
@@ -0,0 +1,116 @@
1
+ # agentified-langchain
2
+
3
+ LangChain adapter for [Agentified](../../../README.md) — wraps SDK classes so that `session.context.assemble()` returns LangChain `StructuredTool` instances directly. No manual conversion needed.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ pip install agentified-langchain
9
+ ```
10
+
11
+ Requires Python >= 3.10. Peer dependencies: `agentified >= 0.0.5`, `langchain-core >= 0.3`.
12
+
13
+ ## Quick Start
14
+
15
+ ```python
16
+ from agentified_langchain import LangchainAgentified, BackendTool, RegisterInput
17
+ from langchain_openai import ChatOpenAI
18
+ from langgraph.prebuilt import create_react_agent
19
+
20
+ ag = LangchainAgentified()
21
+ await ag.connect("http://localhost:9119")
22
+
23
+ instance = await ag.register(RegisterInput(tools=[
24
+ BackendTool(name="get_weather", description="Get current weather",
25
+ parameters={"type": "object", "properties": {"city": {"type": "string"}}},
26
+ handler=lambda args: {"temp": 22, "city": args["city"]}),
27
+ ]))
28
+
29
+ session = instance.session("chat-1")
30
+
31
+ # Assemble context — tools are LangChain StructuredTools
32
+ ctx = await session.context.messages(strategy="recent").assemble()
33
+
34
+ # Pass directly to LangGraph
35
+ llm = ChatOpenAI(model="gpt-4o-mini")
36
+ agent = create_react_agent(llm, list(ctx.tools.values()))
37
+ result = await agent.ainvoke({"messages": [{"role": "user", "content": "What's the weather?"}]})
38
+ ```
39
+
40
+ ## API Hierarchy
41
+
42
+ ```
43
+ LangchainAgentified
44
+ ├── connect(server_url)
45
+ ├── disconnect()
46
+ ├── dataset(name) → LangchainDatasetRef
47
+ │ └── register(RegisterInput) → LangchainInstance
48
+ └── register(RegisterInput) → LangchainInstance
49
+
50
+ LangchainInstance
51
+ ├── discover_tool → StructuredTool (agentified_discover)
52
+ ├── get_tools() → list[StructuredTool]
53
+ ├── session(id) → LangchainSession
54
+ └── namespace(id) → LangchainNamespace
55
+ └── session(id) → LangchainSession
56
+
57
+ LangchainSession
58
+ ├── discover_tool → StructuredTool
59
+ ├── context → LangchainContextBuilder
60
+ │ ├── .tools(dict[str, StructuredTool]) → self
61
+ │ ├── .messages(strategy?, max_tokens?) → self
62
+ │ ├── .recall() → self
63
+ │ └── .assemble() → LangchainAssembledContext
64
+ ├── get_tools() → list[StructuredTool]
65
+ ├── conversation → Conversation
66
+ ├── get_messages(opts?) → GetMessagesResult
67
+ └── update_conversation(messages)
68
+ ```
69
+
70
+ ## `LangchainAssembledContext`
71
+
72
+ Returned by `.assemble()`. Tools are already `StructuredTool` instances:
73
+
74
+ ```python
75
+ ctx = await session.context.messages(strategy="recent").assemble()
76
+
77
+ ctx.tools # dict[str, StructuredTool] — explicit + discovered
78
+ ctx.messages # list[StoredMessage]
79
+ ctx.token_estimate # int
80
+ ctx.strategy_used # str
81
+ ctx.recalled # list (stub)
82
+ ```
83
+
84
+ ## `LangchainContextBuilder`
85
+
86
+ Fluent API — chain `.tools()`, `.messages()`, `.recall()`, then `.assemble()`:
87
+
88
+ ```python
89
+ ctx = await session.context \
90
+ .tools({"custom": my_structured_tool}) \
91
+ .messages(strategy="recent", max_tokens=4000) \
92
+ .assemble()
93
+ ```
94
+
95
+ Explicit tools passed via `.tools()` are merged with auto-discovered tools.
96
+
97
+ ## `LangchainSession.get_tools()`
98
+
99
+ Returns `discover_tool` + any tools discovered so far as `StructuredTool` instances:
100
+
101
+ ```python
102
+ tools = session.get_tools()
103
+ # [StructuredTool(agentified_discover), StructuredTool(get_weather), ...]
104
+ ```
105
+
106
+ ## Links
107
+
108
+ - [Root README](../../../README.md)
109
+ - [Documentation](../../../docs/)
110
+ - [LangGraph guide](../../../docs/python/integrations/langgraph.md) — Full Python walkthrough
111
+ - [Python SDK](../sdk/README.md)
112
+ - [py-langchain-sdk-smoke example](../../../examples/py-langchain-sdk-smoke/) — Runnable smoke test
113
+
114
+ ## License
115
+
116
+ [MIT](../../../LICENSE.md#mit-license)
@@ -0,0 +1,35 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "agentified-langchain"
7
+ version = "0.0.6"
8
+ description = "LangChain adapter for Agentified — tool injection for LangChain/LangGraph agents"
9
+ license = "MIT"
10
+ requires-python = ">=3.10"
11
+ dependencies = [
12
+ "agentified>=0.0.6",
13
+ "langchain-core>=0.3",
14
+ ]
15
+
16
+ [project.optional-dependencies]
17
+ dev = [
18
+ "pytest>=8",
19
+ "pytest-asyncio>=0.24",
20
+ ]
21
+
22
+ [tool.hatch.build.targets.wheel]
23
+ packages = ["src/agentified_langchain"]
24
+
25
+ [tool.pytest.ini_options]
26
+ asyncio_mode = "auto"
27
+
28
+ [tool.uv.sources]
29
+ agentified = { path = "../sdk", editable = true }
30
+
31
+ [dependency-groups]
32
+ dev = [
33
+ "pytest>=8",
34
+ "pytest-asyncio>=0.24",
35
+ ]
@@ -0,0 +1,26 @@
1
+ from .agentified import (
2
+ LangchainAgentified,
3
+ LangchainAssembledContext,
4
+ LangchainContextBuilder,
5
+ LangchainDatasetRef,
6
+ LangchainInstance,
7
+ LangchainNamespace,
8
+ LangchainSession,
9
+ )
10
+
11
+ from agentified import (
12
+ BackendTool,
13
+ RegisterInput,
14
+ )
15
+
16
+ __all__ = [
17
+ "LangchainAgentified",
18
+ "LangchainAssembledContext",
19
+ "LangchainContextBuilder",
20
+ "LangchainDatasetRef",
21
+ "LangchainInstance",
22
+ "LangchainNamespace",
23
+ "LangchainSession",
24
+ "BackendTool",
25
+ "RegisterInput",
26
+ ]
@@ -0,0 +1,262 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+ from pydantic import BaseModel, Field, create_model
6
+ from langchain_core.tools import StructuredTool
7
+
8
+ from agentified import (
9
+ Agentified,
10
+ AssembledContext,
11
+ BackendTool,
12
+ ContextBuilder,
13
+ DatasetRef,
14
+ Instance,
15
+ Namespace,
16
+ RegisterInput,
17
+ Session,
18
+ )
19
+ from agentified.models import DiscoverTool, DiscoverToolInput
20
+
21
+
22
+ # --- Helpers ---
23
+
24
+ def _json_schema_to_pydantic(schema: dict[str, Any]) -> type[BaseModel]:
25
+ props = schema.get("properties", {})
26
+ required = set(schema.get("required", []))
27
+ fields: dict[str, Any] = {}
28
+ type_map = {"string": str, "number": float, "integer": int, "boolean": bool}
29
+ for name, spec in props.items():
30
+ py_type = type_map.get(spec.get("type", "string"), str)
31
+ desc = spec.get("description", "")
32
+ if name in required:
33
+ fields[name] = (py_type, Field(description=desc))
34
+ else:
35
+ fields[name] = (py_type | None, Field(default=None, description=desc))
36
+ return create_model("DynamicInput", **fields)
37
+
38
+
39
+ def _build_lc_tool_map(backend_tools: list[BackendTool]) -> dict[str, StructuredTool]:
40
+ tools: dict[str, StructuredTool] = {}
41
+ for t in backend_tools:
42
+ input_model = _json_schema_to_pydantic(t.parameters)
43
+
44
+ async def _handler(h=t.handler, **kwargs: Any) -> Any:
45
+ result = h(kwargs)
46
+ if hasattr(result, "__await__"):
47
+ return await result
48
+ return result
49
+
50
+ tools[t.name] = StructuredTool.from_function(
51
+ coroutine=_handler,
52
+ name=t.name,
53
+ description=t.description,
54
+ args_schema=input_model,
55
+ )
56
+ return tools
57
+
58
+
59
+ def _wrap_discover_tool(dt: DiscoverTool) -> StructuredTool:
60
+ async def _execute(**kwargs: Any) -> list[dict[str, Any]]:
61
+ inp = DiscoverToolInput(**kwargs)
62
+ result = await dt.execute(inp)
63
+ return [{"name": t.name, "description": t.description, "score": t.score} for t in result]
64
+
65
+ return StructuredTool.from_function(
66
+ coroutine=_execute,
67
+ name=dt.definition.name,
68
+ description=dt.definition.description,
69
+ args_schema=_json_schema_to_pydantic(dt.definition.parameters),
70
+ )
71
+
72
+
73
+ # --- Wrapper classes ---
74
+
75
+ class LangchainAssembledContext:
76
+ def __init__(self, sdk_ctx: AssembledContext, tools: dict[str, StructuredTool]) -> None:
77
+ self._sdk_ctx = sdk_ctx
78
+ self.tools = tools
79
+
80
+ @property
81
+ def messages(self):
82
+ return self._sdk_ctx.messages
83
+
84
+ @property
85
+ def recalled(self):
86
+ return self._sdk_ctx.recalled
87
+
88
+ @property
89
+ def strategy_used(self):
90
+ return self._sdk_ctx.strategy_used
91
+
92
+ @property
93
+ def fallback(self):
94
+ return self._sdk_ctx.fallback
95
+
96
+ @property
97
+ def token_estimate(self):
98
+ return self._sdk_ctx.token_estimate
99
+
100
+ @property
101
+ def conversation_messages(self):
102
+ return self._sdk_ctx.conversation_messages
103
+
104
+ @property
105
+ def total_messages(self):
106
+ return self._sdk_ctx.total_messages
107
+
108
+ @property
109
+ def included_messages(self):
110
+ return self._sdk_ctx.included_messages
111
+
112
+
113
+ class LangchainContextBuilder:
114
+ def __init__(
115
+ self,
116
+ sdk_builder: ContextBuilder,
117
+ discover_tool: StructuredTool,
118
+ discovered_names: set[str],
119
+ lc_tool_cache: dict[str, StructuredTool],
120
+ ) -> None:
121
+ self._sdk_builder = sdk_builder
122
+ self._discover_tool = discover_tool
123
+ self._discovered_names = discovered_names
124
+ self._lc_tool_cache = lc_tool_cache
125
+ self._explicit_tools: dict[str, StructuredTool] = {}
126
+
127
+ def messages(self, **kwargs: Any) -> LangchainContextBuilder:
128
+ self._sdk_builder.messages(**kwargs)
129
+ return self
130
+
131
+ def tools(self, tools: dict[str, StructuredTool]) -> LangchainContextBuilder:
132
+ self._explicit_tools.update(tools)
133
+ return self
134
+
135
+ def recall(self, **kwargs: Any) -> LangchainContextBuilder:
136
+ self._sdk_builder.recall(**kwargs)
137
+ return self
138
+
139
+ async def assemble(self) -> LangchainAssembledContext:
140
+ sdk_ctx = await self._sdk_builder.assemble()
141
+
142
+ resolved: dict[str, StructuredTool] = {**self._explicit_tools}
143
+ for name in self._discovered_names:
144
+ if name not in resolved and name in self._lc_tool_cache:
145
+ resolved[name] = self._lc_tool_cache[name]
146
+
147
+ return LangchainAssembledContext(sdk_ctx, resolved)
148
+
149
+
150
+ class LangchainSession:
151
+ def __init__(self, sess: Session, lc_tool_cache: dict[str, StructuredTool]) -> None:
152
+ self._sess = sess
153
+ self._lc_tool_cache = lc_tool_cache
154
+ self.discover_tool = _wrap_discover_tool(sess.discover_tool)
155
+
156
+ @property
157
+ def id(self) -> str:
158
+ return self._sess.id
159
+
160
+ @property
161
+ def namespace_id(self) -> str:
162
+ return self._sess.namespace_id
163
+
164
+ @property
165
+ def conversation(self):
166
+ return self._sess.conversation
167
+
168
+ @property
169
+ def context(self) -> LangchainContextBuilder:
170
+ return LangchainContextBuilder(
171
+ self._sess.context,
172
+ self.discover_tool,
173
+ self._sess.discover_tool.discovered_names,
174
+ self._lc_tool_cache,
175
+ )
176
+
177
+ def get_tools(self) -> list[StructuredTool]:
178
+ tools: list[StructuredTool] = [self.discover_tool]
179
+ for name in self._sess.discover_tool.discovered_names:
180
+ if name in self._lc_tool_cache:
181
+ tools.append(self._lc_tool_cache[name])
182
+ return tools
183
+
184
+ async def get_messages(self, *args: Any, **kwargs: Any):
185
+ return await self._sess.get_messages(*args, **kwargs)
186
+
187
+ async def update_conversation(self, *args: Any, **kwargs: Any):
188
+ return await self._sess.update_conversation(*args, **kwargs)
189
+
190
+
191
+ class LangchainNamespace:
192
+ def __init__(self, ns: Namespace, lc_tool_cache: dict[str, StructuredTool]) -> None:
193
+ self._ns = ns
194
+ self._lc_tool_cache = lc_tool_cache
195
+
196
+ @property
197
+ def id(self) -> str:
198
+ return self._ns.id
199
+
200
+ def session(self, id: str) -> LangchainSession:
201
+ return LangchainSession(self._ns.session(id), self._lc_tool_cache)
202
+
203
+
204
+ class LangchainInstance:
205
+ def __init__(self, inst: Instance, backend_tools: list[BackendTool]) -> None:
206
+ self._inst = inst
207
+ self._lc_tool_cache = _build_lc_tool_map(backend_tools)
208
+ self.discover_tool = _wrap_discover_tool(inst.discover_tool)
209
+
210
+ @property
211
+ def instance_id(self) -> str:
212
+ return self._inst.instance_id
213
+
214
+ @property
215
+ def dataset_id(self) -> str:
216
+ return self._inst.dataset_id
217
+
218
+ def get_tools(self) -> list[StructuredTool]:
219
+ tools: list[StructuredTool] = [self.discover_tool]
220
+ for name in self._inst.discover_tool.discovered_names:
221
+ if name in self._lc_tool_cache:
222
+ tools.append(self._lc_tool_cache[name])
223
+ return tools
224
+
225
+ def session(self, id: str) -> LangchainSession:
226
+ return LangchainSession(self._inst.session(id), self._lc_tool_cache)
227
+
228
+ def namespace(self, id: str) -> LangchainNamespace:
229
+ return LangchainNamespace(self._inst.namespace(id), self._lc_tool_cache)
230
+
231
+
232
+ class LangchainDatasetRef:
233
+ def __init__(self, ref: DatasetRef) -> None:
234
+ self._ref = ref
235
+
236
+ async def register(self, input: RegisterInput) -> LangchainInstance:
237
+ inst = await self._ref.register(input)
238
+ return LangchainInstance(inst, input.tools)
239
+
240
+
241
+ class LangchainAgentified:
242
+ def __init__(self, ag: Agentified | None = None) -> None:
243
+ self._ag = ag or Agentified()
244
+
245
+ async def connect(self, server_url: str) -> None:
246
+ await self._ag.connect(server_url)
247
+
248
+ async def disconnect(self) -> None:
249
+ await self._ag.disconnect()
250
+
251
+ def dataset(self, name: str) -> LangchainDatasetRef:
252
+ return LangchainDatasetRef(self._ag.dataset(name))
253
+
254
+ async def register(self, input: RegisterInput) -> LangchainInstance:
255
+ inst = await self._ag.register(input)
256
+ return LangchainInstance(inst, input.tools)
257
+
258
+ async def __aenter__(self) -> LangchainAgentified:
259
+ return self
260
+
261
+ async def __aexit__(self, *args: Any) -> None:
262
+ await self.disconnect()
@@ -0,0 +1,26 @@
1
+ from agentified.models import BackendTool, RankedTool, ServerTool
2
+
3
+ TEST_URL = "http://localhost:9119"
4
+
5
+ TEST_TOOL = ServerTool(
6
+ name="get_weather",
7
+ description="Get weather for a city",
8
+ parameters={"type": "object", "properties": {"city": {"type": "string"}}, "required": ["city"]},
9
+ )
10
+
11
+ RANKED_TOOL = RankedTool(**TEST_TOOL.model_dump(), score=0.95)
12
+
13
+ BACKEND_TOOLS = [
14
+ BackendTool(
15
+ name="get_weather",
16
+ description="Get weather for a city",
17
+ parameters={"type": "object", "properties": {"city": {"type": "string"}}, "required": ["city"]},
18
+ handler=lambda args: {"temp": 22, "city": args.get("city", "")},
19
+ ),
20
+ BackendTool(
21
+ name="search_docs",
22
+ description="Search documentation",
23
+ parameters={"type": "object", "properties": {"query": {"type": "string"}}, "required": ["query"]},
24
+ handler=lambda args: {"results": [f"Doc about {args.get('query', '')}"]},
25
+ ),
26
+ ]