> ## Documentation Index
> Fetch the complete documentation index at: https://powercontext.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# LangGraph

> 为异步 LangGraph 加入三个 Memory 工具和按轮次临时召回，不污染检查点。

> [!WARNING]
>
> 仅支持源码安装：`powercontext-langgraph==0.0.1` 尚未发布到 PyPI。官方安装 URL 跟随可变的 `master`，专项软件包测试也没有覆盖这条 VCS 安装路径。可复现性为 **Not validated**；请固定经过审阅的提交及其依赖。

适配器有三个部分：`powercontext_tools()` 返回三个工具，`PowerContextRecall` 准备模型输入，`PowerContextScope` 携带每个运行的配置。它连接到单独运行的 PowerContext Server。

## 成熟度与安装

| 项目 | 状态 |
| - | - |
| 软件包 | 仅源码安装；不在 PyPI |
| 版本 | `0.0.1` |
| 运行时 | Python `>=3.11,<4.0`；LangGraph `>=1.2,<2` |
| 调用方式 | **仅异步** |
| 可变分支的可复现性 | **Not validated** |
| 文档证据中的真实模型提供方往返验证 | **Not validated** |

从已审阅的源码提交安装 Server 和适配器：

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
uv tool install --force "powercontext[cli,server] @ git+https://github.com/oceanbase/powercontext.git@3e629eb163ba0ddf2935ce3b3fbaced0f9371977"
uv pip install "powercontext-langgraph @ git+https://github.com/oceanbase/powercontext.git@3e629eb163ba0ddf2935ce3b3fbaced0f9371977#subdirectory=integrations/langgraph"
powercontext server run
```

如果使用同一提交的本地检出：

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
git -C /path/to/powercontext checkout 3e629eb163ba0ddf2935ce3b3fbaced0f9371977
uv pip install /path/to/powercontext/integrations/langgraph
```

> [!NOTE]
>
> 固定提交消除了可变分支的歧义，但文档验证没有运行上游 VCS 安装路径，也没有运行外部模型提供方往返验证。请在部署环境中分别验证。

## 最小用法

下面的代码展示集成接缝，不是可独立运行的程序：请传入应用已经初始化的聊天 `model` 和 LangGraph `my_checkpointer`。接入这两个依赖前，可以先按 API 串讲写入共用 Memory，再用适配器专项测试直接调用 `PowerContextRecall`；上游测试不依赖外部模型提供方，也能验证这一段。

`create_react_agent` 已经会读取召回 Hook 写入的 `llm_input_messages` 频道。

```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
from langchain_core.messages import HumanMessage
from langgraph.prebuilt import create_react_agent
from powercontext_langgraph import PowerContextRecall, PowerContextScope, powercontext_tools

agent = create_react_agent(
    model,
    tools=powercontext_tools(),
    pre_model_hook=PowerContextRecall(),
    context_schema=PowerContextScope,
    checkpointer=my_checkpointer,
)

result = await agent.ainvoke(
    {
        "messages": [
            HumanMessage(content="What should happen after changing the OpenAPI contract?")
        ]
    },
    context=PowerContextScope(scope_id="project:powercontext-101"),
)
```

> [!WARNING]
>
> 使用 `ainvoke` 或 `astream`。召回和三个工具都是异步；`invoke` 与 `stream` 无法执行它们。

| 工具 | 行为 |
| - | - |
| `powercontext_search` | 搜索 Memory；默认 `limit=5`，最大 `20`；没有搜索 `mode` 参数 |
| `powercontext_remember` | 显式写入一条 Memory |
| `powercontext_context` | 为查询准备有界上下文 |

`powercontext_search` 与 `powercontext_context` 会把模型给出的查询截到 8,192 个 Python 字符。`powercontext_tools()` 按搜索、记住、上下文的顺序返回工具。

## 执行流程

```text theme={"theme":{"light":"github-light","dark":"github-dark"}}
latest human message
→ resolve per-run Scope and connection settings
→ prepare_context(query <= 8192 chars, max_bytes)
→ build ordered llm_input_messages
→ model step
→ optional model-requested search / remember / context tool
→ later model steps reuse the per-turn recall cache
```

每个模型步骤前都会调用 `PowerContextRecall`，但同一用户轮次最多准备一次。ReAct 循环使用 Scope 加最新用户消息 ID 作为缓存键；没有 ID 时使用消息 文本。键包含 Scope，因此两个租户的相同提示词不会共享召回内容。

召回成功时，输入最前面是一条标记为不可信历史证据的 `SystemMessage`，后面保持原始消息顺序。召回空结果或失败时，只传原始消息。

### 保持模型输入临时

召回把完整输入写到 `llm_input_messages`，从不把召回的内容块追加到 `messages`，所以检查点存储器不会积累过期上下文。

自定义图必须声明这个频道，在模型前执行召回，并由模型节点读取它：

```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
from typing import Annotated

from langchain_core.messages import BaseMessage
from langgraph.graph import START, StateGraph
from langgraph.graph.message import add_messages
from powercontext_langgraph import PowerContextRecall, PowerContextScope
from typing_extensions import TypedDict


class AgentState(TypedDict):
    messages: Annotated[list[BaseMessage], add_messages]
    llm_input_messages: list[BaseMessage]


async def call_model(state: AgentState):
    model_input = state.get("llm_input_messages") or state["messages"]
    response = await model.ainvoke(model_input)
    return {"messages": [response]}


builder = StateGraph(AgentState, context_schema=PowerContextScope)
builder.add_node("recall", PowerContextRecall())
builder.add_node("model", call_model)
builder.add_edge(START, "recall")
builder.add_edge("recall", "model")
graph = builder.compile(checkpointer=my_checkpointer)
```

如果只读 `state["messages"]`，模型会静默丢掉召回的上下文。把召回的内容块写回 `messages` 则破坏检查点边界。

## Scope 与配置

多租户服务应在每次调用传入 `PowerContextScope`。每个运行的 Scope、基础 URL、令牌和超时会覆盖对应环境变量。

```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
scope = PowerContextScope(
    scope_id="tenant:acme:project:api",
    base_url="https://context.example.com",
    token="opaque-server-token",
    timeout=8.0,
)

result = await agent.ainvoke(state, context=scope)
```

| 设置 | 默认值 |
| - | -: |
| `POWERCONTEXT_LANGGRAPH_BASE_URL` | `http://127.0.0.1:8000` |
| `POWERCONTEXT_LANGGRAPH_TOKEN` | 未设置 |
| `POWERCONTEXT_LANGGRAPH_SCOPE_ID` | 派生或报错 |
| `POWERCONTEXT_LANGGRAPH_TIMEOUT` | `10` 秒 |
| `POWERCONTEXT_LANGGRAPH_MAX_BYTES` | `8000`；允许范围 `512`–`32768` 字节 |

Scope 优先顺序：

```text theme={"theme":{"light":"github-light","dark":"github-dark"}}
PowerContextScope(scope_id=...)
→ POWERCONTEXT_LANGGRAPH_SCOPE_ID
→ normalized network Git origin
→ MissingScopeError
```

没有 local-path 后备路径。HTTP(S)、SSH、Git 和 SCP-like 网络远程会在移除凭证后做规范化；本地路径或 Windows drive 路径不是远程。过长的显式 Scope 会被替换为有界的 SHA-256 标识。

令牌采用不含 `Bearer ` 的不带前缀的令牌约定。适配器不严格校验该语法，部署前应拒绝错误配置。`repr=False` 只能减少意外显示，不是敏感信息 vault。除非调用方提供并显式标记可信传输层，共享的 `PowerContextClient` 会拒绝向非本机回环地址宿主使用明文 HTTP。

## 为什么没有 BaseStore

> [!NOTE]
>
> LangGraph `BaseStore` 要求 `GetOp`、caller-keyed `PutOp` upsert/删除和 `SearchOp`。只有搜索能自然映射到 PowerContext Memory。Memory 标识与版本由 Server 分配，并不提供 caller-key 读取、写入 或删除语义。

仅搜索的存储可能通过图组装，却在其他节点发出 读取 或 写入 时失败。因此适配器使用节点和工具，不占用 `compile(store=...)`。

## 失败、隐私与 Scope 边界

自动召回为 fail-open。Scope 缺失、配置错误、校验错误、认证失败、超时或 Server 错误都会跳过召回的内容块，让图继续使用原始消息。HTTP 401/403 会记录为配置问题，不会冒充空结果 Memory。

显式工具不会把失败伪装成空结果 success，也不会把异常抛穿图。它们返回简短、模型可见的的不可用或无效请求字符串，模型可以重试或继续。真正的搜索 miss 使用单独的 `(no matching PowerContext memory)` 结果。

> [!WARNING]
>
> 召回的内容可能包含旧用户或模型 文本。它是证据，不是当前指令、标识 proof 或工具授权信息。把它留在 `llm_input_messages`。Scope 只选择数据分区；需要隔离时，应从 已认证 租户/项目映射派生 Scope。

适配器不做自动采集。显式 `powercontext_remember` 仍会持久化模型选择的文本，因此要设置宿主工具权限和保留策略。

## 当前不支持或尚未验证

| 项目 | 状态 |
| - | - |
| 同步召回或工具 | **Unsupported** |
| 自动 Source 采集或刷新 | **Unsupported** |
| `BaseStore` 实现 | 按设计为 **Unsupported** |
| Handoff 与 Candidate Review | **Unsupported** |
| Experience 与 Skill 操作 | **Unsupported** |
| 从可变 `master` 进行可复现安装 | **Not validated** |
| 文档证据中的真实外部模型提供方往返验证 | **Not validated** |

## 完成检查清单

* Server 单独运行，源码安装的适配器固定到经过审阅的提交。
* 图使用 `ainvoke` 或 `astream`。
* `PowerContextRecall` 在模型前运行；自定义模型节点读取 `state.get("llm_input_messages") or state["messages"]`。
* 检查持久化的检查点，确认召回的内容块从不进入持久化的 `messages`。
* 每次调用收到预期 Scope；跨 Scope 测试证明相同提示词仍保持隔离。
* `MAX_BYTES` 在 512 到 32,768 之间，长查询会在 8,192 字符截断，远程传输层使用 HTTPS。
* Server 不可用时召回让图继续；显式工具失败对模型可见，并与空结果搜索区分。
* 显式 Memory 写入已纳入工具权限与保留策略。
* 部署记录没有混淆 Unsupported 与 Not validated。

> [!TIP]
>
> 先运行无模型召回 inspection。确认 Scope、Server reachability、临时状态和失败行为都可观察后，再加入真实模型提供方。

下一页进入 [LangChain](https://powercontext.mintlify.site/zh/frameworks/langchain.md)，比较 per-request 中间件召回与可选的已完成轮次 Source 采集。


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.