Discord bot + LLM (OpenAI-compatible) + Harness — บอทที่คุยกับผู้ใช้ผ่าน LLM พร้อมชุดเครื่องมือ (tools/harness) ให้ทำอะไรได้จริงใน Discord
- คุยกับ LLM แบบ OpenAI-compatible (สลับ provider ได้ผ่าน env)
- Agent loop (tool-calling): iris ตอบผ่านวง agent ที่เรียกใช้เครื่องมือได้จริง (มี
AGENT_MAX_TOOL_CALLS/AGENT_TOOL_TIMEOUT_SECONDSคุมการทำงาน) - บุคลิกจาก
persona.md+ ความจำระยะยาวmemory/(มีmemory.mdเป็นดัชนี) - บันทึก session + flag-memory (สรุปสาระสำคัญ) แบบ 1:1 — thread ใหม่ = session ใหม่; หลายคนใน thread เดียวกัน แชร์ session เดียวกัน แต่ทุกบรรทัดแนบ
user_id→ iris เห็นทั้ง thread พร้อมรู้ว่าใครพูด - กันคำลงท้ายผู้ชาย "ครับ" หลุดด้วยตัวกรองอัตโนมัติ (persona เป็นหญิง ใช้ คะ/ค่ะ)
- Harness:
time,search(SearXNG),mention_user,store memory,schedule task,music,request_tool(ขอเพิ่มเครื่องมือผ่าน GitHub issue) — ส่วนsteam_priceยัง deferred
สถานะ: อยู่ระหว่างพัฒนาตาม docs/PHASES.md
- Python 3.11
- uv
- Docker (สำหรับรัน SearXNG แบบ local — ดูหัวข้อ SearXNG)
- (Phase 9)
ffmpegสำหรับฟีเจอร์ music
git clone <repo-url> iris
cd iris
uv sync # ติดตั้ง dependencies (รวม dev)
cp .env.example .env # แล้วแก้ค่าให้ครบ| ตัวแปร | คำอธิบาย |
|---|---|
DISCORD_BOT_TOKEN |
token ของ Discord bot |
LLM_BASE_URL / LLM_API_KEY / LLM_MODEL |
LLM แบบ OpenAI-compatible |
SEARXNG_URL |
URL ของ SearXNG instance (รัน local ผ่าน make searxng-up) |
GITHUB_TOKEN / GITHUB_REPO |
ใช้โดย request_tool (Phase 10) |
ดูตัวแปรทั้งหมดใน .env.example
tool search ของ iris ค้นเว็บผ่าน SearXNG — รัน SearXNG ผ่าน docker compose ที่แยกไว้เฉพาะ (docker/searxng/)
iris เองยังไม่ได้ containerized (compose ของ iris จะมาทีหลังใน Phase 12):
make searxng-up # รัน SearXNG (ครั้งแรกจะ pull image) + รอจนพร้อม
make searxng-logs # ดู log แบบ realtime (Ctrl+C เพื่อออก)
make searxng-status # สถานะ container
make searxng-restart # รีสตาร์ท
make searxng-down # หยุด SearXNGตั้ง SEARXNG_URL=http://127.0.0.1:8080 ใน .env (default ใน .env.example มีให้แล้ว) → uv run iris ก็เชื่อมได้เลย
เช็คด้วยตัวเอง:
curl "http://127.0.0.1:8080/search?q=hello&format=json"uv run irisต้องตั้งค่าใน .env ก่อน: DISCORD_BOT_TOKEN (token ของ bot) + LLM_BASE_URL / LLM_API_KEY / LLM_MODEL
วิธีคุย: @iris หรือพิมพ์ prefix (iris, ...) ใน channel → iris จะสร้าง thread เป็นพื้นที่สนทนาและตอบในนั้น
ภายใน thread คุยต่อได้เลยโดยไม่ต้อง mention ซ้ำ (channel ที่สร้าง thread ไม่ได้ เช่น DM → ตอบตรงๆ)
ทุกข้อความที่ iris ตอบแล้วจะถูก add reaction ✅ กำกับ (เครื่องหมาย "ตอบแล้ว")
token/secret ที่ยังไม่ได้ตั้งจะ fail พร้อม error ที่ชัดเจนตอนรัน (ไม่ fail ตอน import)
Privileged intents: iris ใช้
Message ContentและServer Membersintent — ต้องเปิดทั้ง 2 ตัวใน Discord Developer Portal (Applications → bot ของคุณ → Bot → Privileged Gateway Intents) ก่อนรัน ถ้าไม่เปิดServer Members Intentบอทจะหาเพื่อนสมาชิกเจอเฉพาะตัวมันเอง → toolmention_userจะบอก "ไม่พบผู้ใช้" (tool มี fallback เรียก APIfetch_memberอยู่แล้ว แต่เปิด intent ให้ก็จะ resolve จาก cache ได้ทันที ไม่เสียรอบ API)
คุยกับ LLM ที่ตั้งค่าไว้ใน .env โดยตรง ไม่ต้องเปิดบอท (ยังไม่มี tool-calling loop):
uv run python -m iris.llm.chat "สวัสดี"
echo "ตอนนี้กี่โมง" | uv run python -m iris.llm.chatถ้าโมเดลขอใช้ tool จะแสดงเป็น [tool_call] <name>(<arguments>) (ยังไม่ execute — เป็นฝั่งล่างนี้ที่ execute จริง)
รันวง agent loop (tool-calling) เต็มรูปแบบโดยไม่ต้องเปิด Discord — โมเดลจะเรียกใช้ tool ได้จริง:
uv run python -m iris.agent "ตอนนี้กี่โมง" # ควรเห็น iris เรียก tool `time` แล้วตอบเวลาจริง- แก้
persona.mdแล้ว save → บุคลิกเปลี่ยนทันทีใน turn ถัดไป (โหลดจาก disk ใหม่ทุกครั้ง ไม่มี cache) - เพิ่ม/แก้ไฟล์ใน
memory/แล้วรัน regenerate index → ไฟล์ขึ้นในmemory.mdและถูก inject เข้า system prompt อัตโนมัติ:
uv run python -m iris.memory.index # regenerate memory.md จาก listing memory/คุยใน Discord 2 รอบใน thread เดียว → iris จำบริบทรอบแรกได้ (history โหลดจาก transcript);
thread ใหม่ → ได้ session ใหม่แยกไฟล์; หลายคนใน thread เดียวกัน → แชร์ session เดียวกัน แต่แต่ละบรรทัดมี user_id กำกับ
ls data/sessions/ # transcript JSONL ต่อ session (ชื่อไฟล์ = {guild_id}:{thread_id}; แต่ละบรรทัดผู้ใช้มี user_id)
ls data/flag-memory/ # สรุปสาระสำคัญ 1:1 กับ session (LLM ตัวย่อย condense ทุกจบ turn)สั่งใน Discord เช่น "จำไว้ว่าฉันชื่อไก่" / "ลืมเรื่อง X" → iris เรียก memory_write / memory_delete
สร้าง/ลบไฟล์ใน memory/ และ regenerate memory.md ให้อัตโนมัติ (ADR-007 บังคับในโค้ด — ไม่ต้องสั่ง index เอง)
ls memory/ # ไฟล์ที่ iris จำไว้ (เช่น users/kiki.md)
cat memory.md # ดัชนีที่ถูก regenerate ให้อัตโนมัติ
uv run python -m iris.memory.index # regenerate ด้วยมือ (กรณีคนสร้างไฟล์เอง)restart บอท แล้วถามเรื่องที่จำ → iris ยังตอบได้ (โหลดไฟล์จาก disk ทุก turn); ลองใช้ชื่อไฟล์ ../escape.md
หรือ .txt → ถูกบล็อกเป็น success=False ไม่มีการเขียนเกิดขึ้น (path traversal guard)
uv run pytest
uv run ruff check .
uv run mypy srcsrc/iris/ # โค้ดหลัก (ดู docs/SPEC.md)
agent.py # วง agent loop (tool-calling) — system prompt = persona + memory (Phase 4)
harness/ # framework ของ tools: base.py (ToolSpec/ToolResult/...) + registry.py
builtin/ # tools ที่ติดมากับ iris (time, ...)
bot.py # Discord bot + trigger + thread
llm/ # LLM gateway (OpenAI-compatible)
memory/ # MemoryStore (store.py) + regenerate memory.md (index.py) — Phase 4
session/ # SessionStore (store.py) + FlagMemory (flag.py) — Phase 5
docs/ # สเปค + แผนพัฒนา
persona.md # บุคลิก (Phase 4)
memory.md # ดัชนีความจำ (Phase 4)
memory/ # ความจำระยะยาว (Phase 4)
data/ # runtime data (sessions / flag-memory / schedules) — ไม่ commit
- token/secret fields มี default ว่างไว้เพื่อให้
import irisและ test รันได้โดยไม่ต้องมี.env— จะ fail ตอนรันจริงถ้าไม่ได้ตั้งค่า - ดู docs/DECISIONS.md สำหรับการตัดสินใจออกแบบ