Skip to content
RasaGet a free licence
Guides for AI teams

Guide · AI product engineer

Build a card-policy assistant with LangChain and Rasa

Use your existing document search to answer card-policy questions, with Rasa asking for details and explaining the sources.

by Rod Rivera

About 5 minutes

Key takeaways (3)
  • Reuse your existing LangChain document search through a Rasa tool.
  • Check that returned policies apply to the customer’s question.
  • Check the task outcome as well as the quotation.

A customer asks whether they can replace a damaged card. Your LangChain search can find the policy; Rasa can ask for missing details and explain the answer. This tutorial connects the two and checks that personal-card questions receive personal-card policies.

A source quotation also needs to support what the assistant says. Our offline citation test pairs these exact fixture strings:

Scripted answer: Your card has already been replaced.

Cited policy: A damaged personal debit card can be replaced.

The checker returns evidence_checked. The quote is genuine; it offers no record of a completed replacement. In a separate scope control, a retriever constructed for a personal-card question returns a genuine business-card policy. In our controlled fault, the document requires the nominated company administrator’s approval. The application refuses it. Remove only the check on returned scope, and the conversation receives that business policy instead.

The native handoff controls keep the selected personal demo fixed and replace the retriever’s return with a real fixture document. A separate citation control attaches a genuine quotation to a scripted claim that the card has already been replaced. These independent controls inspect different handoffs:

Controlled inputCheck at the handoffObserved result
Personal scope and its personal policyReturned document scope retainedSource P-101 returned
Personal scope but business policy returnedReturned document scope retainedevidence_unavailable
Same personal scope and business policyOnly returned-scope guard removedSource B-201 and its company-administrator quotation returned
Genuine policy quote beside a false replacement claimCitation identity, revision and quotation membershipevidence_checked

This is a deliberately faulty return and a scripted answer, not an observed provider mistake. Selecting the scope when calling retrieval and checking the returned documents are separate decisions. Checking a quotation’s membership then answers another question: where the words came from. The final claim still needs to be assessed against those words and the task’s actual outcome.

If you already have useful LangChain retrieval, keep it. This example gives that retriever a Rasa conversation without treating the worker’s generated conclusion as a verified fact. The tool returns permitted source quotations; Mantle collects missing information and explains those sources to the user.

The example extends fictional Jacaranda Bank. Its card policies, public demo codes and document corpus are synthetic. No real account is accessed or changed.

Keep search where it already works

The native retriever uses LangChain’s BaseRetriever and returns Document objects through invoke. Each source carries an ID, scope and revision. The teaching fixture uses keyword search over two documents. Assess your existing retrieval on its own task corpus.

In your application, retain the existing index, loaders and ranking. Adapt its retriever to return the same metadata contract. Supply the permitted scope from your application before constructing a prompt, and keep the refusal controls when replacing the fixture.

Existing investmentResponsibility in this composition
LangChain retrieverFind permitted documents and retain their metadata
Existing worker prompt/model pipelineRemain available for worker tasks; its conclusion is not the tool’s source record
Application sessionSelect the allowed scope and refuse a change mid-conversation
Rasa skill and toolsAsk for missing information, retrieve sources and explain their limits
Account-action serviceRemain separate; no replacement tool exists here
User asks a policy question Rasa collects missing demo selection Application binds permitted scope Existing LangChain retriever Source ID, revision and exact quote Existing worker generation separate path optional existing pipeline Rasa explains the returned sources No account-action tool
  1. Rasa asks for the missing selection through the skill; the selector is a public demonstration value.
  2. Application code owns document scope. The retrieval tool’s argument is the question.
  3. The handoff returns source records without calling the worker’s generation pipeline.
FigureReuse retrieval, then explain its sources

Mantle still uses its configured model for the conversation. Returning quotes directly removes an additional worker-model call from this path.

Bind the source scope before retrieval

The application handoff asks for a demo code before it retrieves anything. Code 111111 selects a personal-card source; 222222 selects a business-card source. Once selected, that scope cannot change within the session.

After retrieval, the application checks every returned document against the bound scope. This also catches a faulty retriever:

The following abbreviated excerpt shows the checks at the retrieval boundary.

policy_worker/handoff.py — the retrieval boundary
documents = ScopedPolicyRetriever(scope=self.scope).invoke(question)
if not documents:
    return {"status": "no_evidence"}
if any(d.metadata.get("scope") != self.scope for d in documents):
    return {"status": "evidence_unavailable"}
return {"status": "sources_found", "sources": [
    {"id": d.metadata["id"], "revision": d.metadata["revision"], "quote": d.page_content}
    for d in documents
]}
  1. The application supplies the scope to the actual LangChain retriever.
  2. A wrong-scope document is refused before it becomes a conversation source.
  3. The model receives source records, with no worker-generated answer or account-action result.

The native mutation control makes the retriever return the business policy for a personal demo. The guarded handoff returns evidence_unavailable. Removing only this application’s scope comparison returns business source B-201, including its company-administrator requirement. Preserve that control when connecting your own search service.

A production service needs an authorised identity and a data-access policy before retrieval. Public codes and REST sender IDs do not provide those checks. This demo stores its selected scope in process memory; a restart clears it. Use durable, authorised session mapping if the conversation must survive a restart.

Return the existing sources through Mantle

The decorated tools separate selecting the demo from asking the policy question:

@tool(description="Retrieve quoted synthetic card-policy sources for the current demo. Only the user's question is an argument; the application chooses document scope. Does not replace a card or execute an account action.")
async def retrieve_card_policy(question: str, context: ToolContext = None) -> ToolResult:
    return ToolResult(llm_response=session().retrieve(question))

The tool gets its session from the current turn context. Its model-facing parameter is the question, with no scope parameter. The binding controls call the real decorated tool and native retriever; separate mocked sender contexts retain separate personal and business scopes. That proves the local binding, not the authenticity of a REST caller.

The skill asks for the missing demo code, explains only returned quotations, and names their source ID and revision. On missing evidence, it asks whether the user wants to clarify the question or stop. Its instruction also forbids claiming that a card replacement has occurred. Test the final wording in your own model conversation, using the sources returned by the retriever.

Run the pinned example

Check out the qualified source rather than copying a running environment:

git clone https://github.com/RasaHQ/rasa-community-resources.git
cd rasa-community-resources
git fetch origin e7db3283483f8836f28e540349f359f51c71df6f
git checkout e7db3283483f8836f28e540349f359f51c71df6f
python3 scripts/workspace.py start tutorials/frameworks-with-rasa/langchain-mantle --owner policy-conversation-demo
cd tutorials/frameworks-with-rasa/langchain-mantle
make test
make validate

If Mantle is new to you, start with the Rasa quickstart. Use Python 3.12 and the supplied lockfile. The selected conversation environment installs the worker from its local source, so this route needs one environment. The tests need no provider credentials. Set your own RASA_LICENSE for training and OPENAI_API_KEY for an actual model conversation; never commit them.

make train
make run

The supplied channel is text REST on port 5017. In another terminal:

curl -sS http://localhost:5017/webhooks/rest/webhook \
  -H 'Content-Type: application/json' \
  -d '{"sender":"policy-demo","message":"Can I replace a damaged card?"}'
curl -sS http://localhost:5017/webhooks/rest/webhook \
  -H 'Content-Type: application/json' \
  -d '{"sender":"policy-demo","message":"Use personal demo 111111"}'

These commands are a reader exercise, not a recorded model run. Keep the same sender when supplying the missing selection. Record the question, source and reply. Check for personal source P-101 with revision 2026-10-11, and check that the assistant does not say a replacement has happened. Use a new sender for business demo 222222; its source is B-201.

What the qualification establishes

The source qualification records 25 distinct passing controls: nine citation checks, five native retrieval checks, six application handoff checks and five Mantle binding checks. Project validation, licensed training and a cold archive load passed on Rasa Pro 3.21.0.dev5 with langchain-core 1.6.6.

The review-correction run printed these test summaries, first for the core suite and then for the decorated bindings:

Ran 20 tests in 0.021s

OK

Ran 5 tests in 7.927s

OK

The summaries show elapsed time for the local test suites.

The scripted model controls exercise actual LangChain interfaces without provider requests. The controls check source scope, quotation matching and tool bindings. When using your own model, evaluate whether each answer follows from the permitted sources and whether missing sources produce the intended clarification.

Can I keep the worker's existing model pipeline?

Yes. policy_worker/pipeline.py retains the retriever, prompt and supplied model. This conversation path calls the retriever directly. If you also return worker conclusions, distinguish them from source records and evaluate their support; a passing exact-quote check is not enough.

What happens when retrieval finds nothing?

The handoff returns no_evidence. The skill instructs Mantle to ask whether to clarify or stop. The native pipeline’s no-document control makes zero calls to its scripted worker model.

How do I retire the experiment?

Stop your own server, return to the repository root, and run python3 scripts/workspace.py retire tutorials/frameworks-with-rasa/langchain-mantle --owner policy-conversation-demo --inactive. Keep the source, locks and any recorded acceptance results; recreate only the environment you need for the next experiment.