Skip to content

Build an enhanced chat app

A release assistant reads a rollout policy and a security ticket. Asked whether the team can ship, it answers: “The launch is blocked by the security review.” Neither document contains that sentence. The answer combines a rule in one source with a fact in another.

Add an evidence panel to the chat: select that conclusion and inspect which passages influenced it. This is the selection-to-evidence interaction used in Strahl’s chat workspace.

Research chatIllustrative UI
You

Can we ship?

Assistant

The launch is blocked by the security review.

Selected answer → source evidence
Sources behind this selection

“The launch is blocked by the security review.”

Rollout policy · supplied context

Production rollout requires all critical findings to be closed.

Security ticket · supplied context

SEC-17 remains open at critical severity.

Inspect the original passage before relying on the answer.

Concept mockup: selected answer text in amber, source evidence in blue. Illustrative content, not a recorded Prism result.

The following small, illustrative transcript stands in for an answer saved by your chat backend. In your app, use the exact context and assistant output from the original generation.

Source Supplied text
Rollout policy Production rollout requires all critical findings to be closed.
Security ticket SEC-17 remains open at critical severity.
Assistant output The launch is blocked by the security review.

Searching for the answer verbatim finds neither source. Retrieval can find related documents; the attribution request asks which supplied passages influenced this particular output.

Set up the attribute function from Quickstart in prism.py. Save this as chat_evidence.py and run python chat_evidence.py:

from prism import attribute
messages = [
{"role": "system", "content": "Answer release questions using the supplied records."},
{"role": "user", "content": (
"Rollout policy: Production rollout requires all critical findings to be closed.\n"
"Security ticket: SEC-17 remains open at critical severity.\n"
"Can we ship?"
)},
{"role": "assistant", "content": "The launch is blocked by the security review."},
]
answer = messages[-1]["content"]
result = attribute(
conversation={"messages": messages},
target={
"ref": {"message_index": 2, "field": "content"},
"span": {"start": 0, "end": len(answer)},
},
)
for excerpt in result["excerpts"]:
print(excerpt["ref"], excerpt["text"])
for highlight in excerpt["highlights"]:
print(" →", highlight["text"])

This makes a live, billable attribution request. The mockup illustrates the intended interface; it does not predict the excerpts or scores this call returns.

Store each completed answer with an immutable conversation snapshot. When a reader selects text, send its answer ID and selection to your backend; resolve them against that snapshot before calling Prism. Return the result to the panel associated with that same selection.

Render excerpt.text and shade its highlights. The highlight coordinates are relative to the original message field, so subtract excerpt.start when drawing inside an excerpt. This browser helper constructs a highlighted passage without interpreting source text as HTML:

function renderExcerpt(excerpt) {
const passage = document.createElement("p");
const characters = Array.from(excerpt.text);
let cursor = 0;
for (const highlight of excerpt.highlights) {
const start = highlight.start - excerpt.start;
const end = highlight.end - excerpt.start;
passage.append(characters.slice(cursor, start).join(""));
const mark = document.createElement("mark");
mark.textContent = characters.slice(start, end).join("");
passage.append(mark);
cursor = end;
}
passage.append(characters.slice(cursor).join(""));
return passage;
}

The panel now connects a synthesized conclusion to inspectable source passages. Use effective_span to show the output actually analyzed. Keep source titles and “Open original” links in your application’s snapshot mapping.

Selections in rendered Markdown must map back to original text. JavaScript uses UTF-16 positions, while Prism spans count Unicode code points; Array.from handles the latter when rendering. Keep attribution associated with the answer and selection that requested it so a late response cannot overwrite newer evidence.

For measured results from longer conversations, see the legal conclusion and financial synthesis examples.