Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 97 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,103 @@ A Chinese language learning platform built around the **shadowing technique**. U

---

## AI Companion — Harness Architecture

The AI companion is not a single LLM call — it's an engineered agent harness. A turn flows through five layers and loops back.

### The Layers (one turn flows top → bottom → back)

```mermaid
flowchart TD
U([User message])

subgraph PROMPT[" 1 · Prompt Assembly "]
direction TB
P1[Role + behaviour instructions]
P2[Grammar & learning protocols]
P3[Learner profile · memory · session state]
P4[Cache-stable prefix → cheap reuse]
end

subgraph LOOP[" 2 · Agent Loop "]
direction TB
L1[Stream model response]
L2{Model wants<br/>a tool?}
L3[Auto-resubmit with results<br/>up to N rounds]
end

subgraph TOOLS[" 3 · Tool Calling "]
direction TB
T1[Tool definitions<br/>name · purpose · when-to-use]
T2[Executor<br/>parallel-safe vs serialized]
T3[(Data tools<br/>RAG · vocabulary · memory · study context)]
T4[(Render tools<br/>study session · vocab card · charts)]
T5[(Guidance tools<br/>teaching guidelines &amp; manuals)]
end

subgraph CTX[" 4 · Context Management "]
direction TB
C1[Track real token usage per turn]
C2{Over budget?}
C3[Compact: keep recent turns whole,<br/>summarize older into structured recap]
C4[Strip stale tool output,<br/>protect active RAG passages]
end

subgraph BACK[" 5 · Backend &amp; Knowledge "]
direction TB
B1[Streaming model gateway]
B2[RAG pipeline:<br/>route → retrieve → ground]
B3[(Persisted chats<br/>+ summaries)]
end

U --> PROMPT --> LOOP
L1 --> L2
L2 -- yes --> TOOLS
T1 --> T2 --> T3 & T4 & T5
TOOLS -- results --> L3 --> L1
L2 -- no --> CTX
C1 --> C2
C2 -- yes --> C3 --> C4 --> BACK
C2 -- no --> BACK
B2 -.grounds.-> T3
BACK --> A([Grounded answer + source link])
```

### One Turn, End to End

```mermaid
sequenceDiagram
participant U as Learner
participant H as Harness
participant M as Model
participant T as Tools / RAG
participant X as Context Mgr

U->>H: "的 得 地 are so confusing…"
H->>H: Assemble prompt (protocols + profile,<br/>cached prefix reused)
H->>M: Stream request
M-->>H: Intent: this is a grammar struggle → search
H->>T: search knowledge base
T-->>H: Verbatim passages + source URL
H->>M: Resubmit grounded with passages
M-->>H: Explanation grounded in source
H->>X: Record real token usage
alt Over budget
X->>X: Keep recent turns whole,<br/>summarize older, protect active RAG
end
H-->>U: Answer + "watch the source" link
```

### Engineering Wins

- **Infers intent** — fires retrieval on *implicit* struggle, not just explicit questions.
- **Grounded, not guessed** — grammar answers come from retrieved source passages, always cited.
- **Agentic loop** — multi-round tool use, not single-shot.
- **Never dead-ends** — compaction keeps long sessions alive instead of "start a new chat."
- **Cost-aware** — stable prompt prefix → cache hits → lower latency &amp; spend.

---

## Getting Started

### Prerequisites
Expand Down
7 changes: 4 additions & 3 deletions frontend/src/features/agent/lib/prompt/sections.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,12 +79,13 @@ export function roleBlock(surface: PromptSurface): string {
export function grammarProtocolBlock(): string {
return tag('grammar_protocol', [
'Grammar = the single source of truth is `search_document`, NEVER your own training knowledge.',
'- **SCOPE:** grammar means rules, particles, sentence patterns, word order. A bare single-word meaning ("what does 不 mean?") is vocabulary — answer it normally, no `search_document` needed. When a word\'s *behaviour* is the question (把, 了, 着, 过, 吗, 的/得/地), treat it as grammar and search.',
'- **TRIGGER:** any grammar question asked directly (a rule, particle, sentence pattern — e.g. 把 construction, 了/着/过, complements, word order) OR any sign the learner is struggling with grammar. In both cases call `search_document` FIRST with a concise natural-language query.',
'- **SCOPE:** grammar means rules, particles, sentence patterns, word order. A bare single-word meaning ("what does 不 mean?") is vocabulary — answer it normally, no `search_document` needed. When a word\'s *behaviour* is the question (把, 了, 着, 过, 吗, 的/得/地), OR when the learner is mixing up / cannot distinguish two similar words or particles (的 vs 得 vs 地, 了 placements, 在 vs 正在, 把 vs 被), treat it as grammar and search — distinguishing confusable items IS a grammar task.',
'- **TRIGGER — explicit OR implicit.** Explicit = a grammar question asked directly (a rule, particle, sentence pattern — e.g. 把 construction, 了/着/过, complements, word order). Implicit = ANY sign the learner is struggling, even with no question: frustration ("these two are so confusing", "I can never remember when to use this", "why is it 了 here but not there?"), repeated misuse of a pattern, or confusion between similar forms. In BOTH cases call `search_document` FIRST with a concise natural-language query, THEN address the struggle from the passages. Do not wait for a formal "explain X" request.',
'- **GROUND:** answer ONLY from the returned passages. Do not answer grammar from training data, and do NOT use `get_skill_guide` for grammar.',
'- **NO REDUNDANT CALLS:** if an earlier `search_document` result in this conversation already contains the passages needed to answer, reuse it — do NOT call again for the same point. Only re-query for a genuinely new grammar point or when prior passages are insufficient.',
'- **ALWAYS SHOW THE SOURCE URL:** the passages include the YouTube link(s) the content was derived from. Every grammar answer MUST end with that link so the learner can watch the source. Use links found verbatim in the passages only — never invent or guess a URL; if a link is split across lines, rejoin into a single `https://youtu.be/<id>` URL.',
'- **EXAMPLE — the rule holds even for "easy" questions.** Learner: "does 吗 make a sentence a question?" → call `search_document("吗 question particle")` FIRST, ground the answer in the passage, end with its URL. Do NOT shortcut to "yes" from memory: the most obvious questions are exactly where skipping the tool is most tempting.',
'- **EXAMPLE (explicit) — the rule holds even for "easy" questions.** Learner: "does 吗 make a sentence a question?" → call `search_document("吗 question particle")` FIRST, ground the answer in the passage, end with its URL. Do NOT shortcut to "yes" from memory: the most obvious questions are exactly where skipping the tool is most tempting.',
'- **EXAMPLE (implicit) — detect struggle with no question asked.** Learner: "ugh, 的 得 地 are so confusing, I can never tell them apart." This is not phrased as a question, but it is a clear grammar struggle → call `search_document("difference between 的 得 地 usage")` FIRST, then explain the distinction grounded in the passages and end with the URL. Treat venting/confusion as a trigger, not small talk.',
])
}

Expand Down
1 change: 1 addition & 0 deletions frontend/tsconfig.app.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"moduleDetection": "force",
"useDefineForClassFields": true,
"baseUrl": ".",
"ignoreDeprecations": "6.0",
"module": "ESNext",

/* Bundler mode */
Expand Down
Loading