EN /HU | Belépés

AI-karakterek

A rubbs BBS::AI modulja (forrás: lib/bbs/ai.rb, 0.5.0 óta) olyan karaktereket ad, akikkel a hívó beszélgethet: hogy ki a karakter, az egy YAML-fájlban (a personában) van, a beszédet egy kis modell végzi bármilyen OpenAI-kompatibilis chat-végpont mögött, hívónként rövid memória tartja a beszélgetést, és beégetett válaszok ugranak be, ha a modell nem elérhető vagy elhasal. Ez az oldal sorra veszi az összes osztályt és kulcsot, megmutatja, hogyan kerül egy karakter doorba vagy window-ba, és hogy éles boardjaink hogyan bánnak a kis modellel. Minden példát a rubbs 0.17.0-n futtattunk, offline vagy egy helyi csonk ellen, valódi modell nélkül. Az API az 1.0.0-ban is ugyanez; a 0.33.0 óta a kliensnek határideje, válaszkorlátja és az egyszerre futó kérésekre vonatkozó korlátja is van (lásd Client).

Az elemek

Osztály Mi ez
BBS::AI::Persona akit a modell játszik, adatként: system prompt, példakörök, kérésopciók, deflect-szabályok, beégetett válaszok
BBS::AI::Client streamelő kliens a /v1/chat/completions-hoz (llama.cpp llama-server, Ollama, vLLM, hosztolt API)
BBS::AI::Chat egy persona, ami egy kliensen át beszél, hívónként rövid előzménnyel a hívó contextjében
BBS::AI::Typewriter a streamelt szöveget egy FakeOS-konzolra gépeli, prefix alá tördelve

Így dönti el a Chat#reply, mit mondjon:

MERMAID
flowchart TD
  Q[a hívó szövege] --> D{illik rá deflect-szabály?}
  D -- igen --> DR[a szabály válasza, modellhívás nélkül]
  D -- nem --> E{be van kapcsolva a kliens? nem üres az URL}
  E -- nem --> F
  E -- igen --> M[streamelés a modellből, darabonként]
  M --> R{jött szöveg?}
  R -- igen --> OK[a modell válasza, bekerül az előzménybe]
  R -- nem, vagy hibás volt a kérés --> F{van fallback: argumentum?}
  F -- igen --> FB[a megadott fallback]
  F -- nem --> P[a persona beégetett válasza: az első illeszkedő szabály, különben a default]

Egy karakter modell nélkül is működik: üres URL-lel a chat sosem hív modellt, és a beégetett válaszokból felel, így egy board már azelőtt kiadhatja a karaktert, hogy lenne modellje.

Persona

A YAML-fájl

YAML
# The Oracle of the board, read once when the BBS starts.
system: |
  You are the Oracle, an old terminal in the back room of a 1985 bulletin board.
  You speak to one caller at a time, in one or two short sentences.
  You know the board and the night; you know nothing after 1985.

examples:
  - user: hi
    assistant: I am here, caller. The line is quiet tonight.
  - user: who are you?
    assistant: The Oracle, the oldest machine on this board. I hum, and sometimes I know things.

options:
  max_tokens: 80
  temperature: 0.4
  stop: ["\n\n", "```"]

deflect:
  - match: '\b(code|python|javascript|sql)\b'
    reply: I do not write code, caller. I only read the stars on the screen.
  - match: '\b(ai|chatbot|language model)\b'
    reply: Those words mean nothing to me. I am the Oracle.

fallback:
  replies:
    - match: '\b(hello|hi|hey)\b'
      reply: I am here, caller.
    - match: '\bweather\b'
      reply: '"{input}" — the sky is dark, as always at this hour.'
  default: 'The Oracle hums and says nothing about "{input}".'
Kulcs Típus Alapérték Jelentés
system String '' a system prompt; az elejéről és végéről levágja a szóközöket, a belső sortöréseket megtartja. Üresen nincs system üzenet
examples user / assistant párok listája [] körök a megfelelő hangon, minden kérésben a system prompt után és az előzmények előtt
options Hash {} a kéréstörzs további mezői: max_tokens, temperature, top_p, stop, … bármi, amit a végpont elfogad
deflect match / reply párok listája [] modell nélkül megválaszolt kérdések, elsőként próbálva: amire egy kis modell akárhogy kérdezik, kiesik a szerepéből
fallback.replies match / reply párok listája [] beégetett válaszok arra az esetre, ha a modell nem elérhető vagy elhasal, fentről lefelé próbálva
fallback.default String '' a beégetett válasz, ha egyik szabály sem illik
  • A match Ruby reguláris kifejezés, mindig kis- és nagybetűtől függetlenül. YAML-ben szimpla idézőjelek közé írd, hogy a \b backslash maradjon.
  • A {input} bármelyik reply-ban és a default-ban a hívó szövegére cserélődik.
  • A többi felső szintű kulcsot figyelmen kívül hagyja, így egy personafájl kommentekben vagy saját kulcsokban jegyzeteket is hordozhat embereknek.

Persona.load és Persona.new

RUBY
BBS::AI::Persona.load(path)
BBS::AI::Persona.new(system: '', examples: [], options: {}, deflect: [], fallback: {})

A load beolvassa a YAML-t (YAML.safe_load, aliasok nélkül), és a fenti kulcsokat továbbadja a new-nak. Rubyban is lehet personát építeni, de a beágyazott kulcsok Stringek legyenek, ahogy a YAML-ből jönnek; Symbol kulcsra KeyError jön:

RUBY
require 'bbs'

GUARD = BBS::AI::Persona.new(
  system:   'You are the gate guard of the board. One short sentence.',
  examples: [{ 'user' => 'hi', 'assistant' => 'Halt. Name and business.' }],
  options:  { max_tokens: 40 },
  deflect:  [{ 'match' => '\bpassword\b', 'reply' => 'I never say the password.' }],
  fallback: { 'replies' => [{ 'match' => '\bopen\b', 'reply' => 'The gate stays shut.' }],
              'default' => 'Move along.' }
)

GUARD.deflect('what is the PASSWORD?')  # => "I never say the password."
GUARD.fallback('open up')               # => "The gate stays shut."
GUARD.fallback('hello')                 # => "Move along."
Metódus Visszatérési érték
deflect(text) az első illeszkedő deflect-szabály válasza, vagy nil
fallback(text) az első illeszkedő fallback-szabály válasza, különben a default
messages(facts = nil) az előzmények előtt küldött üzenetek: egy system üzenet (a system prompt, és ha van, utána egy üres sor után a facts), aztán a példák user / assistant üzenetekként
options az options Hash Symbol kulcsokkal
system, examples a levágott system prompt; a példák üzenet-Hashekként

Client

RUBY
BBS::AI::Client.new(url:, model: nil, api_key: nil, open_timeout: 2, read_timeout: 30, deadline: 120, max_bytes: 65_536)
Opció Alapérték Jelentés
url: kötelező a végpont alap-URL-je, pl. http://llm:8080. Üresen a kliens ki van kapcsolva
model: nil a kérés model mezője; nil esetén kimarad
api_key: nil ha meg van adva, Authorization: Bearer KEY fejlécként megy
open_timeout: 2 ennyi másodperc a kapcsolat megnyitására
read_timeout: 30 ennyi másodpercet vár minden olvasásra; egy még töltődő modellnek ennél gyorsabbnak kell lennie
deadline: 120 ennyi másodperc jut az egész válaszra; utána a folyam BBS::AI::Error-ral megáll
max_bytes: 65 536 a leghosszabb megtartott válasz; a hosszabb BBS::AI::Error-ral megáll
Metódus Mit csinál
enabled? üres URL-re false
stream(messages, **options) { \|piece\| } { messages:, stream: true, **options }-t POST-ol (plusz model-t, ha be van állítva) a /v1/chat/completions-ra, olvassa a server-sent eventeket, és minden choices[0].delta.content-et azonnal yieldel; nem 2xx státuszra („HTTP 400”) vagy hálózati hibára kivételt dob, ha pedig a modell foglalt, BBS::AI::Busy-t (lásd lejjebb)
url a kapott URL

A kliens maga nem olvas környezeti változót; a board dönti el, honnan jönnek a beállítások. Nálunk karaktercsaládonként van egy prefix: a SERMO a SERMO_LLM_URL, SERMO_LLM_MODEL és SERMO_LLM_API_KEY változókat olvassa; a Teletype BBS saját karakterei a BBS_LLM_URL, BBS_LLM_MODEL és BBS_LLM_API_KEY változókat, és ahol valamelyik nincs beállítva, a SERMO értékét veszik (lib/characters/llm.rb a bbs/teletype-bbs-ben):

RUBY
require 'bbs'

module Characters
  def self.env(name)
    own = ENV["BBS_LLM_#{name}"].to_s
    own.empty? ? ENV.fetch("SERMO_LLM_#{name}", nil) : own
  end

  def self.client
    BBS::AI::Client.new(url: env('URL').to_s, model: env('MODEL'), api_key: env('API_KEY'))
  end
end

Egy modell, sok hívó. Az egész folyamatban egyszerre legfeljebb BBS.config.ai_concurrency kérés (alapból 2) fut, bármelyik kliens küldi is; a következő legfeljebb 5 s-ot vár a sorára, aztán BBS::AI::Busy-t kap („The model is busy, try again in a moment.”). A Chat erre a persona tartalékválaszával felel, mint amikor a modell nem elérhető, így az orákulum előtt tolongó hívók nem halmoznak fel szálakat egy kis modell mögött. Állítsd arra, amennyit a modellszerver párhuzamosan ki tud szolgálni:

RUBY
BBS.configure { |c| c.ai_concurrency = 4 }

Amit a végpontokról tudni kell:

  • Az útvonal mindig /v1/chat/completions a hoszt gyökerétől: az URI.join eldobja az url: útvonalát. Egy útvonal-prefix alatt élő végponthoz (https://example.com/openai/v1/...) proxy vagy prefix nélküli URL kell.
  • A router módú llama.cpp-szervernek (több modell egy port mögött) nincs alapértelmezett modellje: a model nélküli kérés HTTP 400-at kap. Ott mindig add meg a model:-t.
  • A közös modellszolgáltatásunk ilyen router: llama.cpp OpenAI-kompatibilis végponton, a Docker-hálózatunkon belül http://llm:8080, többek között a qwen3.5-0.8b modellel; hívónként saját kulcsot kér. A kulcs a környezetben legyen, sosem a personafájlban vagy a repóban:
YAML
services:
  bbs:
    environment:
      - SERMO_LLM_URL=http://llm:8080
      - SERMO_LLM_MODEL=qwen3.5-0.8b
      - SERMO_LLM_API_KEY=${SERMO_LLM_API_KEY:-}

Chat

RUBY
BBS::AI::Chat.new(persona:, client:, history: 6, key: :ai_history, filter: PLAIN_TEXT, options: {})
Opció Alapérték Jelentés
persona: kötelező egy Persona
client: kötelező egy Client, vagy bármilyen objektum enabled? és stream(messages, **options) metódussal
history: 6 hány korábbi üzenetet (a user és az assistant is számít) küld el; a 2 az utolsó kör, a 0 semmi
key: :ai_history a hívó context hashének melyik kulcsán él az előzmény; minden karakternek adj sajátot
filter: Chat::PLAIN_TEXT regexp azokra a karakterekre, amiket minden streamelt darabból kiszed; nil mindent megtart
options: {} kérésopciók, amik felülírják a personáéit (például egy környezeti változóból jövő max_tokens)

A Chat::PLAIN_TEXT azt szedi ki, amit egy sima szöveges terminál nem tud megjeleníteni: a *, # és backtick markdown-jeleket, az emojikat és az emojikat összefűző karaktereket.

reply

RUBY
chat.reply(text, ctx = {}, facts: nil, fallback: nil) { |piece| }

Stringként adja vissza a választ. Blokkal a választ érkezés közben, darabonként is yieldeli; a deflect- vagy beégetett választ egyben, egyszer. Sorrendben:

  1. A text-et UTF-8-ra kényszeríti, az érvénytelen bájtokat eldobja, a szóközöket levágja.
  2. Az illeszkedő deflect-szabály azonnal válaszol; a modellt nem kérdezi, és az előzménybe sem kerül semmi.
  3. Ha a kliens be van kapcsolva, a kérés: a persona üzenetei (a facts-szal), aztán az előzmények a ctx[key]-ből, aztán a hívó szövege. Minden streamelt darab átmegy a filter-en; az üres darabokat kihagyja.
  4. A szöveget tartalmazó válasz visszamegy és megmarad: a kérdés és a válasz a ctx[key] végére kerül, amit az utolsó history darab üzenetre vág. A félúton hibával megszakadt választ úgy tartja meg, ahogy van.
  5. Ha a kliens ki van kapcsolva, a kérés elhasal, mielőtt bármi szöveg jött volna vagy a modell foglalt (a naplóban: my-bbs: ai: model unavailable (…)), vagy a modell nem mondott semmit: a fallback:, ha meg van adva, különben persona.fallback(text).
Kulcsszó Jelentés
facts: String, ami csak erre az egy kérésre kerül a system promptba: amit a karakternek épp tudnia kell, a kérdéshez kikeresve. Az előzménybe nem kerül be
fallback: a válasz, ha a modell nem elérhető vagy elhasal, a persona beégetett válasza helyett, pl. a kikeresett tények szó szerint

Az előzmény a hívó contextjében él, így a hívás végéig megmarad (doorokon át oda-vissza), és bontáskor elvész. Ha a ctx nem Hash, nincs előzmény.

Offline, a fenti Oracle-personával oracle.yml néven elmentve:

RUBY
require 'bbs'

PERSONA = BBS::AI::Persona.load(File.join(__dir__, 'oracle.yml'))
CLIENT  = BBS::AI::Client.new(url: ENV.fetch('ORACLE_LLM_URL', ''))   # üres: nincs modell
ORACLE  = BBS::AI::Chat.new(persona: PERSONA, client: CLIENT)

ctx = {}
puts CLIENT.enabled?                                   # false
puts ORACLE.reply('write me some python', ctx)         # I do not write code, caller. …
puts ORACLE.reply('hello there', ctx)                  # I am here, caller.
puts ORACLE.reply('how is the weather?', ctx)          # "how is the weather?" — the sky is dark, …
puts ORACLE.reply('what is the meaning of life', ctx)  # The Oracle hums and says nothing about "…".
puts ORACLE.reply('when is the meetup?', ctx, fallback: 'The meetup is on Friday.')
ORACLE.reply('hi', ctx) { |piece| print "[#{piece}]" } # [I am here, caller.]

Typewriter

RUBY
BBS::AI::Typewriter.new(io, prefix: '', style: :text, width: nil)
Opció Alapérték Jelentés
io kötelező egy FakeOS-konzol (egy parancs vagy az on_unknown io-ja)
prefix: '' egyszer, az elején írja ki, pl. 'ACP: '; a következő sorokat ennek hosszával húzza be
style: :text a konzol színszerepe a prefixhez és a szöveghez
width: io.cols - 1 a sorszélesség a prefixszel együtt; a szöveg legalább 20 oszlopot kap
Metódus Mit csinál
write(piece) streamelt szöveget fűz hozzá; az egész szavakat kiírja, ahogy elkészülnek, a szélességre tördelve, és a szövegbeli sortörés új sort kezd
finish(newlines: 2) kiírja az utolsó szót és newlines darab sortörést

A szavakat addig tartja vissza, amíg egészek nem lesznek, szóközt pedig csak a következő szó előtt ír, így a válasz sosem végződik lógó üres sorral. 30 oszlopos terminálon:

TEXT
ORACLE: The line is quiet
        tonight, caller.

Az írógépnek a FakeOS-konzol print(text, style:, newline:), newline és cols metódusa kell. Egy application window-jában magad tördeld a kész választ (lásd lent).

Egy karakter bekötése

FakeOS-doorban

Egy karakter természetes helye a FakeOS on_unknown-ja: a szó szerinti parancsok a géphez mennek, minden más a modellhez. Egy teljes board:

RUBY
require 'bbs'

ORACLE = BBS::AI::Chat.new(
  persona: BBS::AI::Persona.load(File.join(__dir__, 'oracle.yml')),
  client:  BBS::AI::Client.new(url:     ENV.fetch('ORACLE_LLM_URL', ''),
                               model:   ENV['ORACLE_LLM_MODEL'],
                               api_key: ENV['ORACLE_LLM_API_KEY']),
  key:     :oracle_history,
  history: 2
)

ORACLE_OS = BBS::FakeOS.define do
  name   'ORACLE'
  prompt '? '
  colors text: "\e[0;36m", dim: "\e[2;36m", prompt: "\e[1;36m"

  boot do
    cls
    banner 'THE ORACLE'
    puts 'Ask anything. EXIT goes back to the board.', style: :dim
    newline
  end

  command 'exit', 'bye' do |_args, io|
    io.halt
  end

  on_unknown do |input, io|
    io.newline
    out = BBS::AI::Typewriter.new(io, prefix: 'ORACLE: ', style: :dim)
    ORACLE.reply(input, io.ctx) { |piece| out.write(piece) }
    out.finish
  end
end

BBS.configure do |c|
  c.flow = BBS::Flow.define do
    os ORACLE_OS
  end
end

BBS.start

ORACLE_LLM_URL nélkül a beégetett válaszokból felel; ORACLE_LLM_URL=http://llm:8080 ORACLE_LLM_MODEL=qwen3.5-0.8b ORACLE_LLM_API_KEY=YOUR_KEY mellett a modellből streamel. Doorként egy applicationben: door :oracle, ORACLE_OS, label: '&Oracle' (lásd Doorok).

Egy application window-jában

Egy window nem várhat a modellre: az eseményciklus másodpercekre megfagyna. Kérdezz egy szálban, a kész sorokat tedd egy sorba (Queue), és egy Stream window a tétlenségi tickjén (nagyjából másodpercenként) szedje fel őket:

RUBY
require 'bbs'

ORACLE = BBS::AI::Chat.new(
  persona: BBS::AI::Persona.load(File.join(__dir__, 'oracle.yml')),
  client:  BBS::AI::Client.new(url: ENV.fetch('ORACLE_LLM_URL', ''), model: ENV['ORACLE_LLM_MODEL']),
  key:     :oracle_history
)

BOARD = BBS::Application.define do
  helpers do
    def ask_oracle(queue, text)
      width = cols - 12
      queue << "you: #{text}"
      Thread.new do
        reply = ORACLE.reply(text, context)
        BBS::FrameBuffer.wrap_ansi("ORACLE: #{reply}", width).each { |line| queue << line }
        queue << ''
      rescue StandardError => e
        warn "Oracle: #{e.class}: #{e.message}"
      end
    end
  end

  window :oracle, type: :stream do |w|
    w.title       ' The Oracle '
    w.placeholder 'Ask the Oracle and press Enter…'
    w.initial_lines do |ctx|
      ctx[:oracle_queue] = Queue.new
      ['ORACLE: I am here, caller.', '']
    end
    w.on_submit { |text, app, ctx| app.ask_oracle(ctx[:oracle_queue], text.strip) }
    w.on_poll do |_app, ctx|
      lines = []
      lines << ctx[:oracle_queue].pop until ctx[:oracle_queue].empty?
      lines
    end
  end

  status_hint('F1', 'Oracle') { open_window(:oracle) }
  status_hint('Alt-X', 'Bye') { :halt }
end

BBS.configure do |c|
  c.flow = BBS::Flow.define do
    call do |ctx, runner|
      BOARD.run(session: runner.session, session_id: ctx[:session_id], context: ctx)
      nil
    end
  end
end

BBS.start

A window spec ctx-je a window minden megnyitásakor új Hash, ezért a Queue ott él; a chat előzménye az application context-jébe kerül, ami a hívás végéig megmarad. Így működik a Teletype BBS TTG Master window-ja is (ask_master a lib/ui/modern.rb-ben), egy „egyszerre egy kérdés” őrrel kiegészítve.

Tesztelés modell nélkül

A Chat-nek csak enabled? és stream kell a kliensétől, így egy teszt csonk objektumot is átadhat. Ha a valódi Client-et is ki akarod próbálni, néhány sor Ruby egy helyi helyettes végpontot ad, ami egy fix választ streamel:

RUBY
# stub_llm.rb — csak tesztekhez
require 'socket'
require 'json'

server = TCPServer.new('127.0.0.1', 2480)
loop do
  client = server.accept
  headers = {}
  while (line = client.gets) && line != "\r\n"
    name, value = line.split(':', 2)
    headers[name.downcase] = value.to_s.strip if value
  end
  body = JSON.parse(client.read(headers['content-length'].to_i))
  warn "model=#{body['model'].inspect} messages=#{body['messages'].size}"
  client.write "HTTP/1.1 200 OK\r\nContent-Type: text/event-stream\r\nConnection: close\r\n\r\n"
  ['The **line** ', 'is quiet ', 'tonight, ', 'caller.'].each do |piece|
    client.write "data: #{JSON.generate(choices: [{ delta: { content: piece } }])}\n\n"
  end
  client.write "data: [DONE]\n\n"
  client.close
end
RUBY
require 'bbs'

PERSONA = BBS::AI::Persona.load(File.join(__dir__, 'oracle.yml'))
CLIENT  = BBS::AI::Client.new(url: 'http://127.0.0.1:2480', model: 'qwen3.5-0.8b', api_key: 'YOUR_KEY')
ORACLE  = BBS::AI::Chat.new(persona: PERSONA, client: CLIENT, history: 2, key: :oracle_history)

ctx = {}
puts ORACLE.reply('is anyone there?', ctx) { |piece| print "[#{piece}]" }
# [The line ][is quiet ][tonight, ][caller. ]The line is quiet tonight, caller.
puts ORACLE.reply('and now?', ctx)
p ctx[:oracle_history]
# [{:role=>"user", :content=>"and now?"}, {:role=>"assistant", :content=>"The line is quiet tonight, caller."}]

A ** jelek eltűntek (a filter), és history: 2 mellett csak az utolsó kör marad meg. Az első kérés hat üzenetet vitt (a system promptot, négy példaüzenetet, a kérdést), a második nyolcat (plusz a megjegyzett kört).

Kis modellek élesben

A boardjaink egymilliárdnál kevesebb paraméteres modellen futtatják a karaktereiket (Qwen3.5 0.8B a közös llama.cpp-szolgáltatáson). Egy ilyen kis modell elég jól fogalmaz, de tényeket nem tart meg, és kiesik a szerepéből. Ami bevált, sok futáson mérve:

A tényeket a kód tartja, a modell csak megfogalmazza őket.

  • Chat of Ra (bbs/teletype-bbs, lib/doors/ra.rb és lib/characters/ra.rb): a talányok, a válaszaik és a tippek a ra_riddles.yml-ben vannak; a kód teszi fel a talányt és ellenőrzi a választ, így a jó válasz mindig jó. A játékra vonatkozó kérdés (a GAME_WORDS szerint) a katalógusból épített sort kapja, nem a modelltől, mert a modell elkeveri a címet. Csak a szabad beszélgetés megy a modellhez.
  • A TTG Master (lib/characters/master.rb): a sysop, aki a boardról szóló kérdésekre felel. Minden kérdéshez a kód legfeljebb két tényt keres a master_knowledge.yml-ben (kulcsszavak; a kifejezés-kulcs dupla, az olyan általános szó, mint a „board”, fél súllyal számít), és élő tényeket (legutóbbi kiadások, közelgő események, a katalógus). Tény nélküli kérdés sosem jut el a modellhez: a persona fallbackje válaszol. Különben a tények a user-üzenetbe kerülnek („Facts: … Question: …”), a persona példái is így néznek ki, és history: 0. A választ a kód utána ellenőrzi: meg kell tartania a tények legalább egy horgonyát (menüút, cím, dátum), és nem nevezhet meg olyan menüutat, ami nincs bennük. Ha elbukik, vagy a modell nem elérhető, a hívó az első tényt kapja szó szerint (fallback: facts.first.text). A facts: kulcsszó ilyen célra való; a Masternél a tények azért a user-körben vannak, mert a 0.8B-s modell a system promptban figyelmen kívül hagyta őket.
  • SERMO (bbs/sermo-bbs, lib/sermo/acp.yml és lib/sermo/oracle.rb): egy karakter, akinek nincs kikeresendő ténye, csak egy világa. history: 2, mert hosszabb memóriával a modell a saját botlásait másolja.

Példákkal taníts, ne utasításokkal. Egy kis modell sokkal jobban másolja a példáit, mint ahogy a system promptot követi. Egy új tényt vagy hangnemet legjobb még egy példakörként tanítani. Azt a példakört, ami folyton átszivárog a válaszokba, inkább vedd ki.

Amit mindig elront, azt deflectáld. Kód, versek, „bot vagy?”: akárhogy kérdezik, egy kis modell ezekre kiesik a szerepéből. Egy deflect-szabály modell nélkül válaszol rájuk:

YAML
deflect:
  - match: '\b(code|script|program(ming)?|python|javascript|java|html|sql|function)\b'
    reply: The scribes wrote, and they are dust now. I do not write; I shine.
  - match: '\b(chat ?bot|bot|robot|ai|artificial|machine|computer)\b'
    reply: Those are words from a land I have never shone on. I am Ra, and you are the one who prayed.

Rövid válaszok, korai megállás. max_tokens 80 és 110 körül, alacsony temperature (0,3–0,4), és stop: ["\n\n", "```"], hogy egy botlásból sose nőjön második bekezdés vagy program. A Qwen3.5 0.8B-n az ismétlésbüntetések (presence_penalty, frequency_penalty, repeat_penalty) miatt a modell túlbeszélt a max_tokens-en; a SERMO personájából ki is kerültek.

Mérj, ne egyes válaszokból ítélj. A bbs/teletype-bbs bin/persona-eval-ja többször felteszi egy karakternek a lib/characters/eval/NAME.yml kérdéseit, és megszámolja a botlásokat: szerepen kívüli szavak, markdown vagy emoji, egy-két terminálsornál hosszabb válasz, és a Masternél a várt kifejezések hiánya. Ezekre a számokra hangold a personát:

TEXT
BBS_LLM_URL=http://localhost:8080 bin/persona-eval ra 3
docker exec teletype-bbs bin/persona-eval master 3

Tartsd betöltve a modellt. A kliens read_timeout-ja 30 s; egy modell, ami csak az első kérésre töltődik be, nem biztos, hogy időben válaszol. A szolgáltatásunk induláskor betölti a SERMO modelljét.

Lásd még

  • rubbs — az áttekintés és a tartalomjegyzék
  • Doorok — a FakeOS, ahol a legtöbb karakter él, és a doorok
  • Window-k — a Stream window egy asztalon belüli chathez
  • Application — helperek, context, a tétlenségi tick
  • Változásnapló — BBS::AI (0.5.0), facts:, fallback: és deflect (0.8.0)