LangChain.

AgentLoop integrates into existing LangChain chains with two additions: a memory-injection step before the prompt template, and a callback handler on the LLM. Together they bring relevant past corrections into the prompt and log every turn for review.

Install

shell
pip install agentloop-py agentloop-py-langchain langchain-openai
# or langchain-anthropic, langchain-google-genai, etc.,
# depending on your provider

The complete pattern

python
from agentloop import AgentLoop
from agentloop_langchain import (
    AgentLoopMemoryInjector,
    AgentLoopCallbackHandler,
)
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI

loop = AgentLoop(api_key="ak_live_...")

prompt = ChatPromptTemplate.from_messages([
    ("system",
     "You are a helpful assistant.\n\n"
     "Trusted facts from past corrections:\n{agentloop_memories}"),
    ("user", "{question}"),
])

llm = ChatOpenAI(model="gpt-4o").with_config(
    callbacks=[AgentLoopCallbackHandler(loop=loop)],
)

chain = (
    AgentLoopMemoryInjector(loop=loop, query_field="question")
    | prompt
    | llm
)

chain.invoke({"question": "Can enterprise customers request a refund after 60 days?
    

What happens at runtime

  1. Retrieve. The injector reads the user's question, calls loop.search(), and receives the most relevant memories (e.g. "Enterprise refunds must be requested within 30 days of billing").
  2. Inject. Those facts get rendered into the input under the agentloop_memories key, which the prompt template uses as a slot.
  3. Call. The LLM receives the augmented prompt and answers correctly, even if its training data alone would have led it astray.
  4. Log. The callback captures the turn in on_llm_end and posts to /v1/turns for review.
  5. Loop. Reviewers add annotations in the dashboard. Annotations create memories. The next time someone asks a similar question, step 1 retrieves the relevant memory — the agent improves without retraining.

Async chains

For chain.ainvoke(), use AsyncAgentLoopCallbackHandler and pass an AsyncAgentLoop from agentloop.aio to the injector. If you accidentally pass a sync client, network calls dispatch to a thread-pool executor automatically.

Per-call options

python
chain.invoke(
    {"question": "..."},
    config={"metadata": {"agentloop": {
        "user_id": "u_42",
        "session_id": "sess_xyz",
        "signals": {"thumbs_down": True},
    }}},
)

Recognized keys: user_id, search_user_id, session_id, signals, tags, metadata, skip.

user_id is log-only by default. Pass search_user_id to also scope retrieval to that user (per-user personalization). See the Patterns page for recipes.

Verifying memory retrieval

To confirm the injector is pulling memories correctly, drop a debug step between the prompt and the LLM. It prints the final assembled prompt — including any retrieved facts — before the LLM call.

python
from langchain_core.runnables import RunnableLambda

def debug_print_prompt(prompt_value):
    if hasattr(prompt_value, "messages"):
        for msg in prompt_value.messages:
            print(f"[{msg.type}] {msg.content[:300]}")
    return prompt_value

chain = (
    AgentLoopMemoryInjector(loop=loop, query_field="question")
    | prompt
    | RunnableLambda(debug_print_prompt)   # ← debug step
    | llm
)

If the system message shows (none yet) where retrieved facts should be, the search returned nothing.Add a more relevant annotation in the dashboard, confirm the memory is active, or try a question that is closer to the scenario you want AgentLoop to retrieve.

Remember

Remove the RunnableLambda step before deploying — it's a debug aid, not part of the integration.