Usar APIs de Curto Prazo de Memória do Agente com LangGraph

Os agentes geralmente precisam preservar o contexto de trabalho recente sem passar a conversa completa de volta ao modelo em cada turno. Se você mantiver apenas a mensagem mais recente em estado, o modelo poderá facilmente perder o controle dos detalhes da tarefa anterior, do progresso intermediário ou do tópico do thread ativo.

O Oracle Agent Memory expõe dois ajudantes de curto prazo diferentes para este problema:

Quando o aplicativo mantém as mensagens N mais recentes como mensagens originais, você pode excluir essa mesma cauda do conteúdo derivado do cartão da seguinte forma:

#External-tail pattern:
#LLM Prompt = [context card, last N raw messages]
thread.get_context_card(except_last_messages=N, max_recent_messages=0)

O prompt resultante é uma lista que contém uma mensagem de cartão de contexto seguida pelas mensagens tail originais:

prompt_messages = [
    {"role": "user", "content": "<context_card>...</context_card>"},
    {"role": "user", "content": "The latest user message"},
    {"role": "assistant", "content": "The latest assistant reply"},
]

É preferível aproveitar o armazenamento em cache de prompt, pois você mantém o mesmo cartão de contexto para várias conversações e anexa novas mensagens ao prompt do LLM.

Alternativamente, você pode fazer o cartão autocontido e manter a cauda crua dentro dele da seguinte forma:

#Self-contained-card pattern:
#LLM Prompt = [context card only]
thread.get_context_card(
    except_last_messages=N,
    max_recent_messages=N,
)

O prompt resultante é uma lista com apenas uma mensagem: a mensagem de cartão de contexto, cuja seção <recent_messages> contém a cauda bruta:

prompt_messages = [
    {
        "role": "user",
        "content": (
            "<context_card>..."
            "<recent_messages>...</recent_messages>"
            "</context_card>"
        ),
    },
]

Observe que quando except_last_messages é diferente de zero, max_recent_messages deve ser zero ou o mesmo valor. Isso evita que a mesma cauda bruta seja fornecida dentro e fora do cartão de contexto.

Neste guia, você usará o middleware do LangGraph em torno de um agente pré-criado para que a Memória do Agente Oracle possa persistir seja girada automaticamente e injete um cartão de contexto Oracle quando o prompt em execução aumentar demais. Em outras palavras, o middleware compactará o prompt assim que passar por um limite configurado. Este exemplo seleciona get_context_card() porque a compactação deve preservar o contexto com reconhecimento de recuperação, não apenas uma recapitulação de transcrição.

Para ajustar quais tipos de registro aparecem nas informações relevantes do cartão de contexto, incluindo mensagens pesquisadas do thread atual, consulte Personalizar Conteúdo do Cartão de Contexto.

Importante: Resumos, cartões de contexto, registros recuperados e memórias extraídas automaticamente são texto derivado do modelo ou recuperado e devem ser tratados como não confiáveis. Quando a extração ou consolidação automática estiver ativada, esse texto também poderá ser reutilizado pelo SDK em prompts posteriores, como extração de memória, resumo, cartão de contexto ou prompts de agente, antes que o aplicativo tenha a oportunidade de revisar o valor intermediário específico. Revise as saídas que seu aplicativo consome, evite permitir que texto derivado da memória autorize ações privilegiadas e use memory_extraction_config=MemoryExtractionConfig(extract_memories=False) ou gravações explícitas de memória quando seu workflow exigir revisão antes que o texto derivado possa influenciar a extração posterior ou a construção do contexto.

Neste tutorial, você aprenderá a:

Dica: Para configurar o pacote, consulte Conceitos Básicos da Memória do Agente. Se você precisar de um Oracle AI Database local para este exemplo, siga Executar o Oracle AI Database localmente.

Configurar Memória do Agente e Modelos LangGraph

Crie um cliente de Memória do Agente Oracle com uma conexão ou pool do Oracle DB, configure um Embedder para pesquisa vetorial, forneça um LLM de memória Oracle para resolução de cartão de contexto e use ChatOpenAI para o agente LangGraph.

from typing import Any

import oracledb
from langchain.agents import create_agent
from langchain.agents.middleware import AgentMiddleware
from langchain_core.messages import AIMessage, BaseMessage, HumanMessage, RemoveMessage
from langchain_core.messages.utils import count_tokens_approximately
from langchain_openai import ChatOpenAI
from langgraph.graph.message import REMOVE_ALL_MESSAGES
from langgraph.runtime import Runtime

from oracleagentmemory.core import MemoryExtractionConfig, SchemaPolicy
from oracleagentmemory.core.embedders.embedder import Embedder
from oracleagentmemory.core.llms.llm import Llm
from oracleagentmemory.core.oracleagentmemory import OracleAgentMemory

embedder = Embedder(
    model="YOUR_EMBEDDING_MODEL",
    api_base="YOUR_EMBEDDING_BASE_URL",
    api_key="YOUR_EMBEDDING_API_KEY",
)
memory_llm = Llm(
    model="YOUR_MEMORY_LLM_MODEL",
    api_base="YOUR_MEMORY_LLM_BASE_URL",
    api_key="YOUR_MEMORY_LLM_API_KEY",
    temperature=0,
)
langgraph_llm = ChatOpenAI(
    model="YOUR_CHAT_MODEL",
    base_url="YOUR_CHAT_BASE_URL",
    api_key="YOUR_CHAT_API_KEY",
    temperature=0,
)
db_pool = oracledb.SessionPool(
    user="YOUR DB USER",
    password="YOUR DB PASSWORD",
    dsn="localhost:1521/...",
)
memory_store_id = "T_ST_MEMORY"

agent_memory = OracleAgentMemory(
    connection=db_pool,
    embedder=embedder,
    llm=memory_llm,
    schema_policy=SchemaPolicy.CREATE_IF_NECESSARY,
    memory_store_id=memory_store_id,
)
thread_id = "langgraph_short_term_demo"
user_id = "user_123"
agent_id = "assistant_456"
Referência de API: OracleAgentMemory OracleThread

Configurar Middleware e um Agente Predefinido

O middleware persiste e o novo usuário e assistente se transforma na Memória do Agente Oracle. Depois que o prompt em execução cruza um limite de token, ele compacta o estado substituindo a lista completa de mensagens por uma mensagem oracle_context_card sintética mais uma pequena cauda das rotações brutas mais recentes. Isso mantém o estado do LangGraph compacto enquanto ainda dá ao agente pré-criado contexto de curto prazo com reconhecimento de recuperação.

Este guia usa compactação baseada em token, mas o mesmo padrão pode ser adaptado a outras políticas, como compactar a cada poucos turnos ou após algum outro trigger específico do aplicativo.

def _message_text(message: BaseMessage | Any) -> str:
    content = getattr(message, "content", "")
    if isinstance(content, str):
        return content
    return str(content)

def _is_context_card_message(message: BaseMessage) -> bool:
    return isinstance(message, HumanMessage) and (
        getattr(message, "name", None) == "memory_context_card"
    )

class OracleShortTermMemoryMiddleware(AgentMiddleware):
    """Persist LangGraph turns and compact prompts with an OracleAgentMemory context card.

    Notes
    -----
    - ``before_model()`` receives the current LangGraph message state for this turn.
      After compaction, that state already includes the synthetic ``memory_context_card``
      message returned by a previous ``before_model()`` call.
    - The middleware strips that synthetic message back out before persisting or
      measuring token usage so OracleAgentMemory only stores real user/assistant turns
      and the compaction threshold is based on the organic conversation.
    - When compaction triggers, the middleware replaces the message history with one
      context-card message plus the most recent raw turns. On the next turn, that
      same injected message is seen again and filtered out before recomputing the
      next compacted prompt.
    """

    def __init__(
        self,
        memory: OracleAgentMemory,
        thread_id: str,
        user_id: str,
        agent_id: str,
        compaction_token_trigger: int,
        kept_message_count: int,
    ) -> None:
        self._thread = memory.create_thread(
            thread_id=thread_id,
            user_id=user_id,
            agent_id=agent_id,
            memory_extraction_config=MemoryExtractionConfig(
                context_summary_update_frequency=4
            ),
        )
        self._compaction_token_trigger = int(compaction_token_trigger)
        self._kept_message_count = int(kept_message_count)
        self._persisted_message_ids: set[str] = set()

    def before_model(
        self,
        state: dict[str, Any],
        runtime: Runtime[Any],
    ) -> dict[str, Any] | None:
        del runtime
        messages = list(state["messages"])
        #^ This will contain the context card message once the compaction occurs
        raw_messages = [message for message in messages if not _is_context_card_message(message)]
        self._persist_new_messages(raw_messages)

        #we exclude the context card from the token counting
        if count_tokens_approximately(raw_messages) < self._compaction_token_trigger:
            return None

        #External-tail pattern: exclude the raw tail from derived card
        #content, then append the original messages separately.
        context_card = self._thread.get_context_card(
            except_last_messages=self._kept_message_count,
            max_recent_messages=0,
        ).content
        #Self-contained-card alternative: use the matching count for
        #max_recent_messages and omit the raw-message tail from the return
        #value.
        #context_card = self._thread.get_context_card(
        #except_last_messages=self._kept_message_count,
        #max_recent_messages=self._kept_message_count,
        #).content
        if not context_card:
            context_card = "<context_card>\n  No relevant short-term context yet.\n</context_card>"
        return {
            "messages": [
                RemoveMessage(id=REMOVE_ALL_MESSAGES),  # Clear existing message state.
                HumanMessage(content=context_card, name="memory_context_card"),
                *raw_messages[-self._kept_message_count :],
            ]
        }

    def _persist_new_messages(self, messages: list[BaseMessage]) -> None:
        persisted: list[dict[str, str]] = []
        for message in messages:
            #Persist only the conversational roles that map directly to short-
            #term memory turns. Tool/system/synthetic messages are skipped here.
            role = (
                "user"
                if isinstance(message, HumanMessage)
                else "assistant" if isinstance(message, AIMessage) else None
            )
            if role is None:
                continue

            content = _message_text(message).strip()
            if not content:
                continue

            #LangGraph messages usually have stable IDs. When they do not, fall back
            #to a content-derived key so the same turn is not persisted repeatedly if
            #the caller reuses the returned message list across later invocations.
            message_id = str(getattr(message, "id", "") or f"{role}:{hash(content)}")
            if message_id in self._persisted_message_ids:
                continue

            #Track what this middleware instance has already written so each real turn
            #is added to Oracle once even though later turns may still carry the same
            #messages in the LangGraph state.
            self._persisted_message_ids.add(message_id)
            persisted.append({"role": role, "content": content})

        if persisted:
            self._thread.add_messages(persisted)

short_term_middleware = OracleShortTermMemoryMiddleware(
    memory=agent_memory,
    thread_id=thread_id,
    user_id=user_id,
    agent_id=agent_id,
    compaction_token_trigger=6000,
    kept_message_count=3,
)
agent = create_agent(
    model=langgraph_llm,
    tools=[],
    middleware=[short_term_middleware],
)

Resposta Posteriormente com Contexto Injetado pelo Middleware

Anexar usuário se volta para a lista de mensagens em execução do agente pré-criado e deixa o middleware decidir quando injetar um cartão de contexto. Quando o turno posterior chegar, o agente poderá responder de um estado compacto que ainda contenha o contexto de curto prazo da Memória do Agente Oracle. O exemplo imprime o cartão de contexto injetado e inclui uma amostra aparada para que você possa inspecionar qual compactação foi inserida no prompt sem despejar o bloco inteiro em linha.

messages: list[BaseMessage] = []

def print_current_context_card(messages: list[BaseMessage]) -> None:
    for message in messages:
        if _is_context_card_message(message):
            print(_message_text(message))
            return
    print("<context_card>\n  No injected context card yet.\n</context_card>")

def run_turn(user_text: str) -> str:
    messages.append(HumanMessage(content=user_text))
    result = agent.invoke({"messages": messages})
    messages[:] = list(result["messages"])
    assistant_message = next(
        message for message in reversed(messages) if isinstance(message, AIMessage)
    )
    return _message_text(assistant_message)

run_turn(
    "I'm Maya. I'm migrating our nightly invoice reconciliation workflow "
    "from cron jobs to LangGraph."
)
run_turn("The failing step right now is ledger enrichment after reconciliation.")
final_answer = run_turn(
    "What workflow am I migrating, which step is failing, and who am I?"
)

print_current_context_card(messages)
#<context_card>
#<topics>
#<topic>invoice reconciliation migration</topic>
#<topic>ledger enrichment failure</topic>
#...
#</topics>
#<summary>
#Maya is migrating the nightly invoice reconciliation workflow from cron jobs
#to LangGraph. The failing step is ledger enrichment after reconciliation.
#</summary>
#...
#</context_card>
print(final_answer)
#You're Maya, migrating your nightly invoice reconciliation workflow from cron jobs
#to LangGraph, and the ledger-enrichment step after reconciliation is currently failing.

Conclusão

Neste guia, aprendemos a distinguir get_summary() de get_context_card(), configurar o contexto de curto prazo da Memória do Agente Oracle em torno de um agente LangGraph pré-criado e permitir que o middleware compacte o prompt com um cartão de contexto quando a conversa aumentar demais para ser mantida literalmente.

→ Depois de aprender a adicionar contexto de thread de curto prazo a um fluxo do LangGraph, agora você pode prosseguir para Usar a Memória do Agente Oracle com o LangGraph.

Código Inteiro

Copie o código completo a seguir.

#Copyright © 2026 Oracle and/or its affiliates.
#This software is under the Apache License 2.0
#(LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0) or Universal Permissive License
#(UPL) 1.0 (LICENSE-UPL or https://oss.oracle.com/licenses/upl), at your option.

#Oracle Agent Memory Code Example - LangGraph Short-Term Memory
#--------------------------------------------------------------

##Configure Oracle Agent Memory and LangGraph models for short term context

from typing import Any

import oracledb
from langchain.agents import create_agent
from langchain.agents.middleware import AgentMiddleware
from langchain_core.messages import AIMessage, BaseMessage, HumanMessage, RemoveMessage
from langchain_core.messages.utils import count_tokens_approximately
from langchain_openai import ChatOpenAI
from langgraph.graph.message import REMOVE_ALL_MESSAGES
from langgraph.runtime import Runtime

from oracleagentmemory.core import MemoryExtractionConfig, SchemaPolicy
from oracleagentmemory.core.embedders.embedder import Embedder
from oracleagentmemory.core.llms.llm import Llm
from oracleagentmemory.core.oracleagentmemory import OracleAgentMemory

embedder = Embedder(
    model="YOUR_EMBEDDING_MODEL",
    api_base="YOUR_EMBEDDING_BASE_URL",
    api_key="YOUR_EMBEDDING_API_KEY",
)
memory_llm = Llm(
    model="YOUR_MEMORY_LLM_MODEL",
    api_base="YOUR_MEMORY_LLM_BASE_URL",
    api_key="YOUR_MEMORY_LLM_API_KEY",
    temperature=0,
)
langgraph_llm = ChatOpenAI(
    model="YOUR_CHAT_MODEL",
    base_url="YOUR_CHAT_BASE_URL",
    api_key="YOUR_CHAT_API_KEY",
    temperature=0,
)
db_pool = oracledb.SessionPool(
    user="YOUR DB USER",
    password="YOUR DB PASSWORD",
    dsn="localhost:1521/...",
)
memory_store_id = "T_ST_MEMORY"

agent_memory = OracleAgentMemory(
    connection=db_pool,
    embedder=embedder,
    llm=memory_llm,
    schema_policy=SchemaPolicy.CREATE_IF_NECESSARY,
    memory_store_id=memory_store_id,
)
thread_id = "langgraph_short_term_demo"
user_id = "user_123"
agent_id = "assistant_456"

##Configure short term memory middleware and a prebuilt LangGraph agent

def _message_text(message: BaseMessage | Any) -> str:
    content = getattr(message, "content", "")
    if isinstance(content, str):
        return content
    return str(content)

def _is_context_card_message(message: BaseMessage) -> bool:
    return isinstance(message, HumanMessage) and (
        getattr(message, "name", None) == "memory_context_card"
    )

class OracleShortTermMemoryMiddleware(AgentMiddleware):
    """Persist LangGraph turns and compact prompts with an OracleAgentMemory context card.

    Notes
    -----
    - ``before_model()`` receives the current LangGraph message state for this turn.
      After compaction, that state already includes the synthetic ``memory_context_card``
      message returned by a previous ``before_model()`` call.
    - The middleware strips that synthetic message back out before persisting or
      measuring token usage so OracleAgentMemory only stores real user/assistant turns
      and the compaction threshold is based on the organic conversation.
    - When compaction triggers, the middleware replaces the message history with one
      context-card message plus the most recent raw turns. On the next turn, that
      same injected message is seen again and filtered out before recomputing the
      next compacted prompt.
    """

    def __init__(
        self,
        memory: OracleAgentMemory,
        thread_id: str,
        user_id: str,
        agent_id: str,
        compaction_token_trigger: int,
        kept_message_count: int,
    ) -> None:
        self._thread = memory.create_thread(
            thread_id=thread_id,
            user_id=user_id,
            agent_id=agent_id,
            memory_extraction_config=MemoryExtractionConfig(
                context_summary_update_frequency=4
            ),
        )
        self._compaction_token_trigger = int(compaction_token_trigger)
        self._kept_message_count = int(kept_message_count)
        self._persisted_message_ids: set[str] = set()

    def before_model(
        self,
        state: dict[str, Any],
        runtime: Runtime[Any],
    ) -> dict[str, Any] | None:
        del runtime
        messages = list(state["messages"])
        #^ This will contain the context card message once the compaction occurs
        raw_messages = [message for message in messages if not _is_context_card_message(message)]
        self._persist_new_messages(raw_messages)

        #we exclude the context card from the token counting
        if count_tokens_approximately(raw_messages) < self._compaction_token_trigger:
            return None

        #External-tail pattern: exclude the raw tail from derived card
        #content, then append the original messages separately.
        context_card = self._thread.get_context_card(
            except_last_messages=self._kept_message_count,
            max_recent_messages=0,
        ).content
        #Self-contained-card alternative: use the matching count for
        #max_recent_messages and omit the raw-message tail from the return
        #value.
        #context_card = self._thread.get_context_card(
        #except_last_messages=self._kept_message_count,
        #max_recent_messages=self._kept_message_count,
        #).content
        if not context_card:
            context_card = "<context_card>\n  No relevant short-term context yet.\n</context_card>"
        return {
            "messages": [
                RemoveMessage(id=REMOVE_ALL_MESSAGES),  # Clear existing message state.
                HumanMessage(content=context_card, name="memory_context_card"),
                *raw_messages[-self._kept_message_count :],
            ]
        }

    def _persist_new_messages(self, messages: list[BaseMessage]) -> None:
        persisted: list[dict[str, str]] = []
        for message in messages:
            #Persist only the conversational roles that map directly to short-
            #term memory turns. Tool/system/synthetic messages are skipped here.
            role = (
                "user"
                if isinstance(message, HumanMessage)
                else "assistant" if isinstance(message, AIMessage) else None
            )
            if role is None:
                continue

            content = _message_text(message).strip()
            if not content:
                continue

            #LangGraph messages usually have stable IDs. When they do not, fall back
            #to a content-derived key so the same turn is not persisted repeatedly if
            #the caller reuses the returned message list across later invocations.
            message_id = str(getattr(message, "id", "") or f"{role}:{hash(content)}")
            if message_id in self._persisted_message_ids:
                continue

            #Track what this middleware instance has already written so each real turn
            #is added to Oracle once even though later turns may still carry the same
            #messages in the LangGraph state.
            self._persisted_message_ids.add(message_id)
            persisted.append({"role": role, "content": content})

        if persisted:
            self._thread.add_messages(persisted)

short_term_middleware = OracleShortTermMemoryMiddleware(
    memory=agent_memory,
    thread_id=thread_id,
    user_id=user_id,
    agent_id=agent_id,
    compaction_token_trigger=6000,
    kept_message_count=3,
)
agent = create_agent(
    model=langgraph_llm,
    tools=[],
    middleware=[short_term_middleware],
)

##Answer later turns with the middleware backed agent

messages: list[BaseMessage] = []

def print_current_context_card(messages: list[BaseMessage]) -> None:
    for message in messages:
        if _is_context_card_message(message):
            print(_message_text(message))
            return
    print("<context_card>\n  No injected context card yet.\n</context_card>")

def run_turn(user_text: str) -> str:
    messages.append(HumanMessage(content=user_text))
    result = agent.invoke({"messages": messages})
    messages[:] = list(result["messages"])
    assistant_message = next(
        message for message in reversed(messages) if isinstance(message, AIMessage)
    )
    return _message_text(assistant_message)

run_turn(
    "I'm Maya. I'm migrating our nightly invoice reconciliation workflow "
    "from cron jobs to LangGraph."
)
run_turn("The failing step right now is ledger enrichment after reconciliation.")
final_answer = run_turn(
    "What workflow am I migrating, which step is failing, and who am I?"
)

print_current_context_card(messages)
#<context_card>
#<topics>
#<topic>invoice reconciliation migration</topic>
#<topic>ledger enrichment failure</topic>
#...
#</topics>
#<summary>
#Maya is migrating the nightly invoice reconciliation workflow from cron jobs
#to LangGraph. The failing step is ledger enrichment after reconciliation.
#</summary>
#...
#</context_card>
print(final_answer)
#You're Maya, migrating your nightly invoice reconciliation workflow from cron jobs
#to LangGraph, and the ledger-enrichment step after reconciliation is currently failing.