agentship-langgraph 0.0.1__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.
- agentship_langgraph/__init__.py +17 -0
- agentship_langgraph/agent.py +77 -0
- agentship_langgraph/durability.py +58 -0
- agentship_langgraph/engine.py +725 -0
- agentship_langgraph/mcp.py +159 -0
- agentship_langgraph/mcp_auth.py +93 -0
- agentship_langgraph/models.py +201 -0
- agentship_langgraph/py.typed +0 -0
- agentship_langgraph/studio.py +33 -0
- agentship_langgraph/templates/__init__.py +68 -0
- agentship_langgraph/templates/autonomous_tpl.py +86 -0
- agentship_langgraph/templates/graph.py +100 -0
- agentship_langgraph/templates/graph_config.py +76 -0
- agentship_langgraph/templates/graph_supervisor.py +377 -0
- agentship_langgraph/templates/single.py +40 -0
- agentship_langgraph/testing.py +130 -0
- agentship_langgraph/tools.py +185 -0
- agentship_langgraph/tracing.py +414 -0
- agentship_langgraph-0.0.1.dist-info/METADATA +24 -0
- agentship_langgraph-0.0.1.dist-info/RECORD +22 -0
- agentship_langgraph-0.0.1.dist-info/WHEEL +4 -0
- agentship_langgraph-0.0.1.dist-info/entry_points.txt +2 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""The LangGraph engine package — AgentShip's default real-model engine.
|
|
2
|
+
|
|
3
|
+
:class:`~agentship_langgraph.engine.LangGraphEngine` builds the agent — either its
|
|
4
|
+
own default single-agent graph (system prompt + user input → chat model → answer)
|
|
5
|
+
or, when a developer subclasses :class:`~agentship_langgraph.agent.LangGraphAgent`
|
|
6
|
+
and implements ``build_graph(model, tools)``, that author's native graph — over a
|
|
7
|
+
LiteLLM-backed model resolved by :mod:`agentship_langgraph.models`. It ships as the
|
|
8
|
+
``agentship-langgraph`` dist and is discovered via the ``agentship.engines``
|
|
9
|
+
entry-point group, so ``engine: langgraph`` in a spec resolves here.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
from .agent import LangGraphAgent
|
|
15
|
+
from .engine import LangGraphEngine
|
|
16
|
+
|
|
17
|
+
__all__ = ["LangGraphAgent", "LangGraphEngine"]
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"""The custom-authoring base class: :class:`LangGraphAgent`.
|
|
2
|
+
|
|
3
|
+
This is the answer to CRUX 1 (DESIGN §4.3): *"how do I build a custom LangGraph
|
|
4
|
+
agent while still getting AgentShip's wired services?"* A developer subclasses
|
|
5
|
+
:class:`LangGraphAgent` and writes native LangGraph in :meth:`build_graph` — nodes,
|
|
6
|
+
edges, conditional routing, subgraphs, anything LangGraph does. The engine's
|
|
7
|
+
``build(spec)`` resolves the ``model`` and ``tools`` and hands them to
|
|
8
|
+
``build_graph`` as plain, already-wired params; the author never wires a vendor
|
|
9
|
+
themselves and never loses native power. Per-request services (memory recall,
|
|
10
|
+
tracing, caller identity) are read from ``RunContext`` inside the nodes, not passed
|
|
11
|
+
in a bundle — there is deliberately no ``EngineKit``.
|
|
12
|
+
|
|
13
|
+
The author points a spec's ``code:`` at a factory that returns a configured
|
|
14
|
+
subclass instance (which carries its own ``.spec``); the core threads that object
|
|
15
|
+
to :meth:`~agentship_langgraph.engine.LangGraphEngine.build`, which calls
|
|
16
|
+
``build_graph``. Every LangChain/LangGraph type stays inside this adapter — the
|
|
17
|
+
vendor-neutral core never sees one.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
from abc import ABC, abstractmethod
|
|
23
|
+
from typing import TYPE_CHECKING
|
|
24
|
+
|
|
25
|
+
from agentship.errors import CapabilityError
|
|
26
|
+
|
|
27
|
+
if TYPE_CHECKING:
|
|
28
|
+
from agentship.spec import AgentSpec
|
|
29
|
+
from langchain_core.language_models.chat_models import BaseChatModel
|
|
30
|
+
from langchain_core.tools import BaseTool
|
|
31
|
+
from langgraph.graph import StateGraph
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class LangGraphAgent(ABC):
|
|
35
|
+
"""Base class a developer subclasses to author a native LangGraph agent.
|
|
36
|
+
|
|
37
|
+
A subclass implements :meth:`build_graph` in native LangGraph; the engine
|
|
38
|
+
binding is fixed to ``"langgraph"`` and ``run``/``stream``/``resume`` are
|
|
39
|
+
provided by the engine adapter (the author never writes them). Construct one
|
|
40
|
+
with its :class:`~agentship.spec.AgentSpec`; the spec's ``engine`` must be
|
|
41
|
+
``"langgraph"`` or construction fails fast with a
|
|
42
|
+
:class:`~agentship.errors.CapabilityError` (never a silent mis-bind).
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
#: The engine this agent is bound to. Fixed for every LangGraph agent so the
|
|
46
|
+
#: subclass never has to restate it; the spec's ``engine`` must still match.
|
|
47
|
+
engine_name = "langgraph"
|
|
48
|
+
|
|
49
|
+
def __init__(self, spec: AgentSpec) -> None:
|
|
50
|
+
"""Bind ``spec`` to this agent, asserting its engine is ``langgraph``.
|
|
51
|
+
|
|
52
|
+
The custom-authoring path only makes sense when the spec targets this
|
|
53
|
+
engine; a spec whose ``engine`` is anything else is a misconfiguration, so
|
|
54
|
+
it fails fast here (declare, don't fake) rather than compiling a graph the
|
|
55
|
+
wrong engine would try to drive.
|
|
56
|
+
"""
|
|
57
|
+
if spec.engine != self.engine_name:
|
|
58
|
+
raise CapabilityError(
|
|
59
|
+
f"{type(self).__name__} is a LangGraph agent, but its spec sets "
|
|
60
|
+
f"engine: {spec.engine!r} — set engine: {self.engine_name!r} or use the "
|
|
61
|
+
f"matching engine's agent base class."
|
|
62
|
+
)
|
|
63
|
+
self.spec = spec
|
|
64
|
+
|
|
65
|
+
@abstractmethod
|
|
66
|
+
def build_graph(self, model: BaseChatModel, tools: list[BaseTool]) -> StateGraph:
|
|
67
|
+
"""Return a native LangGraph ``StateGraph`` built over the wired pieces.
|
|
68
|
+
|
|
69
|
+
The engine passes ``model`` (a LiteLLM-backed, cost-traced chat model it
|
|
70
|
+
resolved from the spec / ``RunContext.routed_model``) and ``tools`` (adapted
|
|
71
|
+
MCP/python tools — an empty list until Phase 03 wires tool execution). Build
|
|
72
|
+
and return your graph using them; the engine compiles it (attaching the
|
|
73
|
+
checkpointer/store it resolved, plus callbacks) and drives ``run``/``stream``.
|
|
74
|
+
You may return an already-compiled graph — the engine detects that and reuses
|
|
75
|
+
it. Read per-request services (memory, tracing, caller identity) from
|
|
76
|
+
``RunContext`` via ``get_run_context()`` inside your nodes.
|
|
77
|
+
"""
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""The durable-checkpointer substrate for the LangGraph engine (Phase 02 · C4).
|
|
2
|
+
|
|
3
|
+
One decision lives here: which LangGraph checkpointer a run uses. With no database configured
|
|
4
|
+
(dev, tests, the Week-1 slice) it is an in-memory saver; with ``AGENT_SESSION_STORE_URI`` set it
|
|
5
|
+
is a real ``AsyncPostgresSaver`` whose per-node checkpoints survive a crash so ``engine.resume``
|
|
6
|
+
can continue from the frontier. Because the Postgres saver owns a live connection pool that must
|
|
7
|
+
stay open for as long as the graph runs, this is exposed as an **async context manager**, not a
|
|
8
|
+
factory that returns a bare object.
|
|
9
|
+
|
|
10
|
+
``setup=True`` runs the saver's schema DDL. That is a migration, so per the project's "never migrate
|
|
11
|
+
silently" policy the caller opts in — it is invoked from ``agentship doctor`` / first-boot, never on
|
|
12
|
+
every process start. See phase 02 §C4 and DESIGN §6/§13.6.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from collections.abc import AsyncIterator
|
|
18
|
+
from contextlib import asynccontextmanager
|
|
19
|
+
from typing import TYPE_CHECKING
|
|
20
|
+
|
|
21
|
+
from langgraph.checkpoint.memory import InMemorySaver
|
|
22
|
+
|
|
23
|
+
if TYPE_CHECKING: # only for typing; the Postgres saver is imported lazily so a bare install works
|
|
24
|
+
from langgraph.checkpoint.base import BaseCheckpointSaver
|
|
25
|
+
|
|
26
|
+
#: One process-wide in-memory saver for the no-database path. It must be **shared** across
|
|
27
|
+
#: ``open_checkpointer`` calls so an in-process ``run`` then ``resume`` see the same checkpoints
|
|
28
|
+
#: (a fresh saver per call would forget them). Entries are keyed by ``thread_id`` internally, so
|
|
29
|
+
#: distinct runs never collide. This is single-process only — real cross-process durability needs
|
|
30
|
+
#: the Postgres saver.
|
|
31
|
+
_MEMORY_SAVER = InMemorySaver()
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@asynccontextmanager
|
|
35
|
+
async def open_checkpointer(
|
|
36
|
+
conninfo: str | None, *, setup: bool = False
|
|
37
|
+
) -> AsyncIterator[BaseCheckpointSaver]:
|
|
38
|
+
"""Yield the checkpointer for this environment, holding its resources for the ``with`` body.
|
|
39
|
+
|
|
40
|
+
``conninfo`` is the Postgres connection string (typically ``AGENT_SESSION_STORE_URI``). Blank
|
|
41
|
+
or ``None`` → an :class:`InMemorySaver` (no cross-process durability, fine for dev/tests). A
|
|
42
|
+
real connection string → an ``AsyncPostgresSaver`` bound to a live pool that is opened on enter
|
|
43
|
+
and closed on exit. ``setup=True`` runs the saver's table DDL first (gated migration; Postgres
|
|
44
|
+
only — the in-memory saver needs no schema).
|
|
45
|
+
"""
|
|
46
|
+
if not conninfo:
|
|
47
|
+
# Shared, not fresh — so an in-process run→resume sees the same checkpoints.
|
|
48
|
+
yield _MEMORY_SAVER
|
|
49
|
+
return
|
|
50
|
+
|
|
51
|
+
# Imported lazily: psycopg + langgraph-checkpoint-postgres are the optional [postgres] extra,
|
|
52
|
+
# so a bare install that never touches durability does not need them present.
|
|
53
|
+
from langgraph.checkpoint.postgres.aio import AsyncPostgresSaver
|
|
54
|
+
|
|
55
|
+
async with AsyncPostgresSaver.from_conn_string(conninfo) as saver:
|
|
56
|
+
if setup:
|
|
57
|
+
await saver.setup()
|
|
58
|
+
yield saver
|