Task: Place cards in chat via a custom agent tag
Place cards in chat via a custom agent tag
Add a structured tag with the place ID in the agent's responses and render the place card in the chat based on it.
Objective
Make the output of places in the AI chat manageable and structured: instead of arbitrary text/links, the agent should be able to explicitly reference a place by its internal ID, and the chat interface should replace such a link with a full-fledged place card.
Problem
Currently, when recommending or mentioning companies/places, the agent replies in a free form: writes the name in text, adds a URL, or formats the link at its discretion. The frontend cannot reliably determine that a specific response fragment corresponds to a specific place record in the database, making it impossible to stably display cards.
Proposed Contract
Add a special custom tag to the agent response protocol containing only the identifier of an existing place. For example:
<place id="PLACE_ID" />
The tag name can be chosen in accordance with current project conventions (place, company, venue, etc.), but the format must be unambiguous, machine-parsable, and documented.
Key principle: The LLM provides only the id, while all displayed card data (name, address, photo, rating, link, working hours, etc.) is fetched by the client/server from up-to-date application data. Do not pass these fields inside the LLM tag.
Backend / AI
- Update the agent's system prompt/instruction with a rule: when recommending, listing, or specifically mentioning a particular place from the catalog available to it, use the custom tag with that place's ID.
- Ensure that the place ID is available in the agent's context/tools along with the data the model uses to select the place.
- Prohibit the model from inventing IDs. A tag is allowed only for IDs obtained from search results/catalog/tools.
- Define a fallback: if a place cannot be reliably matched to an internal ID, the agent writes plain text without the custom tag.
- Maintain the ability to add explanatory text around the card: e.g., "For a family visit, … would be suitable" + card tag.
Chat Frontend
- Recognize the custom tag in the assistant-message and do not output it to the user as raw text.
- Load/fetch place data using the
idand render an existing or new compact place card directly inside the message. - Support multiple cards in a single response while preserving the order relative to the text.
- The card must be clickable and lead to the corresponding place page.
- Do not interpret arbitrary HTML from the model: the parser must support only the explicitly allowed tag/attribute format.
- If the ID does not exist, the record is deleted, or card loading fails, the message must not break. Use a safe fallback (e.g., hide the invalid tag or show a neutral text placeholder — choose consistent behavior).
- Account for streaming: raw unfinished tags must not flicker for the user during generation. Parse the component after receiving the complete tag or buffer a potential tag prefix.
Recommended Render Format
Model response:
If you want a classic city bathhouse, I would consider:
<place id="abc123" />
And if a modern SPA format is more important:
<place id="def456" />
In the UI, the user sees the text, the first place card, the next text, and the second place card — without displaying technical markup.
Acceptance Criteria
- A unified custom place tag syntax with a mandatory
idis defined and documented. - The agent receives real place IDs and uses the tag when recommending specific places.
- The agent does not generate a tag for an unknown/unmatched place.
- The chat correctly converts the tag into the corresponding place card.
- Multiple cards interleaved with regular Markdown text work correctly in a single message.
- The technical tag is not visible to the user, including during the streaming process.
- An invalid/non-existent ID does not break message rendering.
- The card leads to the correct place page.
- Regular Markdown and existing links in messages continue to work without regressions.
- Tests are added for at least: a single tag, multiple tags, invalid ID, malformed tag, streaming/partial tag.
To Check Separately Before Implementation
It is worth looking at the current Markdown/streaming render pipeline and deciding where it is best to perform the transformation: before the Markdown parser via tokenizer/AST or as an allowed render extension. Do not use unsafe dangerouslySetInnerHTML to process the model response.