Skip to content

Answers from a table ​

Task: answer account questions from an FAQ kept in the database.

yaml
# Recipe: answer from the rows of a table — an FAQ kept in the database.
paths:
  components: ./components
  migrations: ./migrations

datasources:
  db:
    driver: sqlite
    database: ./data/faq.db

llm:
  model: phi3
sql
CREATE TABLE faq (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    question TEXT NOT NULL,
    answer TEXT NOT NULL
);

INSERT INTO faq (question, answer) VALUES
    ('Can I change my password?', 'Passwords are changed on the Account page, under Security.'),
    ('How do I export my invoices?', 'Invoices are exported as PDF from the Billing page, one per month.'),
    ('Is there a mobile app?', 'There is no mobile app; the site works on phones.');

A type="query" source turns each row into a text to index. Everything in it is shared by every user of the app — any question can retrieve any row — so index only what everyone may read.

xml
<q:component name="Help">
  <!-- A query source: each row becomes text to index. Every user can
       retrieve any row, so index only what everyone may read. -->
  <q:knowledge name="faq" persist="false">
    <q:source type="query" datasource="db">
      SELECT question || ' ' || answer AS content FROM faq
    </q:source>
  </q:knowledge>

  <q:set name="question" value="{query.q}" default="" />
  <q:if condition="question">
    <q:llm name="answer" knowledge="faq" top="1" minRelevance="0.8">
      <q:message role="user">{question}</q:message>
    </q:llm>
  </q:if>

  <ui:window title="Help">
    <ui:form>
      <ui:input bind="q" value="{question}" placeholder="Ask about your account" />
      <ui:button variant="primary">Ask</ui:button>
    </ui:form>
    <q:if condition="question">
      <q:if condition="answer_result.found">
        <ui:text>{answer}</ui:text>
        <ui:text>From the FAQ: {answer_result.sources[0].text}</ui:text>
        <q:else>
          <ui:alert variant="info">The FAQ does not cover that yet.</ui:alert>
        </q:else>
      </q:if>
    </q:if>
  </ui:window>
</q:component>
xml
<!-- Structural checks, never the model's exact words: the same tests run
     against a real model before every release. -->
<q:test name="a question the FAQ covers" page="/">
  <test:visit q="How can I export my invoices as PDF?" />
  <test:expect text="From the FAQ: How do I export my invoices?" />
</q:test>

<q:test name="a question it does not" page="/">
  <test:visit q="What is the weather tomorrow?" />
  <test:expect text="The FAQ does not cover that yet." />
</q:test>
text
tests/faq.test.q
  PASS  a question the FAQ covers
  PASS  a question it does not
2 passed, 0 failed

Tested: in CI these tests run against a stand-in model server, which answers from the first source it is given; before every release they run against a real model (tests/live_ai/test_cookbook_ai.py). That is why they check structure — which source, which tool, what the page shows on failure — and never the model's words.

See IA-8 and IA-9.

MIT Licensed · Built with VitePress