agentkit-sdk-python 0.7.3__py3-none-any.whl → 0.7.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.
@@ -0,0 +1,29 @@
1
+ """Framework adapters for running third-party agents in AgentKit apps."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ from agentkit.frameworks._common import (
8
+ FrameworkBridgeError,
9
+ UnsupportedFrameworkAgentError,
10
+ )
11
+
12
+ __all__ = [
13
+ "FrameworkBridgeError",
14
+ "LangChainAgentkitBridge",
15
+ "LangGraphAgentkitBridge",
16
+ "UnsupportedFrameworkAgentError",
17
+ ]
18
+
19
+
20
+ def __getattr__(name: str) -> Any:
21
+ if name == "LangChainAgentkitBridge":
22
+ from agentkit.frameworks.langchain import LangChainAgentkitBridge
23
+
24
+ return LangChainAgentkitBridge
25
+ if name == "LangGraphAgentkitBridge":
26
+ from agentkit.frameworks.langgraph import LangGraphAgentkitBridge
27
+
28
+ return LangGraphAgentkitBridge
29
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
@@ -0,0 +1,136 @@
1
+ """Shared helpers for framework-to-ADK adapters."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import inspect
6
+ import json
7
+ from typing import Any
8
+
9
+ from google.adk.agents.invocation_context import InvocationContext
10
+ from google.adk.events import Event
11
+ from google.genai import types
12
+
13
+
14
+ class FrameworkBridgeError(RuntimeError):
15
+ """Base error raised by AgentKit framework adapters."""
16
+
17
+
18
+ class UnsupportedFrameworkAgentError(FrameworkBridgeError):
19
+ """Raised when an entry object does not expose a supported agent protocol."""
20
+
21
+
22
+ def user_text(ctx: InvocationContext) -> str:
23
+ content = ctx.user_content
24
+ if content is None or not content.parts:
25
+ return ""
26
+ texts: list[str] = []
27
+ for part in content.parts:
28
+ text = getattr(part, "text", None)
29
+ if text:
30
+ texts.append(text)
31
+ return "\n".join(texts)
32
+
33
+
34
+ def json_text(value: Any) -> str:
35
+ try:
36
+ return json.dumps(value, ensure_ascii=False, default=str)
37
+ except Exception:
38
+ return str(value)
39
+
40
+
41
+ def content_to_text(content: Any) -> str:
42
+ if content is None:
43
+ return ""
44
+ if isinstance(content, str):
45
+ return content
46
+ if isinstance(content, dict):
47
+ for key in ("text", "content", "output", "answer"):
48
+ if key in content:
49
+ text = content_to_text(content[key])
50
+ if text:
51
+ return text
52
+ return json_text(content)
53
+ if isinstance(content, list):
54
+ return "".join(content_to_text(item) for item in content)
55
+ text = getattr(content, "text", None)
56
+ if isinstance(text, str):
57
+ return text
58
+ return str(content)
59
+
60
+
61
+ def chunk_to_text(value: Any) -> str:
62
+ if value is None:
63
+ return ""
64
+ if isinstance(value, str):
65
+ return value
66
+ content = getattr(value, "content", None)
67
+ if content is not None:
68
+ return content_to_text(content)
69
+ text = getattr(value, "text", None)
70
+ if isinstance(text, str):
71
+ return text
72
+ if isinstance(value, dict):
73
+ for key in ("output", "answer", "text", "content"):
74
+ if key in value:
75
+ text = chunk_to_text(value[key])
76
+ if text:
77
+ return text
78
+ messages = value.get("messages")
79
+ if isinstance(messages, (list, tuple)) and messages:
80
+ return chunk_to_text(messages[-1])
81
+ for nested in value.values():
82
+ text = chunk_to_text(nested)
83
+ if text:
84
+ return text
85
+ return json_text(value)
86
+ if isinstance(value, (list, tuple)):
87
+ for item in reversed(value):
88
+ text = chunk_to_text(item)
89
+ if text:
90
+ return text
91
+ return ""
92
+ return str(value)
93
+
94
+
95
+ def adk_event(
96
+ ctx: InvocationContext,
97
+ author: str,
98
+ text: str,
99
+ *,
100
+ partial: bool,
101
+ ) -> Event:
102
+ return Event(
103
+ invocation_id=ctx.invocation_id,
104
+ author=author,
105
+ branch=ctx.branch,
106
+ partial=partial,
107
+ content=types.Content(role="model", parts=[types.Part(text=text)]),
108
+ )
109
+
110
+
111
+ async def maybe_await(value: Any) -> Any:
112
+ if inspect.isawaitable(value):
113
+ return await value
114
+ return value
115
+
116
+
117
+ def is_input_shape_error(exc: Exception) -> bool:
118
+ message = str(exc)
119
+ name = exc.__class__.__name__
120
+ return (
121
+ name == "InvalidUpdateError"
122
+ or "Invalid input type" in message
123
+ or "Must be a PromptValue, str, or list of BaseMessages" in message
124
+ or "Expected dict" in message
125
+ or "string indices must be integers" in message
126
+ or "'str' object is not subscriptable" in message
127
+ or "Input to ChatPromptTemplate is missing variables" in message
128
+ )
129
+
130
+
131
+ def chunk_delta(accumulated: str, text: str) -> str:
132
+ if not accumulated:
133
+ return text
134
+ if text.startswith(accumulated):
135
+ return text[len(accumulated) :]
136
+ return text
@@ -0,0 +1,170 @@
1
+ """LangChain adapter for AgentKit apps."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import AsyncGenerator
6
+ from typing import Any
7
+
8
+ from google.adk.agents.base_agent import BaseAgent
9
+ from google.adk.agents.invocation_context import InvocationContext
10
+ from google.adk.events import Event
11
+ from pydantic import PrivateAttr
12
+
13
+ from agentkit.frameworks._common import (
14
+ UnsupportedFrameworkAgentError,
15
+ adk_event,
16
+ chunk_delta,
17
+ chunk_to_text,
18
+ is_input_shape_error,
19
+ maybe_await,
20
+ user_text,
21
+ )
22
+
23
+
24
+ try:
25
+ from langchain_core.messages import HumanMessage
26
+ except ImportError: # pragma: no cover - depends on optional packages.
27
+ HumanMessage = None # type: ignore[assignment]
28
+
29
+
30
+ class LangChainAgentkitBridge(BaseAgent):
31
+ """Adapt a LangChain Runnable or callable to AgentKit's ADK runtime boundary."""
32
+
33
+ _runnable: Any = PrivateAttr()
34
+ _input_key: str = PrivateAttr(default="input")
35
+
36
+ def __init__(
37
+ self,
38
+ runnable: Any,
39
+ *,
40
+ name: str = "langchain_agent",
41
+ description: str = "LangChain agent adapted for AgentKit runtime",
42
+ input_key: str = "input",
43
+ ) -> None:
44
+ super().__init__(name=name, description=description)
45
+ self._runnable = runnable
46
+ self._input_key = input_key
47
+
48
+ def _input_candidates(self, payload: dict[str, Any], text_input: str) -> list[Any]:
49
+ candidates: list[Any] = [payload, text_input]
50
+ if HumanMessage is not None:
51
+ message = HumanMessage(content=text_input)
52
+ candidates.extend(([message], {"messages": [message]}))
53
+ return candidates
54
+
55
+ async def _call_once(self, payload: dict[str, Any], text_input: str) -> Any:
56
+ candidates = self._input_candidates(payload, text_input)
57
+ ainvoke = getattr(self._runnable, "ainvoke", None)
58
+ if callable(ainvoke):
59
+ last_error: Exception | None = None
60
+ for candidate in candidates:
61
+ try:
62
+ return await ainvoke(candidate)
63
+ except Exception as exc:
64
+ if not is_input_shape_error(exc):
65
+ raise
66
+ last_error = exc
67
+ if last_error is not None:
68
+ raise last_error
69
+
70
+ invoke = getattr(self._runnable, "invoke", None)
71
+ if callable(invoke):
72
+ last_error = None
73
+ for candidate in candidates:
74
+ try:
75
+ return invoke(candidate)
76
+ except Exception as exc:
77
+ if not is_input_shape_error(exc):
78
+ raise
79
+ last_error = exc
80
+ if last_error is not None:
81
+ raise last_error
82
+
83
+ if callable(self._runnable):
84
+ last_error = None
85
+ for candidate in candidates:
86
+ try:
87
+ return await maybe_await(self._runnable(candidate))
88
+ except Exception as exc:
89
+ if not is_input_shape_error(exc):
90
+ raise
91
+ last_error = exc
92
+ if last_error is not None:
93
+ raise last_error
94
+
95
+ raise UnsupportedFrameworkAgentError(
96
+ "LangChain entry must be a Runnable-like object exposing "
97
+ "astream, stream, ainvoke, or invoke, or a callable that accepts the user input."
98
+ )
99
+
100
+ async def _stream_chunks(self, payload: dict[str, Any], text_input: str):
101
+ astream = getattr(self._runnable, "astream", None)
102
+ if callable(astream):
103
+ last_error: Exception | None = None
104
+ for candidate in self._input_candidates(payload, text_input):
105
+ emitted = False
106
+ try:
107
+ async for chunk in astream(candidate):
108
+ emitted = True
109
+ yield chunk
110
+ return
111
+ except Exception as exc:
112
+ if emitted:
113
+ raise
114
+ if not is_input_shape_error(exc):
115
+ raise
116
+ last_error = exc
117
+ if last_error is not None:
118
+ raise last_error
119
+ return
120
+
121
+ stream = getattr(self._runnable, "stream", None)
122
+ if callable(stream):
123
+ last_error = None
124
+ for candidate in self._input_candidates(payload, text_input):
125
+ emitted = False
126
+ try:
127
+ for chunk in stream(candidate):
128
+ emitted = True
129
+ yield chunk
130
+ return
131
+ except Exception as exc:
132
+ if emitted:
133
+ raise
134
+ if not is_input_shape_error(exc):
135
+ raise
136
+ last_error = exc
137
+ if last_error is not None:
138
+ raise last_error
139
+
140
+ async def _run_async_impl(
141
+ self,
142
+ ctx: InvocationContext,
143
+ ) -> AsyncGenerator[Event, None]:
144
+ text_input = user_text(ctx)
145
+ payload = {self._input_key: text_input}
146
+ accumulated_text = ""
147
+ has_output = False
148
+ last_text = ""
149
+ streamed = False
150
+
151
+ async for chunk in self._stream_chunks(payload, text_input):
152
+ streamed = True
153
+ text = chunk_to_text(chunk)
154
+ if not text:
155
+ continue
156
+ delta = chunk_delta(accumulated_text, text)
157
+ if not delta:
158
+ continue
159
+ accumulated_text += delta
160
+ has_output = True
161
+ last_text = accumulated_text
162
+ yield adk_event(ctx, self.name, delta, partial=True)
163
+
164
+ if not streamed:
165
+ result = await self._call_once(payload, text_input)
166
+ last_text = chunk_to_text(result)
167
+
168
+ final_text = accumulated_text if has_output else last_text
169
+ if final_text:
170
+ yield adk_event(ctx, self.name, final_text, partial=False)
@@ -0,0 +1,349 @@
1
+ """LangGraph adapter for AgentKit apps."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import AsyncGenerator
6
+ import inspect
7
+ import json
8
+ from typing import Any
9
+
10
+ try:
11
+ from langchain_core.messages import HumanMessage
12
+ except ImportError as exc: # pragma: no cover - depends on optional packages.
13
+ raise ImportError(
14
+ "LangGraph bridge requires langgraph/langchain-core. "
15
+ "Install langgraph or use the requirements generated by agentkit migrate."
16
+ ) from exc
17
+
18
+ from google.adk.agents.base_agent import BaseAgent
19
+ from google.adk.agents.invocation_context import InvocationContext
20
+ from google.adk.events import Event, EventActions
21
+ from google.genai import types
22
+ from pydantic import PrivateAttr
23
+
24
+ try:
25
+ from langgraph.types import Command
26
+ except ImportError: # pragma: no cover - older optional langgraph versions.
27
+ Command = None # type: ignore[assignment]
28
+
29
+ from agentkit.frameworks._common import (
30
+ UnsupportedFrameworkAgentError,
31
+ adk_event,
32
+ chunk_delta,
33
+ chunk_to_text,
34
+ user_text,
35
+ )
36
+
37
+
38
+ LANGGRAPH_PENDING_INTERRUPT_STATE_KEY = "agentkit:langgraph:pending_interrupt"
39
+
40
+
41
+ def _method_kwargs(method: Any, kwargs: dict[str, Any]) -> dict[str, Any]:
42
+ try:
43
+ signature = inspect.signature(method)
44
+ except (TypeError, ValueError):
45
+ return kwargs
46
+
47
+ parameters = signature.parameters
48
+ accepts_kwargs = any(param.kind == inspect.Parameter.VAR_KEYWORD for param in parameters.values())
49
+ return {key: value for key, value in kwargs.items() if accepts_kwargs or key in parameters}
50
+
51
+
52
+ def _jsonable(value: Any) -> Any:
53
+ try:
54
+ json.dumps(value, ensure_ascii=False)
55
+ return value
56
+ except Exception:
57
+ return str(value)
58
+
59
+
60
+ def _interrupt_payload(data: Any) -> Any | None:
61
+ if not isinstance(data, dict) or "__interrupt__" not in data:
62
+ return None
63
+ interrupts = data.get("__interrupt__")
64
+ if not isinstance(interrupts, (list, tuple)) or not interrupts:
65
+ return interrupts
66
+ values: list[dict[str, Any]] = []
67
+ for interrupt in interrupts:
68
+ values.append(
69
+ {
70
+ "id": getattr(interrupt, "id", None),
71
+ "value": _jsonable(getattr(interrupt, "value", interrupt)),
72
+ }
73
+ )
74
+ return values
75
+
76
+
77
+ def _resume_value(text_input: str) -> Any:
78
+ stripped = text_input.strip()
79
+ if stripped.startswith(("{", "[")):
80
+ try:
81
+ return json.loads(stripped)
82
+ except json.JSONDecodeError:
83
+ return text_input
84
+ return text_input
85
+
86
+
87
+ class LangGraphAgentkitBridge(BaseAgent):
88
+ """Adapt a compiled LangGraph graph to AgentKit's ADK runtime boundary."""
89
+
90
+ _graph: Any = PrivateAttr()
91
+ _input_key: str | None = PrivateAttr(default=None)
92
+
93
+ def __init__(
94
+ self,
95
+ graph: Any,
96
+ *,
97
+ name: str = "langgraph_agent",
98
+ description: str = "LangGraph agent adapted for AgentKit runtime",
99
+ input_key: str | None = None,
100
+ ) -> None:
101
+ super().__init__(name=name, description=description)
102
+ self._graph = graph
103
+ self._input_key = input_key
104
+
105
+ def _input_payload(self, text_input: str) -> dict[str, Any]:
106
+ if self._input_key:
107
+ return {self._input_key: text_input}
108
+ return {"messages": [HumanMessage(content=text_input)]}
109
+
110
+ def _graph_config(self, ctx: InvocationContext) -> dict[str, Any] | None:
111
+ session = getattr(ctx, "session", None)
112
+ session_id = getattr(session, "id", None)
113
+ if not session_id:
114
+ return None
115
+ return {"configurable": {"thread_id": session_id}}
116
+
117
+ def _pending_interrupt(self, ctx: InvocationContext) -> Any | None:
118
+ session = getattr(ctx, "session", None)
119
+ state = getattr(session, "state", None)
120
+ if not isinstance(state, dict):
121
+ return None
122
+ pending = state.get(LANGGRAPH_PENDING_INTERRUPT_STATE_KEY)
123
+ if not pending:
124
+ return None
125
+ if isinstance(pending, dict) and pending.get("agent") not in {None, self.name}:
126
+ return None
127
+ return pending
128
+
129
+ def _run_payload(self, ctx: InvocationContext) -> Any:
130
+ text_input = user_text(ctx)
131
+ if self._pending_interrupt(ctx) is None:
132
+ return self._input_payload(text_input)
133
+ if Command is None:
134
+ raise UnsupportedFrameworkAgentError(
135
+ "LangGraph HITL resume requires langgraph.types.Command. "
136
+ "Upgrade langgraph or start a new AgentKit session."
137
+ )
138
+ return Command(resume=_resume_value(text_input))
139
+
140
+ def _interrupt_state_delta(self, ctx: InvocationContext, config: dict[str, Any] | None, interrupt: Any) -> dict[str, Any]:
141
+ session = getattr(ctx, "session", None)
142
+ session_id = getattr(session, "id", None)
143
+ if not session_id:
144
+ return {}
145
+ return {
146
+ LANGGRAPH_PENDING_INTERRUPT_STATE_KEY: {
147
+ "agent": self.name,
148
+ "thread_id": config.get("configurable", {}).get("thread_id") if config else session_id,
149
+ "interrupts": interrupt,
150
+ }
151
+ }
152
+
153
+ def _final_event(self, ctx: InvocationContext, text: str, *, clear_pending_interrupt: bool) -> Event:
154
+ event_kwargs: dict[str, Any] = {}
155
+ if clear_pending_interrupt:
156
+ event_kwargs["actions"] = EventActions(state_delta={LANGGRAPH_PENDING_INTERRUPT_STATE_KEY: None})
157
+ return Event(
158
+ invocation_id=ctx.invocation_id,
159
+ author=self.name,
160
+ branch=ctx.branch,
161
+ partial=False,
162
+ content=types.Content(role="model", parts=[types.Part(text=text)]),
163
+ **event_kwargs,
164
+ )
165
+
166
+ def _clear_pending_interrupt_event(self, ctx: InvocationContext) -> Event:
167
+ return Event(
168
+ invocation_id=ctx.invocation_id,
169
+ author=self.name,
170
+ branch=ctx.branch,
171
+ actions=EventActions(state_delta={LANGGRAPH_PENDING_INTERRUPT_STATE_KEY: None}),
172
+ )
173
+
174
+ async def _call_once(self, payload: Any, config: dict[str, Any] | None) -> Any:
175
+ ainvoke = getattr(self._graph, "ainvoke", None)
176
+ if callable(ainvoke):
177
+ return await ainvoke(payload, **_method_kwargs(ainvoke, {"config": config}))
178
+
179
+ invoke = getattr(self._graph, "invoke", None)
180
+ if callable(invoke):
181
+ return invoke(payload, **_method_kwargs(invoke, {"config": config}))
182
+
183
+ raise UnsupportedFrameworkAgentError(
184
+ "LangGraph entry must be a compiled graph-like object exposing "
185
+ "astream, stream, ainvoke, or invoke."
186
+ )
187
+
188
+ def _sync_stream(self, payload: Any, config: dict[str, Any] | None):
189
+ stream_fn = getattr(self._graph, "stream", None)
190
+ if not callable(stream_fn):
191
+ return None
192
+ try:
193
+ return stream_fn(
194
+ payload,
195
+ **_method_kwargs(
196
+ stream_fn,
197
+ {
198
+ "config": config,
199
+ "stream_mode": ["messages", "updates"],
200
+ "version": "v2",
201
+ },
202
+ ),
203
+ )
204
+ except TypeError:
205
+ try:
206
+ return stream_fn(
207
+ payload,
208
+ **_method_kwargs(
209
+ stream_fn,
210
+ {
211
+ "config": config,
212
+ "stream_mode": ["messages", "updates"],
213
+ },
214
+ ),
215
+ )
216
+ except TypeError:
217
+ return stream_fn(payload, **_method_kwargs(stream_fn, {"config": config}))
218
+
219
+ async def _stream_graph(self, payload: Any, config: dict[str, Any] | None, *, prefer_sync: bool = False):
220
+ if prefer_sync:
221
+ stream = self._sync_stream(payload, config)
222
+ if stream is not None:
223
+ for item in stream:
224
+ yield item
225
+ return
226
+
227
+ astream = getattr(self._graph, "astream", None)
228
+ if callable(astream):
229
+ try:
230
+ stream = astream(
231
+ payload,
232
+ **_method_kwargs(
233
+ astream,
234
+ {
235
+ "config": config,
236
+ "stream_mode": ["messages", "updates"],
237
+ "version": "v2",
238
+ },
239
+ ),
240
+ )
241
+ except TypeError:
242
+ try:
243
+ stream = astream(
244
+ payload,
245
+ **_method_kwargs(
246
+ astream,
247
+ {
248
+ "config": config,
249
+ "stream_mode": ["messages", "updates"],
250
+ },
251
+ ),
252
+ )
253
+ except TypeError:
254
+ stream = astream(payload, **_method_kwargs(astream, {"config": config}))
255
+ async for item in stream:
256
+ yield item
257
+ return
258
+
259
+ stream = self._sync_stream(payload, config)
260
+ if stream is not None:
261
+ for item in stream:
262
+ yield item
263
+
264
+ async def _run_async_impl(
265
+ self,
266
+ ctx: InvocationContext,
267
+ ) -> AsyncGenerator[Event, None]:
268
+ config = self._graph_config(ctx)
269
+ resume_pending = self._pending_interrupt(ctx) is not None
270
+ payload = self._run_payload(ctx)
271
+ accumulated_text = ""
272
+ has_output = False
273
+ last_update: Any = None
274
+ pending_update_text = ""
275
+ saw_messages = False
276
+ streamed = False
277
+
278
+ async for item in self._stream_graph(payload, config, prefer_sync=resume_pending):
279
+ streamed = True
280
+ mode = ""
281
+ data = item
282
+ if isinstance(item, tuple) and len(item) == 2 and isinstance(item[0], str):
283
+ mode, data = item
284
+ elif isinstance(item, dict) and isinstance(item.get("type"), str):
285
+ mode = item["type"]
286
+ data = item.get("data")
287
+
288
+ if mode == "messages":
289
+ message = data[0] if isinstance(data, tuple) and data else data
290
+ text = chunk_to_text(message)
291
+ if not text:
292
+ continue
293
+ delta = chunk_delta(accumulated_text, text)
294
+ if not delta:
295
+ continue
296
+ saw_messages = True
297
+ accumulated_text += delta
298
+ has_output = True
299
+ yield adk_event(ctx, self.name, delta, partial=True)
300
+ continue
301
+
302
+ if mode == "updates":
303
+ interrupt = _interrupt_payload(data)
304
+ if interrupt is not None:
305
+ state_delta = self._interrupt_state_delta(ctx, config, interrupt)
306
+ event_kwargs: dict[str, Any] = {}
307
+ if state_delta:
308
+ event_kwargs["actions"] = EventActions(state_delta=state_delta)
309
+ yield Event(
310
+ invocation_id=ctx.invocation_id,
311
+ author=self.name,
312
+ branch=ctx.branch,
313
+ interrupted=True,
314
+ error_code="LANGGRAPH_INTERRUPT",
315
+ error_message="LangGraph execution interrupted and requires resume input.",
316
+ custom_metadata={"langgraph_interrupt": interrupt},
317
+ **event_kwargs,
318
+ )
319
+ return
320
+ last_update = data
321
+ if saw_messages:
322
+ continue
323
+ text = chunk_to_text(data)
324
+ if text:
325
+ pending_update_text = text
326
+ continue
327
+
328
+ text = chunk_to_text(data)
329
+ if text:
330
+ delta = chunk_delta(accumulated_text, text)
331
+ if delta:
332
+ accumulated_text += delta
333
+ has_output = True
334
+ yield adk_event(ctx, self.name, delta, partial=True)
335
+
336
+ if streamed:
337
+ if saw_messages or has_output:
338
+ final_text = accumulated_text
339
+ else:
340
+ final_text = pending_update_text or chunk_to_text(last_update)
341
+ if final_text:
342
+ yield adk_event(ctx, self.name, final_text, partial=True)
343
+ else:
344
+ final_text = chunk_to_text(await self._call_once(payload, config))
345
+
346
+ if final_text:
347
+ yield self._final_event(ctx, final_text, clear_pending_interrupt=resume_pending)
348
+ elif resume_pending:
349
+ yield self._clear_pending_interrupt_event(ctx)
@@ -0,0 +1,6 @@
1
+ """Serving compatibility helpers for migrated framework agents."""
2
+
3
+ from agentkit.frameworks.serving.fastapi_mount import mount_legacy_fastapi_app
4
+ from agentkit.frameworks.serving.langserve import attach_langserve_compat_routes
5
+
6
+ __all__ = ["attach_langserve_compat_routes", "mount_legacy_fastapi_app"]
@@ -0,0 +1,65 @@
1
+ """Helpers for mounting an existing FastAPI app beside AgentKit routes."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ from typing import Any
7
+
8
+ from fastapi import FastAPI
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+
13
+ def _normalize_prefix(prefix: str) -> str:
14
+ if not prefix:
15
+ raise ValueError("legacy FastAPI mount prefix must not be empty.")
16
+ if not prefix.startswith("/"):
17
+ raise ValueError("legacy FastAPI mount prefix must start with '/'.")
18
+ if prefix != "/" and prefix.endswith("/"):
19
+ return prefix.rstrip("/")
20
+ return prefix
21
+
22
+
23
+ def mount_legacy_fastapi_app(
24
+ app: FastAPI,
25
+ legacy_app: Any,
26
+ *,
27
+ prefix: str = "/legacy",
28
+ allow_root: bool = False,
29
+ ) -> None:
30
+ """Mount a user-owned FastAPI app without rewriting its routes."""
31
+
32
+ normalized_prefix = _normalize_prefix(prefix)
33
+ if normalized_prefix == "/" and not allow_root:
34
+ raise ValueError(
35
+ "mounting a legacy FastAPI app at '/' can shadow AgentKit routes; "
36
+ "pass allow_root=True only when you intentionally want that behavior."
37
+ )
38
+ if not isinstance(legacy_app, FastAPI):
39
+ raise TypeError("legacy_app must be a fastapi.FastAPI instance.")
40
+
41
+ logger.info("Mounting legacy FastAPI app at %s", normalized_prefix)
42
+ app.mount(normalized_prefix, legacy_app)
43
+ _promote_mount(app, normalized_prefix)
44
+
45
+
46
+ def _promote_mount(app: FastAPI, path: str) -> None:
47
+ routes = app.router.routes
48
+ route_index = next(
49
+ (
50
+ index
51
+ for index, route in enumerate(routes)
52
+ if getattr(route, "path", None) == ("" if path == "/" else path)
53
+ ),
54
+ None,
55
+ )
56
+ if route_index is None:
57
+ return
58
+
59
+ route = routes.pop(route_index)
60
+ insert_at = len(routes)
61
+ for index, existing in enumerate(routes):
62
+ if getattr(existing, "path", None) in {"", "/"}:
63
+ insert_at = index
64
+ break
65
+ routes.insert(insert_at, route)
@@ -0,0 +1,502 @@
1
+ """LangServe-like HTTP compatibility routes for migrated LangChain agents."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ import inspect
7
+ import json
8
+ import logging
9
+ from collections.abc import AsyncGenerator
10
+ from typing import Any
11
+ from uuid import uuid4
12
+
13
+ from fastapi import FastAPI, HTTPException, Request
14
+ from fastapi.encoders import jsonable_encoder
15
+ from fastapi.responses import JSONResponse, StreamingResponse
16
+
17
+ from agentkit.frameworks._common import (
18
+ UnsupportedFrameworkAgentError,
19
+ chunk_to_text,
20
+ is_input_shape_error,
21
+ maybe_await,
22
+ )
23
+
24
+ logger = logging.getLogger(__name__)
25
+
26
+ try:
27
+ from langchain_core.messages import HumanMessage
28
+ except ImportError: # pragma: no cover - optional dependency.
29
+ HumanMessage = None # type: ignore[assignment]
30
+
31
+
32
+ def _normalize_prefix(prefix: str) -> str:
33
+ if prefix == "/":
34
+ return ""
35
+ if prefix and not prefix.startswith("/"):
36
+ raise ValueError("LangServe compatibility prefix must start with '/'.")
37
+ return prefix.rstrip("/")
38
+
39
+
40
+ def _jsonable(value: Any) -> Any:
41
+ try:
42
+ return jsonable_encoder(value)
43
+ except Exception:
44
+ return json.loads(json.dumps(value, ensure_ascii=False, default=str))
45
+
46
+
47
+ async def _request_json(request: Request) -> Any:
48
+ try:
49
+ return await request.json()
50
+ except Exception as exc:
51
+ raise HTTPException(status_code=400, detail="Request body must be valid JSON.") from exc
52
+
53
+
54
+ def _extract_input(payload: Any) -> Any:
55
+ if isinstance(payload, dict) and "input" in payload:
56
+ return payload["input"]
57
+ return payload
58
+
59
+
60
+ def _request_options(payload: Any) -> tuple[Any, dict[str, Any]]:
61
+ if not isinstance(payload, dict):
62
+ return None, {}
63
+ config = payload.get("config")
64
+ kwargs = payload.get("kwargs")
65
+ return config, kwargs if isinstance(kwargs, dict) else {}
66
+
67
+
68
+ def _input_candidates(value: Any, input_key: str) -> list[Any]:
69
+ candidates: list[Any] = []
70
+ if isinstance(value, dict):
71
+ candidates.append(value)
72
+ elif input_key != "input":
73
+ candidates.append({input_key: value})
74
+ candidates.append(value)
75
+ else:
76
+ candidates.append(value)
77
+ candidates.append({input_key: value})
78
+
79
+ if isinstance(value, str) and HumanMessage is not None:
80
+ message = HumanMessage(content=value)
81
+ candidates.extend(([message], {"messages": [message]}))
82
+
83
+ deduped: list[Any] = []
84
+ for candidate in candidates:
85
+ if not any(candidate == existing for existing in deduped):
86
+ deduped.append(candidate)
87
+ return deduped
88
+
89
+
90
+ def _method_kwargs(method: Any, config: Any, extra_kwargs: dict[str, Any]) -> dict[str, Any]:
91
+ if config is None and not extra_kwargs:
92
+ return {}
93
+
94
+ try:
95
+ signature = inspect.signature(method)
96
+ except (TypeError, ValueError):
97
+ return {"config": config, **extra_kwargs} if config is not None else dict(extra_kwargs)
98
+
99
+ parameters = signature.parameters
100
+ accepts_kwargs = any(param.kind == inspect.Parameter.VAR_KEYWORD for param in parameters.values())
101
+ call_kwargs: dict[str, Any] = {}
102
+ if config is not None and (accepts_kwargs or "config" in parameters):
103
+ call_kwargs["config"] = config
104
+ for key, value in extra_kwargs.items():
105
+ if accepts_kwargs or key in parameters:
106
+ call_kwargs[key] = value
107
+ return call_kwargs
108
+
109
+
110
+ async def _call_with_candidates(
111
+ runnable: Any,
112
+ method_name: str,
113
+ value: Any,
114
+ *,
115
+ input_key: str,
116
+ config: Any = None,
117
+ extra_kwargs: dict[str, Any] | None = None,
118
+ ) -> Any:
119
+ method = getattr(runnable, method_name, None)
120
+ if not callable(method):
121
+ raise UnsupportedFrameworkAgentError(f"LangChain entry does not expose {method_name}.")
122
+
123
+ call_kwargs = _method_kwargs(method, config, extra_kwargs or {})
124
+ last_error: Exception | None = None
125
+ for candidate in _input_candidates(value, input_key):
126
+ try:
127
+ return await maybe_await(method(candidate, **call_kwargs))
128
+ except Exception as exc:
129
+ if not is_input_shape_error(exc):
130
+ raise
131
+ last_error = exc
132
+ if last_error is not None:
133
+ raise last_error
134
+ raise UnsupportedFrameworkAgentError(f"LangChain entry did not accept any {method_name} input shape.")
135
+
136
+
137
+ async def _invoke(
138
+ runnable: Any,
139
+ value: Any,
140
+ *,
141
+ input_key: str,
142
+ config: Any = None,
143
+ extra_kwargs: dict[str, Any] | None = None,
144
+ ) -> Any:
145
+ if callable(getattr(runnable, "ainvoke", None)):
146
+ return await _call_with_candidates(
147
+ runnable,
148
+ "ainvoke",
149
+ value,
150
+ input_key=input_key,
151
+ config=config,
152
+ extra_kwargs=extra_kwargs,
153
+ )
154
+ if callable(getattr(runnable, "invoke", None)):
155
+ return await _call_with_candidates(
156
+ runnable,
157
+ "invoke",
158
+ value,
159
+ input_key=input_key,
160
+ config=config,
161
+ extra_kwargs=extra_kwargs,
162
+ )
163
+ if callable(runnable):
164
+ call_kwargs = _method_kwargs(runnable, config, extra_kwargs or {})
165
+ last_error: Exception | None = None
166
+ for candidate in _input_candidates(value, input_key):
167
+ try:
168
+ return await maybe_await(runnable(candidate, **call_kwargs))
169
+ except Exception as exc:
170
+ if not is_input_shape_error(exc):
171
+ raise
172
+ last_error = exc
173
+ if last_error is not None:
174
+ raise last_error
175
+ raise UnsupportedFrameworkAgentError(
176
+ "LangChain entry must expose ainvoke/invoke or be callable for LangServe compatibility."
177
+ )
178
+
179
+
180
+ async def _stream_from_method(
181
+ method: Any,
182
+ value: Any,
183
+ *,
184
+ input_key: str,
185
+ config: Any = None,
186
+ extra_kwargs: dict[str, Any] | None = None,
187
+ ) -> AsyncGenerator[Any, None]:
188
+ call_kwargs = _method_kwargs(method, config, extra_kwargs or {})
189
+ last_error: Exception | None = None
190
+ for candidate in _input_candidates(value, input_key):
191
+ emitted = False
192
+ try:
193
+ stream = method(candidate, **call_kwargs)
194
+ if inspect.isawaitable(stream):
195
+ stream = await stream
196
+ if hasattr(stream, "__aiter__"):
197
+ async for chunk in stream:
198
+ emitted = True
199
+ yield chunk
200
+ else:
201
+ for chunk in stream:
202
+ emitted = True
203
+ yield chunk
204
+ return
205
+ except Exception as exc:
206
+ if emitted:
207
+ raise
208
+ if not is_input_shape_error(exc):
209
+ raise
210
+ last_error = exc
211
+ if last_error is not None:
212
+ raise last_error
213
+
214
+
215
+ def _event_stream_kwargs(method: Any) -> dict[str, str]:
216
+ try:
217
+ signature = inspect.signature(method)
218
+ except (TypeError, ValueError):
219
+ return {}
220
+ version = signature.parameters.get("version")
221
+ if version is not None and version.default is inspect.Parameter.empty:
222
+ return {"version": "v2"}
223
+ return {}
224
+
225
+
226
+ async def _stream_from_method_with_candidates(
227
+ method: Any,
228
+ value: Any,
229
+ *,
230
+ input_key: str,
231
+ kwargs: dict[str, Any] | None = None,
232
+ config: Any = None,
233
+ extra_kwargs: dict[str, Any] | None = None,
234
+ ) -> AsyncGenerator[Any, None]:
235
+ call_kwargs = {**_method_kwargs(method, config, extra_kwargs or {}), **(kwargs or {})}
236
+ last_error: Exception | None = None
237
+ for candidate in _input_candidates(value, input_key):
238
+ emitted = False
239
+ try:
240
+ stream = method(candidate, **call_kwargs)
241
+ if inspect.isawaitable(stream):
242
+ stream = await stream
243
+ if hasattr(stream, "__aiter__"):
244
+ async for chunk in stream:
245
+ emitted = True
246
+ yield chunk
247
+ else:
248
+ for chunk in stream:
249
+ emitted = True
250
+ yield chunk
251
+ return
252
+ except Exception as exc:
253
+ if emitted:
254
+ raise
255
+ if not is_input_shape_error(exc):
256
+ raise
257
+ last_error = exc
258
+ if last_error is not None:
259
+ raise last_error
260
+
261
+
262
+ async def _stream_chunks(
263
+ runnable: Any,
264
+ value: Any,
265
+ *,
266
+ input_key: str,
267
+ config: Any = None,
268
+ extra_kwargs: dict[str, Any] | None = None,
269
+ ) -> AsyncGenerator[Any, None]:
270
+ astream = getattr(runnable, "astream", None)
271
+ if callable(astream):
272
+ async for chunk in _stream_from_method(
273
+ astream,
274
+ value,
275
+ input_key=input_key,
276
+ config=config,
277
+ extra_kwargs=extra_kwargs,
278
+ ):
279
+ yield chunk
280
+ return
281
+
282
+ stream = getattr(runnable, "stream", None)
283
+ if callable(stream):
284
+ async for chunk in _stream_from_method(
285
+ stream,
286
+ value,
287
+ input_key=input_key,
288
+ config=config,
289
+ extra_kwargs=extra_kwargs,
290
+ ):
291
+ yield chunk
292
+ return
293
+
294
+ yield await _invoke(runnable, value, input_key=input_key, config=config, extra_kwargs=extra_kwargs)
295
+
296
+
297
+ async def _batch(
298
+ runnable: Any,
299
+ values: list[Any],
300
+ *,
301
+ input_key: str,
302
+ config: Any = None,
303
+ extra_kwargs: dict[str, Any] | None = None,
304
+ ) -> list[Any]:
305
+ abatch = getattr(runnable, "abatch", None)
306
+ if callable(abatch):
307
+ try:
308
+ return await maybe_await(abatch(values, **_method_kwargs(abatch, config, extra_kwargs or {})))
309
+ except Exception:
310
+ logger.debug("LangServe compat abatch failed; falling back to per-item invoke.", exc_info=True)
311
+
312
+ batch = getattr(runnable, "batch", None)
313
+ if callable(batch):
314
+ try:
315
+ return await maybe_await(batch(values, **_method_kwargs(batch, config, extra_kwargs or {})))
316
+ except Exception:
317
+ logger.debug("LangServe compat batch failed; falling back to per-item invoke.", exc_info=True)
318
+
319
+ return await asyncio.gather(
320
+ *[_invoke(runnable, value, input_key=input_key, config=config, extra_kwargs=extra_kwargs) for value in values]
321
+ )
322
+
323
+
324
+ def _sse(event: str, data: Any) -> str:
325
+ return f"event: {event}\ndata: {json.dumps(_jsonable(data), ensure_ascii=False)}\n\n"
326
+
327
+
328
+ def _route_path(prefix: str, path: str) -> str:
329
+ return f"{prefix}{path}" if prefix else path
330
+
331
+
332
+ def _promote_route(app: FastAPI, endpoint: Any, path: str) -> None:
333
+ routes = app.router.routes
334
+ route_index = next(
335
+ (
336
+ index
337
+ for index, route in enumerate(routes)
338
+ if getattr(route, "endpoint", None) is endpoint and getattr(route, "path", None) == path
339
+ ),
340
+ None,
341
+ )
342
+ if route_index is None:
343
+ return
344
+
345
+ route = routes.pop(route_index)
346
+ insert_at = len(routes)
347
+ for index, existing in enumerate(routes):
348
+ existing_path = getattr(existing, "path", None)
349
+ if existing_path == path or existing_path in {"", "/"}:
350
+ insert_at = index
351
+ break
352
+ routes.insert(insert_at, route)
353
+
354
+
355
+ def attach_langserve_compat_routes(
356
+ app: FastAPI,
357
+ runnable: Any,
358
+ *,
359
+ input_key: str = "input",
360
+ prefix: str = "",
361
+ ) -> None:
362
+ """Attach a conservative LangServe-like HTTP contract to a FastAPI app."""
363
+
364
+ normalized_prefix = _normalize_prefix(prefix)
365
+ logger.info("Attaching LangServe compatibility routes at prefix %s", normalized_prefix or "/")
366
+
367
+ async def invoke(request: Request) -> JSONResponse:
368
+ payload = await _request_json(request)
369
+ config, extra_kwargs = _request_options(payload)
370
+ run_id = str(uuid4())
371
+ try:
372
+ output = await _invoke(
373
+ runnable,
374
+ _extract_input(payload),
375
+ input_key=input_key,
376
+ config=config,
377
+ extra_kwargs=extra_kwargs,
378
+ )
379
+ except UnsupportedFrameworkAgentError as exc:
380
+ logger.info("LangServe compat invoke rejected unsupported runnable: %s", exc)
381
+ raise HTTPException(status_code=422, detail=str(exc)) from exc
382
+ except Exception:
383
+ logger.exception("LangServe compat invoke failed.")
384
+ raise
385
+ return JSONResponse({"output": _jsonable(output), "metadata": {"run_id": run_id}})
386
+
387
+ async def batch(request: Request) -> JSONResponse:
388
+ payload = await _request_json(request)
389
+ config, extra_kwargs = _request_options(payload)
390
+ raw_inputs = payload.get("inputs") if isinstance(payload, dict) else None
391
+ if not isinstance(raw_inputs, list):
392
+ raise HTTPException(status_code=400, detail="Batch request body must include an 'inputs' list.")
393
+ outputs = await _batch(runnable, raw_inputs, input_key=input_key, config=config, extra_kwargs=extra_kwargs)
394
+ return JSONResponse(
395
+ {
396
+ "output": _jsonable(outputs),
397
+ "metadata": {"run_ids": [str(uuid4()) for _ in outputs]},
398
+ }
399
+ )
400
+
401
+ async def stream(request: Request) -> StreamingResponse:
402
+ payload = await _request_json(request)
403
+ value = _extract_input(payload)
404
+ config, extra_kwargs = _request_options(payload)
405
+
406
+ async def events() -> AsyncGenerator[str, None]:
407
+ try:
408
+ async for chunk in _stream_chunks(
409
+ runnable,
410
+ value,
411
+ input_key=input_key,
412
+ config=config,
413
+ extra_kwargs=extra_kwargs,
414
+ ):
415
+ yield _sse("data", chunk)
416
+ yield _sse("end", {})
417
+ except Exception as exc:
418
+ logger.exception("LangServe compat stream failed.")
419
+ yield _sse("error", {"message": str(exc)})
420
+
421
+ return StreamingResponse(events(), media_type="text/event-stream")
422
+
423
+ async def stream_events(request: Request) -> StreamingResponse:
424
+ payload = await _request_json(request)
425
+ value = _extract_input(payload)
426
+ config, extra_kwargs = _request_options(payload)
427
+ astream_events = getattr(runnable, "astream_events", None)
428
+
429
+ async def events() -> AsyncGenerator[str, None]:
430
+ try:
431
+ if callable(astream_events):
432
+ async for event in _stream_from_method_with_candidates(
433
+ astream_events,
434
+ value,
435
+ input_key=input_key,
436
+ kwargs=_event_stream_kwargs(astream_events),
437
+ config=config,
438
+ extra_kwargs=extra_kwargs,
439
+ ):
440
+ yield _sse("data", event)
441
+ else:
442
+ async for chunk in _stream_chunks(
443
+ runnable,
444
+ value,
445
+ input_key=input_key,
446
+ config=config,
447
+ extra_kwargs=extra_kwargs,
448
+ ):
449
+ yield _sse(
450
+ "data",
451
+ {
452
+ "event": "on_chain_stream",
453
+ "data": {"chunk": _jsonable(chunk), "text": chunk_to_text(chunk)},
454
+ },
455
+ )
456
+ yield _sse("end", {})
457
+ except Exception as exc:
458
+ logger.exception("LangServe compat stream_events failed.")
459
+ yield _sse("error", {"message": str(exc)})
460
+
461
+ return StreamingResponse(events(), media_type="text/event-stream")
462
+
463
+ async def stream_log(request: Request) -> StreamingResponse:
464
+ payload = await _request_json(request)
465
+ value = _extract_input(payload)
466
+ config, extra_kwargs = _request_options(payload)
467
+ astream_log = getattr(runnable, "astream_log", None)
468
+ if not callable(astream_log):
469
+ logger.info("LangServe compat /stream_log requested but runnable does not expose astream_log.")
470
+ raise HTTPException(
471
+ status_code=501,
472
+ detail="stream_log requires a runnable exposing astream_log; this compatibility layer does not synthesize LangServe logs.",
473
+ )
474
+
475
+ async def events() -> AsyncGenerator[str, None]:
476
+ try:
477
+ async for event in _stream_from_method_with_candidates(
478
+ astream_log,
479
+ value,
480
+ input_key=input_key,
481
+ config=config,
482
+ extra_kwargs=extra_kwargs,
483
+ ):
484
+ yield _sse("data", event)
485
+ yield _sse("end", {})
486
+ except Exception as exc:
487
+ logger.exception("LangServe compat stream_log failed.")
488
+ yield _sse("error", {"message": str(exc)})
489
+
490
+ return StreamingResponse(events(), media_type="text/event-stream")
491
+
492
+ routes: list[tuple[str, Any]] = [
493
+ ("/invoke", invoke),
494
+ ("/batch", batch),
495
+ ("/stream", stream),
496
+ ("/stream_events", stream_events),
497
+ ("/stream_log", stream_log),
498
+ ]
499
+ for path, endpoint in routes:
500
+ full_path = _route_path(normalized_prefix, path)
501
+ app.add_api_route(full_path, endpoint, methods=["POST"])
502
+ _promote_route(app, endpoint, full_path)
agentkit/version.py CHANGED
@@ -12,4 +12,4 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
- VERSION = "0.7.3"
15
+ VERSION = "0.7.4"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agentkit-sdk-python
3
- Version: 0.7.3
3
+ Version: 0.7.4
4
4
  Summary: Python SDK for transforming any AI agent into a production-ready application. Framework-agnostic primitives for runtime, memory, authentication, and tools with volcengine-managed infrastructure.
5
5
  Author-email: Xiangrui Cheng <innsdcc@gmail.com>, Yumeng Bao <baoyumeng.123@gmail.com>, Yaozheng Fang <fangyozheng@gmail.com>, Guodong Li <cu.eric.lee@gmail.com>
6
6
  License: Apache License
@@ -244,6 +244,13 @@ Requires-Dist: pyreadline3; sys_platform == "win32"
244
244
  Provides-Extra: extensions
245
245
  Provides-Extra: toolkit
246
246
  Provides-Extra: dev
247
+ Provides-Extra: langchain
248
+ Requires-Dist: langchain; extra == "langchain"
249
+ Provides-Extra: langgraph
250
+ Requires-Dist: langgraph; extra == "langgraph"
251
+ Provides-Extra: frameworks
252
+ Requires-Dist: langchain; extra == "frameworks"
253
+ Requires-Dist: langgraph; extra == "frameworks"
247
254
  Dynamic: license-file
248
255
 
249
256
  <div align="center">
@@ -1,6 +1,6 @@
1
1
  agentkit/__init__.py,sha256=l27ZMDslc3VhmmnPZJyrqVvTDoZ0LqhCtM5hw0caHcU,1021
2
2
  agentkit/errors.py,sha256=UWlXv0ZsXd_oLTMlxuf4u9-XkTdRYeVZueE--Z3JsHU,1214
3
- agentkit/version.py,sha256=OQzKFr_NITBj2G12DNlZMV3W-rXCEcSJL-iScxwGUXk,653
3
+ agentkit/version.py,sha256=S9zUkhZqaZ0llqw_JF_CXK5OsV7vd2EIpTBKb38hgCI,653
4
4
  agentkit/apps/__init__.py,sha256=-oXTjxV3gejpJ8pffTUcUAXw0LfxKCYZJk-_hIJhtLM,1921
5
5
  agentkit/apps/base_app.py,sha256=3hZZExL1wyTGWveJEZZoqXN086MzmkVS_WU1vyulIWg,754
6
6
  agentkit/apps/utils.py,sha256=IzimIDmT6FS6PV9MLimPh46mq5zT-EjVmnYPxBdyEq0,1934
@@ -40,6 +40,13 @@ agentkit/client/__init__.py,sha256=WHKgNGkpYsJp0xD20drYcxAMDclwDqrfURqxEVv0V1Y,1
40
40
  agentkit/client/base_agentkit_client.py,sha256=NV1m2FtboUcMdVUJL2ep75nAwDQ42roiWgl3YKaUSn8,2955
41
41
  agentkit/client/base_iam_client.py,sha256=F3ggcZjt14CAcgDYpncF2XGr1zTGnhaOzqEJl4NGHwU,2432
42
42
  agentkit/client/base_service_client.py,sha256=zpPUidGMDRAMDnSVjrX7BwuOp4BQdAEk0b8HoXks0TE,15049
43
+ agentkit/frameworks/__init__.py,sha256=NPT7kxcY7shMyA2m1QoxhXzAc9JEvDCNFyWupedTCSY,808
44
+ agentkit/frameworks/_common.py,sha256=0L0cyBQxtlOdi3GbQ_UgaOoHWwenztlKrxUsyvV3hXw,3859
45
+ agentkit/frameworks/langchain.py,sha256=Tcy2HGYqKmcugDSn3OMaD_6uxhsJtZI3o5E8Rw7UJrg,5947
46
+ agentkit/frameworks/langgraph.py,sha256=CHhaX6DcE8McC6Cs31gexEJHqJJ7cFiqh8D6vyQ8FVE,12770
47
+ agentkit/frameworks/serving/__init__.py,sha256=EaR0jzP6np8v1gyD6CYlaRoIY1KgBaFosqCmRUgMc2E,302
48
+ agentkit/frameworks/serving/fastapi_mount.py,sha256=LV6gJunwd7aU8MFdDYSxeaIUYWvYwMl2Am1J07ddP0A,1958
49
+ agentkit/frameworks/serving/langserve.py,sha256=R2R0g95LHcSFJ1BnOm2cxnULv3wQaZNUdm5Aqd-Oq5s,17238
43
50
  agentkit/platform/__init__.py,sha256=XD1YoXWJPpLGrP410Tb_psPFKR3LUo3-47UlqayUumo,3464
44
51
  agentkit/platform/configuration.py,sha256=lclvtiUUO1D6iwapmPygYGhNJCMfkqt4fHPBCIEqUhE,23378
45
52
  agentkit/platform/console_urls.py,sha256=JcnjwyuxN5c4RuXcPevsxy6aInurVFtchqbibJBqjSk,1125
@@ -237,9 +244,9 @@ agentkit/utils/redact.py,sha256=RAtiggxH74eb1Da6Gg0G5SLTVWRaIE5RqYnCozSZozQ,2167
237
244
  agentkit/utils/request.py,sha256=1IGxPS3BSCKk1K1_j862_v34xzLEx04CIvrLVb1MsGQ,1625
238
245
  agentkit/utils/template_utils.py,sha256=Qjg9V6dEpjAd_yYezIIPwuDsDeeMmp6XTMn5ZECifNc,6336
239
246
  agentkit/utils/ve_sign.py,sha256=JfKig7zni8KvdhgbLJXm3Zy_ttyzKrYGUmBY_82_A4Q,12762
240
- agentkit_sdk_python-0.7.3.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
241
- agentkit_sdk_python-0.7.3.dist-info/METADATA,sha256=dBa7oZ41ZZ-ndAa9pUUT9ub2f-1IMwXiuc8qZbK8V-A,19611
242
- agentkit_sdk_python-0.7.3.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
243
- agentkit_sdk_python-0.7.3.dist-info/entry_points.txt,sha256=fhzZUsvsLXeB4mPaa0SBQiTBqFT404uDAKPaePAOUAE,58
244
- agentkit_sdk_python-0.7.3.dist-info/top_level.txt,sha256=ipy8JF-QQ-V0C1oRFLxsyaW8zwrasfJ-zjAh9vgOc7U,9
245
- agentkit_sdk_python-0.7.3.dist-info/RECORD,,
247
+ agentkit_sdk_python-0.7.4.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
248
+ agentkit_sdk_python-0.7.4.dist-info/METADATA,sha256=s47zJ5ciuJGmqJoi4WcqSMqz8lUM4HWt3eAHAQ4kKGo,19880
249
+ agentkit_sdk_python-0.7.4.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
250
+ agentkit_sdk_python-0.7.4.dist-info/entry_points.txt,sha256=fhzZUsvsLXeB4mPaa0SBQiTBqFT404uDAKPaePAOUAE,58
251
+ agentkit_sdk_python-0.7.4.dist-info/top_level.txt,sha256=ipy8JF-QQ-V0C1oRFLxsyaW8zwrasfJ-zjAh9vgOc7U,9
252
+ agentkit_sdk_python-0.7.4.dist-info/RECORD,,