รีโปมีไฟล์ราว 11,000 ไฟล์ เอเจนต์มีเครื่องมือ bash และคำสั่งคลุมเครือว่า "หาให้เจอว่าเราตรวจสอบลายเซ็นเวบฮุกตรงไหน" มันรัน grep -rn "signature" . ได้ผลลัพธ์ 600 รายการกระจายอยู่ในไฟล์ JS ของเวนเดอร์ ฟิกซ์เจอร์ทดสอบ ไฟล์ CHANGELOG และตัวช่วยคริปโตที่ไม่เกี่ยวข้องอีกสามตัว แล้วก็ชี้ไปยังไฟล์ที่ผิดอย่างมั่นใจ เราเฝ้าดูมันทำแบบนี้ติดต่อกันสี่ครั้งด้วยถ้อยคำต่างกัน ก่อนที่ใครบางคนจะพึมพำสิ่งที่เห็นได้ชัด: grep จับคู่สตริง แต่เอเจนต์กำลังถามคำถามเกี่ยวกับ ความหมาย

นั่นคือปัญหาทั้งหมดในประโยคเดียว grep และ rg ยอดเยี่ยมสำหรับ "โทเค็นนี้ปรากฏตรงไหนพอดี" แต่ไร้ประโยชน์สำหรับ "สิ่งที่ทำ X อยู่ที่ไหน" เพราะโค้ดที่ทำ X แทบไม่เคยมีคำที่คุณจะใช้บรรยาย X อยู่เลย ฟังก์ชันชื่อ verify_hmac คุณค้น "webhook signature" ทับซ้อนกันเป็นศูนย์ และเครื่องมือเดียวที่เอเจนต์เอื้อมหยิบเป็นอย่างแรกก็ไม่คืนอะไรที่มีประโยชน์เลย

ทางแก้คือการค้นหาเชิงความหมาย: เอ็มเบดโค้ด เอ็มเบดคำค้น แล้วหาชังก์ที่ความหมายใกล้เคียงที่สุด ส่วนที่ติดขัดคือคำแนะนำแบบ "ก็แค่ใช้เอ็มเบดดิงสิ" ส่วนใหญ่สันนิษฐานว่าคุณจะส่งซอร์สโค้ดไปยัง API คลาวด์และเช่า GPU สำหรับโมโนรีโปส่วนตัว ทั้งสองอย่างนั้นรับไม่ได้: ฝ่ายกฎหมายจะไม่อนุมัติการอัปโหลดโค้ดกรรมสิทธิ์ให้บุคคลที่สาม และคุณก็ไม่อยากได้บิลคิดต่อโทเค็นที่โตขึ้นตามทุกครั้งที่รีอินเด็กซ์

ดังนั้นนี่คือการตั้งค่าที่เราใช้งานจริง: Octocode ที่จัดทำดัชนีรีโปขนาดใหญ่เพื่อค้นหาเชิงความหมาย แบบโลคัลล้วน — ไม่ใช้ GPU ไม่ใช้เอ็มเบดดิงคลาวด์ ไม่มีอะไรออกจากเครื่อง นี่คือวิธีการทำงานที่แท้จริง คอนฟิกที่สำคัญ และสี่สิ่งที่เล่นงานเรา


ทำไม grep ถึงไม่พอ อย่างเป็นรูปธรรม

ก่อนจะเอื้อมไปหาเอ็มเบดดิง ควรชัดเจนเสียก่อนว่า ทำไม การค้นข้อความถึงล้มเหลวสำหรับเอเจนต์ เพราะมันบอกว่าสิ่งที่มาแทนต้องทำอะไร

  • คำศัพท์ไม่ตรงกัน คำค้นคือเจตนา ("rate limiting") แต่โค้ดคือกลไก (Semaphore, token_bucket, RetryAfter) ไม่มีโทเค็นร่วมกัน
  • ไม่มีการจัดอันดับ rg ให้ทุกผลลัพธ์ด้วยน้ำหนักเท่ากัน 600 ผลลัพธ์ไม่ใช่คำตอบ แต่เป็นปัญหาการค้นหาอันที่สอง
  • ไม่มีโครงสร้าง ผลลัพธ์ข้อความในคอมเมนต์ ในเทสต์ และในฟังก์ชันหลัก ดูเหมือนกันหมดสำหรับ grep มันบอกคุณไม่ได้ว่าอันที่สามคือนิยาม

สิ่งที่คุณต้องการแทนคือ: จัดอันดับตามความใกล้เชิง ความหมาย แต่ไม่ทิ้งสิ่งที่ grep เก่งจริง ๆ — การจับคู่ตัวระบุ (identifier) แบบตรงตัว คำตอบของ Octocode คือทำทั้งสองอย่างแล้วผสานเข้าด้วยกัน ซึ่งผมจะกล่าวถึงต่อไป ก่อนอื่น มาดูว่าโค้ดถูกแปลงให้อยู่ในรูปที่ค้นหาได้อย่างไร


Octocode แบ่งชังก์และจัดทำดัชนีโค้ดอย่างไร

RAG มาตรฐานหั่นไฟล์เป็นหน้าต่างข้อความขนาดคงที่ — ทุก N อักขระ เหลื่อมกันนิดหน่อย แล้วเอ็มเบด สำหรับร้อยแก้วก็โอเค แต่สำหรับโค้ดมันแย่อย่างจริงจัง: มันตัดฟังก์ชันออกเป็นสองชังก์ เอ็มเบดครึ่งหนึ่งของแขน match และสูญเสียข้อเท็จจริงที่ว่าเมธอดเป็นของไทป์

Octocode พาร์สแต่ละไฟล์ด้วย tree-sitter ก่อน เดินผ่าน AST และแบ่งชังก์ตามขอบเขตสัญลักษณ์จริง — ฟังก์ชัน เมธอด คลาส โมดูล — แทนที่จะใช้ตำแหน่งไบต์ที่กำหนดเอง สถาปัตยกรรมคือพาร์สเซอร์หนึ่งตัวต่อภาษาดึงฟังก์ชัน คลาส อิมพอร์ตและเอ็กซ์พอร์ตออกมา แล้วประมวลผลแบบแบ่งชังก์สำหรับอะไรก็ตามที่ใหญ่เกินกว่าจะเอ็มเบดเป็นชิ้นเดียว เมธอดที่ดึงมาจากภายในบล็อก impl หรือคลาสจะพกชื่อไทป์แม่ไว้ในรายการสัญลักษณ์ของมัน ดังนั้นการค้น Suppression.mark_set หรือ Foo.bar จึงเข้าชนชังก์เมธอดได้โดยตรง

การรองรับภาษาเป็นไวยากรณ์ tree-sitter จริง ไม่ใช่ฮิวริสติกแบบ regex ตามรีโป สิ่งที่จัดทำดัชนีเป็น โค้ด ด้วยการพาร์ส AST เต็มรูปแบบ:

ภาษา นามสกุลไฟล์
Rust .rs
Python .py
TypeScript / JavaScript .ts, .tsx, .js, .jsx
Go .go
PHP .php
C / C++ .c, .h, .cpp, .hpp, .cc, .cxx, รวมถึงนามสกุลโมดูล C++20 .cppm, .ixx, .mxx, .ccm, .cxxm
Ruby .rb
Java .java
Swift .swift
JSON, Bash, CSS, Lua, Svelte, Markdown เส้นทาง tree-sitter เฉพาะ

อย่างอื่นที่มีประโยชน์สำหรับเป็นบริบท (yaml, toml, dockerfile, makefile, ini, conf, env, xml, html, sql, rst และพวกพ้อง) จะจัดทำดัชนีเป็น บล็อกข้อความ จึงยังค้นหาได้ เพียงแต่ไม่มีการดึงสัญลักษณ์เชิงความหมาย

พารามิเตอร์การแบ่งชังก์อยู่ใต้ [index]:

[index]
chunk_size = 2000        # อักขระสูงสุดต่อชังก์โค้ด
chunk_overlap = 100      # การเหลื่อมระหว่างชังก์ที่ติดกัน
quantization = true      # การบีบอัดเวกเตอร์ RaBitQ ~32x สูญเสียคุณภาพน้อยมาก
require_git = true        # ค่าเริ่มต้นจัดทำดัชนีเฉพาะภายในรีโป git

chunk_size เป็นเพดาน ไม่ใช่เป้าหมาย — ฟังก์ชัน 40 บรรทัดที่เล็กกว่าขีดจำกัดจะอยู่ครบทั้งตัว ขีดจำกัดจะมีผลก็ต่อเมื่อบอดี้ใหญ่จริง ๆ และ Octocode ก็จัดทำดัชนีคลาสขนาดใหญ่ใน Python, TypeScript, C++ และ Ruby แบบทีละเมธอดอยู่แล้ว แทนที่จะเทคลาสทั้งคลาสลงในชังก์เดียว ประเด็นของทั้งหมดนี้: เมื่อคุณค้นหา "ฟังก์ชันที่จัดการการตั้งค่า remote pull" ในภายหลัง หน่วยที่ถูกเอ็มเบดคือ ฟังก์ชันนั้น พร้อมชื่อและไทป์แม่ที่แนบมา ไม่ใช่หน้าต่างที่บังเอิญคร่อมมันอยู่


เอ็มเบดดิงโลคัล: ไม่มี GPU ไม่มีคลาวด์ ไม่มี API key

ส่วนที่ผู้คนเหมาเอาว่าต้องใช้คลาวด์ — การแปลงโค้ดเป็นเวกเตอร์ — จริง ๆ แล้วไม่ต้อง

Octocode มาพร้อม สแตกแบบ local-first ที่สร้างบน fastembed (มี ONNX Runtime อยู่เบื้องหลัง) คุณชี้คอนฟิกไปยังโมเดลโลคัลด้วยพรีฟิกซ์ fastembed: แล้วเอ็มเบดดิงจะรันบน CPU บนเครื่องของคุณ:

[embedding]
code_model = "fastembed:jinaai/jina-embeddings-v2-base-code"   # 768 มิติ ออกแบบมาเพื่อโค้ดโดยเฉพาะ
text_model = "fastembed:nomic-ai/nomic-embed-text-v1.5"        # ข้อความและเอกสาร

สตริงโมเดลจะเป็น provider:model เสมอ พรีฟิกซ์ผู้ให้บริการคือการตัดสินใจเรื่องการกำหนดเส้นทางทั้งหมด: fastembed: และ huggingface: เป็นโลคัล (ไม่ต้องใช้คีย์ ไม่ต้องใช้เครือข่าย) ส่วน voyage:, jina:, google:, openai:, together:, octohub: เป็นคลาวด์และต้องการ API key ที่สอดคล้องในสภาพแวดล้อม จะผสมก็ได้ (เอ็มเบดโค้ดโลคัล เอ็มเบดข้อความคลาวด์) แต่สำหรับรีโปส่วนตัว เส้นทางโลคัลล้วนคือประเด็นสำคัญ

ที่นี่ไม่มีข้อกำหนดเรื่อง GPU และไม่มีเส้นทางโค้ดสำหรับ GPU: นี่คือการอนุมาน ONNX บน CPU บนเครื่องมัลติคอร์รุ่นใหม่ที่มี AVX2 มันเร็วพอที่ตัวเอ็มเบดเดอร์แทบไม่เคยเป็นคอขวด — โดยปกติคือ disk I/O และการพาร์ส tree-sitter ต่างหากที่เป็น บันทึกประสิทธิภาพของรีโปเองระบุว่าการจัดทำดัชนีด้วย FastEmbed แบบโลคัลอยู่ราว 45 วินาทีต่อ 1,000 ไฟล์ในเชิงลำดับขนาดคร่าว ๆ ผลลัพธ์ของคุณจะต่างกันไปตามขนาดไฟล์และจำนวนคอร์ จึงควรถือว่าเป็นตัวอย่างประกอบ ไม่ใช่คำสัญญา

ข้อควรระวังตรง ๆ สองข้อ:

  1. การรันครั้งแรกดาวน์โหลดโมเดล octocode index ครั้งแรกหลังชี้ไปยังโมเดล fastembed: จะดึงน้ำหนัก ONNX (ไม่กี่ร้อย MB) ลงไดเรกทอรีแคช (~/.local/share/octocode/fastembed/ บน Linux/macOS) เป็นต้นทุนครั้งเดียว หลังจากนั้นออฟไลน์ตลอดไป
  2. ผู้ให้บริการโลคัลถูกควบคุมด้วยฟีเจอร์แฟล็กและขึ้นกับแพลตฟอร์ม FastEmbed และ HuggingFace ต้องการฟีเจอร์ build fastembed/huggingface บิลด์ค่าเริ่มต้นจะมีให้ในที่ที่รองรับดี ส่วนแพลตฟอร์มที่ไม่รองรับ คุณจะถอยกลับไปใช้ผู้ให้บริการคลาวด์ ตรวจ octocode models list fastembed เพื่อดูว่าไบนารีของคุณเปิดเผยอะไรจริง ๆ งานเรื่องผู้ให้บริการเอ็มเบดดิงโลคัลใน 0.17.0 เมื่อไม่นานนี้คือสิ่งที่ทำให้รายการนี้ซื่อตรงว่าอะไรพร้อมใช้ที่ไหน

ถ้าคุณยังไม่เคยตั้งค่าการค้นหาเชิงความหมายเหนือโค้ดมาก่อน บทแนะนำการค้นหาโค้ดเชิงความหมายของ Octocode ครอบคลุมพื้นฐานเชิงแนวคิด โพสต์นี้สันนิษฐานว่าคุณผ่านขั้นนั้นแล้วและต้องการการตั้งค่าสำหรับโปรดักชัน


จัดทำดัชนีรีโป

เมื่อคอนฟิกพร้อม การจัดทำดัชนีคือคำสั่งเดียวจากรากของรีโป:

cd /path/to/big-monorepo
octocode index
# → Indexed 12,847 blocks across 342 files

สิ่งที่เกิดขึ้นจริงตามลำดับ: ค้นหาไฟล์ (เคารพกฎการเพิกเฉย) พาร์สแต่ละไฟล์ด้วย tree-sitter ดึงสัญลักษณ์ แบ่งชังก์ตามขอบเขต AST เอ็มเบดแต่ละชังก์แบบโลคัล แล้วเขียนทุกอย่างลงสโตร์แบบคอลัมน์ LanceDB ฐานข้อมูล ไม่ได้ อยู่ในรีโปของคุณ — มันถูกคีย์ตามโปรเจกต์ใต้ไดเรกทอรีข้อมูลของระบบ (~/.local/share/octocode/<project-id>/storage บน Linux/macOS) ซึ่งช่วยให้ working tree ของคุณสะอาด และหมายความว่า git clean -fdx จะไม่ล้างดัชนีของคุณ

แฟล็กที่มีประโยชน์:

octocode index --force      # เพิกเฉยแคชแบบ incremental สร้างใหม่จากศูนย์
octocode index --verbose    # ดูความคืบหน้าทีละไฟล์
octocode stats              # มีบล็อกโค้ด/ข้อความ/เอกสารกี่บล็อก โหนดกราฟ ความเก่าของดัชนี

octocode stats คือคำสั่งที่ควรรันเมื่อรู้สึกว่ามีอะไรผิดปกติ — มันบอกว่า HEAD ของคุณตรงกับคอมมิตที่จัดทำดัชนีล่าสุดหรือไม่ คุณจึงเห็นได้ในแวบเดียวว่าดัชนีเก่าแล้วหรือยัง

Incremental กับการรีอินเด็กซ์เต็ม

การรัน octocode index ครั้งต่อ ๆ ไปเป็นแบบ incremental — เฉพาะไฟล์ที่เปลี่ยนแปลงเท่านั้นที่ถูกพาร์สและเอ็มเบดใหม่ ทุกรอบการจัดทำดัชนีจบด้วยรอบ optimize ตาราง เพื่อให้เส้นทางคิวรียังเร็วเมื่อฐานข้อมูลโต (การจัดทำดัชนีแบบ incremental ซ้ำ ๆ เคยทิ้งหางที่ยังไม่ถูกจัดทำดัชนีซึ่งการค้นหาต้องสแกนแบบ brute-force ตอนนี้จัดการอัตโนมัติแล้ว)

เมื่อคุณเปลี่ยน วิธี การจัดทำดัชนี (กฎการแบ่งชังก์ใหม่ โมเดลเอ็มเบดดิงต่างออกไป) แบบ incremental จะไม่ย้อนกลับไปแบ่งชังก์ไฟล์ที่มีอยู่แล้วใหม่ การเปลี่ยนโมเดลเอ็มเบดดิงเป็นเรื่องใหญ่: เวกเตอร์จากโมเดลหนึ่งเทียบกับอีกโมเดลไม่ได้ หลังเปลี่ยน code_model หรือ text_model ให้สร้างใหม่แบบสะอาด:

octocode clear        # ทิ้งตารางที่จัดทำดัชนีไว้
octocode index        # สร้างใหม่ด้วยโมเดลใหม่

โหมด watch: ทำให้ดัชนีสดอยู่เสมอ

ดัชนีที่ทำครั้งเดียวจะเก่าทันทีที่มีคนคอมมิต สำหรับเอเจนต์ที่ควรตอบคำถามเกี่ยวกับสถานะ ปัจจุบัน ของรีโป ให้รัน watcher:

octocode watch              # รีอินเด็กซ์อัตโนมัติเมื่อไฟล์เปลี่ยน
octocode watch --debounce 5 # รอ 5 วินาทีหลังการเปลี่ยนแปลงครั้งสุดท้ายก่อนรีอินเด็กซ์
octocode watch --quiet

watcher ทำ debounce (ปรับได้ 1–30 วินาที) ดังนั้น git checkout ที่แตะ 400 ไฟล์จึงทริกเกอร์การรีอินเด็กซ์หนึ่งครั้ง ไม่ใช่ 400 ครั้ง มันเคารพกฎการเพิกเฉยแบบเดียวกับการจัดทำดัชนีเต็ม ดังนั้นการแก้ไฟล์ใน node_modules จึงไม่ปลุกมัน

ถ้าคุณรันเซิร์ฟเวอร์ MCP (หัวข้อถัดไป) ก็มีทางเลือกที่สะอาดกว่า: ตั้ง mcp_index = true ใต้ [index] แล้วเซิร์ฟเวอร์ MCP จะจัดทำดัชนีและเฝ้าดู ภายในโปรเซสเดียว — มีของที่รันอยู่ชิ้นเดียวแทนที่จะเป็นสองชิ้น ค่าเริ่มต้นคือ false ซึ่งให้บริการดัชนีที่มีอยู่แบบอ่านอย่างเดียวและสันนิษฐานว่าคุณจัดทำดัชนีแยกต่างหาก เปิดมันเมื่อคุณต้องการให้เซิร์ฟเวอร์เป็นเจ้าของวงจรชีวิตทั้งหมด


การค้นหาแบบไฮบริด: เชิงความหมาย และ แบบตรงตัว ผสานกัน

นี่คือส่วนที่แก้ความล้มเหลวของ grep แต่แรกโดยไม่ทิ้งจุดแข็งหนึ่งเดียวของ grep

การค้นหาด้วยเวกเตอร์ล้วนแพ้สำหรับคำค้นที่หนาแน่นด้วยตัวระบุ ค้น parse_remote แล้วเอ็มเบดดิงแบบ dense จะคืนสิ่งที่ ใกล้เชิงความหมาย กับ parse_remote อย่างเริงร่าในขณะที่พลาดฟังก์ชันที่ชื่อ parse_remote ตรง ๆ การค้นหาคีย์เวิร์ด BM25 มีจุดอ่อนตรงกันข้าม: มันจับตัวระบุแบบตรงตัวได้แม่นแต่พลาดเจตนาที่ถูกเรียบเรียงใหม่อย่าง "ฟังก์ชันที่จัดการการตั้งค่า remote pull"

Octocode รันทั้งสองในทุกคำค้นและผสานด้วย Weighted Reciprocal Rank Fusion ภายใน LanceDB น้ำหนักถูกเปิดเผยให้ปรับ:

[search.hybrid]
enabled = true
default_vector_weight  = 0.7
default_keyword_weight = 0.3

สำหรับรีโปที่หนาแน่นด้วยโค้ดซึ่งตัวระบุแบบตรงตัวให้สัญญาณมาก ให้เอนไปทางคีย์เวิร์ด เบนช์มาร์กการดึงข้อมูลของรีโปเอง (คำค้นการค้นหาโค้ด 127 รายการพร้อม ground truth แบบช่วงบรรทัด รันแบบโลคัลล้วนด้วย jina-embeddings-v2-base-code และ ไม่ใช้ รีแรงเกอร์) แสดงผลชัดเจน: การเปลี่ยนจากสัดส่วนเริ่มต้น 0.7/0.3 ไปเป็น 0.3/0.7 ที่จูนเข้าหาคีย์เวิร์ด ยก Hit@5 จาก 0.598 เป็น 0.732 และ Recall@10 จาก 0.671 เป็น 0.807 โดยไม่มีต้นทุนเพิ่ม นั่นคือ Hit@5 ที่ +22% จากคอนฟิกบรรทัดเดียว สำหรับเอกสารเนื้อยาว ให้เอนไปอีกทาง (0.8/0.2) เพราะเจตนาเป็นใหญ่ที่นั่น

เหนือการผสาน รีแรงเกอร์ที่เป็นทางเลือกจะจัดอันดับผู้สมัครอันดับต้น ๆ ใหม่:

[search.reranker]
enabled = true
model   = "fastembed:jina-reranker-v2-base-multilingual"   # รีแรงเกอร์โลคัล ไม่ต้องใช้คีย์
top_k_candidates = 50    # ดึงจำนวนนี้จากการค้นหาเวกเตอร์
final_top_k = 10         # คืนจำนวนนี้หลังการรีแรงก์

รีแรงเกอร์ก็รันแบบโลคัลถ้าคุณชี้ไปยังโมเดล fastembed: มันเป็นพาส cross-encoder บนผู้สมัคร 50 อันดับแรก ซึ่งจับกรณีที่การจัดอันดับขั้นแรกเรียงผิด ต้นทุนมีจริงแต่จำกัด — มันเห็นแค่ top_k_candidates ไม่ใช่ดัชนีทั้งหมด

จาก CLI พื้นผิวการค้นหาตรงไปตรงมา:

octocode search "webhook signature verification"
octocode search "auth" "middleware" "session"          # มัลติคำค้น ได้ถึง 10
octocode search "database connection pool" --mode code  # จำกัดเฉพาะบล็อกโค้ด
octocode search "auth" --detail-level signatures        # เฉพาะลายเซ็น กระชับ
octocode search "authentication refactor" --mode commits # ค้นประวัติ git

เส้นทาง --mode commits ควรรู้ไว้: ประวัติคอมมิตถูกจัดทำดัชนีแบบ lazy เมื่อค้นคอมมิตครั้งแรก (ไม่ใช่ระหว่าง octocode index) คุณจึงถามได้ว่า "เราเปลี่ยนโฟลว์ auth เมื่อไหร่" แล้วได้คอมมิตที่จัดอันดับเชิงความหมาย ไม่ใช่แค่ git log --grep


การค้นหาเชิงโครงสร้าง: เมื่อคุณต้องการแพตเทิร์น AST ไม่ใช่ความหมาย

บางครั้งคุณไม่ต้องการความหมายเลย — คุณต้องการ "ทุก .unwrap() ใน Rust" หรือ "ทุก new Foo() ใน JS" นั่นคือคำค้นเชิงโครงสร้าง และการค้นข้อความก็พลาดอยู่เรื่อย (คอมเมนต์ที่กล่าวถึง .unwrap() ไม่ใช่การเรียก) Octocode ครอบ ast-grep ไว้:

octocode grep '$FUNC.unwrap()' --lang rust
octocode grep 'new $CLASS($$$ARGS)' --lang javascript
octocode grep 'console.log($ARG)' --lang javascript --rewrite 'logger.info($ARG)' --update-all

$VAR จับคู่โหนด AST หนึ่งโหนด $$$ARGS จับคู่อาร์กิวเมนต์ชุดหนึ่ง และ --rewrite ทำรีแฟกเตอร์ที่รู้จัก AST ในที่ ส่วนที่มีประโยชน์จริงสำหรับเอเจนต์: LLM มักผิด kind ของโหนดอย่างสม่ำเสมอ — มันจะเขียน function_declaration สำหรับ Python ทั้งที่จริงคือ function_definition เมื่อแพตเทิร์นไม่จับคู่กับอะไรเลย Octocode จะลองการตีความที่ผ่อนลงเรื่อย ๆ และรู้ kind ที่ถูกต้องของแต่ละภาษา ดังนั้นความพยายามที่เกือบถูกของเอเจนต์จึงยังคืนผลลัพธ์แทนที่จะเป็นศูนย์เงียบ ๆ


GraphRAG: ความสัมพันธ์ ไม่ใช่แค่ความคล้าย

การค้นหาเชิงความหมายหาโค้ดที่ คล้าย กัน มันบอกคุณไม่ได้ว่า auth_middleware.rs อิมพอร์ต jwt.rs เรียก user_store.rs และเชื่อมเข้ากับ router.rs นั่นเป็นคำถามเชิงกราฟ และ Octocode สร้างกราฟระหว่างการจัดทำดัชนี

[graphrag]
enabled = true
use_llm = true

มันดึงเอจ imports, calls, extends และ implements จาก AST ในเก้าภาษา และด้วย use_llm = true ก็เพิ่มความสัมพันธ์เชิงสถาปัตยกรรมระดับสูงกว่า (configures, uses, แพตเทิร์น factory/observer/strategy) ที่ค้นพบโดย LLM จาก CLI:

octocode graphrag get-relationships --node-id "src/auth/middleware.rs"
octocode graphrag find-path --source-id "src/auth/mod.rs" --target-id "src/database/mod.rs"
octocode graphrag overview

นี่คือความต่างระหว่าง "ขอโค้ดที่ดูเหมือน auth" กับ "ขอทุกอย่างที่ ขึ้นอยู่กับ โมดูล auth" สำหรับเอเจนต์ที่กำลังรีแฟกเตอร์ คำถามที่สองคือคำถามที่กันไม่ให้อะไรพัง

มีลูกเล่นใหม่ที่เป็นทางเลือก: การขยายการดึงข้อมูลที่รู้จักกราฟ ด้วย [search] graph_expansion = true (และเปิด GraphRAG) การค้นหาจะดึงบล็อกโค้ดจากไฟล์ที่สัมพันธ์เชิงโครงสร้างกับผลลัพธ์อันดับต้นของคุณเข้ามา ก่อน การรีแรงก์ ดังนั้นคำค้นที่ตกลงบน auth-middleware อาจดึงตัวช่วย JWT ที่มันเรียกขึ้นมาได้ แม้ตัวช่วยนั้นจะไม่จับคู่กับข้อความคำค้นก็ตาม มันปิดไว้ตามค่าเริ่มต้น และคอมเมนต์ในโค้ดก็ตรงไปตรงมา: ทำ A/B บนชุดประเมินของคุณเองก่อนจะไว้ใจ เพราะการขยายอาจเพิ่มสัญญาณรบกวนได้ง่ายพอ ๆ กับสัญญาณ


ต่อเข้ากับเอเจนต์ผ่าน MCP

ทั้งหมดข้างต้นถูกเปิดเผยให้ผู้ช่วย AI ผ่าน เซิร์ฟเวอร์ MCP ในตัว นี่คือวิธีหลักในการใช้ Octocode — เอเจนต์ได้เครื่องมือ ไม่ใช่เชลล์

octocode mcp --path /path/to/your/project

หรือในคอนฟิกของไคลเอนต์ (Claude Desktop, Cursor, Windsurf, Claude Code):

{
	"mcpServers": {
		"octocode": {
			"command": "octocode",
			"args": ["mcp", "--path", "/path/to/your/project"]
		}
	}
}

เครื่องมือที่เอเจนต์เห็น ตรวจสอบกับเซิร์ฟเวอร์แล้ว:

เครื่องมือ MCP ทำอะไร
semantic_search การค้นหาไฮบริดเชิงความหมาย + คีย์เวิร์ด มัลติคำค้น ทุกโหมดรวมถึง commits
view_signatures โครงสร้างไฟล์ (ลายเซ็น คลาส อิมพอร์ต) ตาม glob โดยไม่ต้องอ่านไฟล์ทั้งไฟล์
structural_search การจับคู่แพตเทิร์น AST ผ่าน ast-grep พร้อมการกู้คืนด้วย kind
graphrag คำค้นความสัมพันธ์: search, get-node, get-relationships, find-path, overview

view_signatures คือฮีโร่ที่ไม่ได้รับการกล่าวถึง แทนที่เอเจนต์จะเผาบริบทด้วยการอ่านไฟล์ 800 บรรทัดสามไฟล์เพื่อหาลายเซ็นฟังก์ชัน มันขอลายเซ็นตาม glob แล้วได้โครงร่างมา (ในเวอร์ชันล่าสุดคุณส่งสตริง glob เดี่ยวได้ ไม่ใช่แค่อาร์เรย์ — เรื่องเล็ก ๆ ที่หยุดการเรียกเครื่องมือที่ผิดรูปแบบไปได้เยอะ)

สำหรับโมโนรีโปที่มีหลายโปรเจกต์ย่อย มีโหมดมัลติรีโป: octocode mcp --multi --path /workspace สแกนไดเรกทอรีย่อยชั้นแรกหารีโป git แล้วให้บริการทั้งหมดจากเอนด์พอยต์เดียว โดยแต่ละเครื่องมือจะได้อาร์กิวเมนต์ project ไว้เลือกเป้าหมาย เซิร์ฟเวอร์ MCP เดียว รีโปทุกตัวในเวิร์กสเปซ


สี่สิ่งที่เล่นงานเรา

เป็นเรื่องจริง ไม่ใช่สมมติ — นี่คือสิ่งที่ทำให้เราเสียเวลา

1. ดัชนีอยู่นอกรีโป และมันทำเอาทุกคนงง ครั้งแรกที่มีคนรันเอเจนต์บนโคลนใหม่เอี่ยม มันไม่มีดัชนี: ฐานข้อมูลอยู่แบบต่อโปรเจกต์ใต้ ~/.local/share/octocode/ ไม่ใช่ใน working tree โคลน ≠ จัดทำดัชนี ทางแก้คือบรรทัดเดียวในการ onboarding: หลังโคลน ให้รัน octocode index หนึ่งครั้ง ชัดเจนเมื่อมองย้อนกลับ แต่ไม่ชัดเลยตอนตีสอง

2. การเปลี่ยนโมเดลเอ็มเบดดิงทำให้ผลลัพธ์แย่ลงอย่างเงียบ ๆ มีคนเปลี่ยน code_model เพื่อลองโมเดลโลคัลตัวอื่น แต่ ไม่ได้ ล้างดัชนี คำค้นใหม่ถูกเอ็มเบดด้วยโมเดลใหม่ ชังก์เก่าถูกเอ็มเบดด้วยตัวเก่า เวกเตอร์เทียบกันไม่ได้ การจัดอันดับเป็นขยะ เวกเตอร์จากต่างโมเดลไม่ได้อยู่ในปริภูมิเดียวกัน เปลี่ยนโมเดลเอ็มเบดดิงเมื่อไหร่ ให้ octocode clear && octocode index เสมอ ไม่มีคำเตือนใดมาช่วยคุณตรงนี้ — มันแค่ค่อย ๆ แย่ลงเงียบ ๆ

3. สัดส่วนเวกเตอร์/คีย์เวิร์ดเริ่มต้นไม่เหมาะกับรีโปของเรา เรารันด้วย 0.7/0.3 (เน้นเวกเตอร์) อยู่หนึ่งสัปดาห์และพลาดคำค้นตัวระบุแบบตรงตัวอยู่เรื่อย โค้ดหนาแน่นด้วยตัวระบุ เบนช์มาร์กข้างบนแสดงว่าน้ำหนักที่จูนเข้าหาคีย์เวิร์ดดีกว่ามากสำหรับการค้นหาโค้ด การเปลี่ยนไปใช้ 0.3/0.7 คือการปรับคอนฟิกที่ให้ผลคุ้มที่สุดที่เราทำ

4. ไฟล์ไบนารีและไฟล์ที่ถูกเจเนอเรตไม่ถูกจัดทำดัชนี — ซึ่งถูกต้องแล้ว แต่ตรวจดูให้แน่ Octocode ข้ามไฟล์ไบนารี (มันตรวจ null byte และอัตราส่วนอักขระที่พิมพ์ได้ก่อนจะถือว่าเนื้อหาเป็นข้อความ) และเคารพ .gitignore, .git/info/exclude และไฟล์ .noindex นั่นคือสิ่งที่คุณต้องการพอดี — คุณไม่อยากได้เอ็มเบดดิงของบันเดิลเวนเดอร์ที่ถูกย่อ แต่ถ้าไดเรกทอรีที่ คุณสนใจ อยู่ใน gitignore (บางทีมใส่ API client ที่ถูกเจเนอเรตไว้ใน gitignore) มันจะไม่ถูกจัดทำดัชนี แล้วคุณจะงงว่าทำไมค้นไม่เจอ วาง .noindex เพื่อ ยกเว้น เส้นทางเพิ่มเติม ตรวจ .gitignore ถ้าบางอย่างที่คาดหวังไว้หายไป octocode stats จะแสดงจำนวนบล็อกให้คุณตรวจสอบความครอบคลุม


การตั้งค่าแบบ end-to-end ขั้นต่ำ

ประกอบรวมกัน นี่คือทั้งหมดสำหรับโมโนรีโปส่วนตัว แบบโลคัลล้วน:

# ~/.local/share/octocode/config.toml (หรือ override แบบโลคัลของโปรเจกต์)
[embedding]
code_model = "fastembed:jinaai/jina-embeddings-v2-base-code"
text_model = "fastembed:nomic-ai/nomic-embed-text-v1.5"

[search.hybrid]
enabled = true
default_vector_weight  = 0.3   # โค้ดหนาแน่นด้วยตัวระบุ → เอนเข้าหาคีย์เวิร์ด
default_keyword_weight = 0.7

[search.reranker]
enabled = true
model   = "fastembed:jina-reranker-v2-base-multilingual"

[graphrag]
enabled = true
use_llm = false   # ความสัมพันธ์จาก AST อย่างเดียว ไม่เรียก LLM ออฟไลน์ล้วน
cd /path/to/monorepo
octocode index                          # การรันครั้งแรกดาวน์โหลดโมเดล แล้วจัดทำดัชนี
octocode search "webhook signature verification"   # ตรวจความสมเหตุสมผล
octocode watch --quiet &                # ทำให้สดอยู่เสมอ
claude mcp add octocode -- octocode mcp --path .   # ต่อเข้ากับเอเจนต์

ในโฟลว์นั้นไม่มีอะไรแตะเครือข่ายเลยหลังการดาวน์โหลดโมเดลครั้งเดียว ไม่มี GPU ไม่มีบิลคิดต่อโทเค็น เอเจนต์ที่เคย grep หา "signature" แล้วชี้ผิดไฟล์ ตอนนี้เรียก semantic_search ได้ verify_hmac ขึ้นอันดับหนึ่ง และใช้ view_signatures ยืนยันก่อนจะแตะอะไร


คำถามที่พบบ่อย

ต้องใช้ GPU ไหม? ไม่ เอ็มเบดดิงโลคัลรันบน CPU ผ่าน ONNX Runtime CPU มัลติคอร์รุ่นใหม่ก็เพียงพอ ตัวเอ็มเบดเดอร์แทบไม่เคยเป็นคอขวด

มีโค้ดอะไรออกจากเครื่องของผมไหม? ด้วยโมเดล fastembed:/huggingface: และ graphrag.use_llm = false ไม่มี — การจัดทำดัชนี การค้นหา และการรีแรงก์ ล้วนเป็นโลคัล ผู้ให้บริการเอ็มเบดดิงคลาวด์และฟีเจอร์ GraphRAG/ข้อความคอมมิตที่ขับเคลื่อนด้วย LLM เป็นแบบ opt-in และต้องใช้คีย์อย่างชัดเจน

ดัชนีอยู่ที่ไหน และอยู่ในรีโปของผมไหม? อยู่ใต้ไดเรกทอรีข้อมูลของระบบ คีย์ตามโปรเจกต์ (~/.local/share/octocode/<project-id>/ บน Linux/macOS) ไม่ใช่ใน working tree git clean จะไม่แตะมัน โคลนใหม่จะไม่มีมัน

ฐานข้อมูลใหญ่แค่ไหน? ราว ๆ ~10KB ต่อไฟล์ โดยมีการควอนไทซ์ RaBitQ ให้การบีบอัดเวกเตอร์ ~32x รีโป 10k ไฟล์อยู่ที่หลักสิบถึงหลักร้อย MB ไม่ใช่กิกะไบต์

Incremental หรือรีอินเด็กซ์เต็ม? Incremental ตามค่าเริ่มต้น — เฉพาะไฟล์ที่เปลี่ยนถูกเอ็มเบดใหม่ บังคับสร้างใหม่เต็มด้วย octocode index --force หรือ octocode clear && octocode index หลังเปลี่ยนโมเดลเอ็มเบดดิง

ค้นประวัติ git ได้ไหม? ได้ octocode search "..." --mode commits คอมมิตถูกจัดทำดัชนีแบบ lazy เมื่อค้นคอมมิตครั้งแรก octocode index แรกจึงยังเร็วอยู่


— Don


Octocode เป็นโอเพนซอร์สภายใต้ Apache-2.0 สแตกแบบ local-first มาถึงใน 0.15.0 และรายการผู้ให้บริการเอ็มเบดดิงโลคัลใน 0.17.x ถ้าการตั้งค่าของคุณเจอกับดักที่โพสต์นี้ไม่ได้ครอบคลุม เปิด issue — นั่นคือวิธีที่เคล็ดลับเหล่านี้ส่วนใหญ่ถูกบันทึกไว้ตั้งแต่แรก