2 คะแนน โดย GN⁺ 2024-12-09 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • สามารถจำกัดการตอบกลับของโมเดลภายในเครื่องให้ตรงตาม JSON Schema ได้ ช่วยลดภาระการพาร์สหลังประมวลผลและทำให้ผลลัพธ์การดึงข้อมูลสม่ำเสมอยิ่งขึ้น
  • ผู้ใช้สามารถส่งสคีมาผ่าน พารามิเตอร์ format ใน cURL, Python และ JavaScript เพื่อขอผลลัพธ์แบบมีโครงสร้างด้วยวิธีเดียวกัน
  • ฝั่ง Python หากใช้ Pydantic และฝั่ง JavaScript หากใช้ Zod จะเชื่อมการนิยามสคีมาและการตรวจสอบผลลัพธ์ได้ง่ายในระดับโค้ด
  • ตัวอย่างแสดงการคืนค่าและตรวจสอบข้อมูลประเทศ ข้อความเกี่ยวกับสัตว์เลี้ยง และผลการวิเคราะห์ภาพจาก llama3.2-vision ให้อยู่ในฟิลด์ JSON ที่กำหนดไว้
  • หากต้องการผลลัพธ์ที่เสถียร แนะนำให้ใส่ “return as JSON” ในพรอมป์ต์และตั้งค่า temperature เป็น 0

จำกัดการตอบกลับของโมเดลด้วย JSON Schema

  • Ollama รองรับ structured outputs ที่จำกัดให้เอาต์พุตของโมเดลเป็นไปตามรูปแบบ JSON Schema ที่กำหนด
  • ไลบรารี Python และ JavaScript ของ Ollama ก็ได้รับการอัปเดตให้รองรับ structured outputs แล้ว
  • ฟีเจอร์นี้สามารถนำไปใช้กับงานต่อไปนี้
    • พาร์สข้อมูลจากเอกสาร
    • ดึงข้อมูลจากภาพ
    • จัดโครงสร้างการตอบกลับของโมเดลภาษา
    • ให้ความน่าเชื่อถือและความสม่ำเสมอสูงกว่า JSON mode

การติดตั้งและวิธีเรียกใช้งาน

  • ต้องดาวน์โหลด Ollama เวอร์ชันล่าสุด
  • ไลบรารี Python อัปเดตเป็นเวอร์ชันล่าสุดด้วยคำสั่งต่อไปนี้
pip install -U ollama
  • ไลบรารี JavaScript ติดตั้งด้วยคำสั่งต่อไปนี้
npm i ollama
  • เมื่อต้องการขอ structured outputs ให้ส่งสคีมาผ่าน พารามิเตอร์ format ในคำขอ cURL หรือผ่านไลบรารี Python และ JavaScript

ส่ง JSON Schema ด้วย cURL

  • ตัวอย่าง cURL จะส่งคำขอไปที่ http://localhost:11434/api/chat โดยตั้ง model เป็น llama3.1 และ stream เป็น false
  • ใน format จะใส่อ็อบเจ็กต์สคีมาที่มี name, capital, languages
    • name: สตริง
    • capital: สตริง
    • languages: อาร์เรย์ของสตริง
    • ทั้งสามฟิลด์ถูกกำหนดเป็น required
  • การตอบกลับจะถูกส่งคืนในรูปแบบ JSON Schema ที่รวมอยู่ในคำขอ
{
  "capital": "Ottawa",
  "languages": [
    "English",
    "French"
  ],
  "name": "Canada"
}

การใช้ไลบรารี Python และ JavaScript

  • Python

    • Ollama Python library ส่งสคีมาเป็นอ็อบเจ็กต์ JSON ผ่านพารามิเตอร์ format
    • สามารถส่งสคีมาเป็น dict ได้ และวิธีที่แนะนำคือซีเรียลไลซ์ด้วย model_json_schema() ของ Pydantic
    • ตัวอย่างกำหนดฟิลด์ name, capital, languages ในโมเดล Country และตรวจสอบผลลัพธ์ด้วย Country.model_validate_json()
country = Country.model_validate_json(response.message.content)
  • เอาต์พุตตัวอย่างจะคืนค่า Canada, Ottawa, English, French ให้ตรงกับฟิลด์ที่กำหนด
  • JavaScript

    • Ollama JavaScript library ส่งสคีมาเป็นอ็อบเจ็กต์ JSON ผ่านพารามิเตอร์ format
    • สามารถส่งสคีมาเป็น object ได้ และวิธีที่แนะนำคือใช้ Zod ร่วมกับ zodToJsonSchema()
    • ตัวอย่างนิยามสคีมา Country เป็นอ็อบเจ็กต์ของ Zod แล้วตรวจสอบผลลัพธ์โดยใช้ JSON.parse() กับเนื้อหาการตอบกลับก่อน จากนั้นใช้ Country.parse()
const country = Country.parse(JSON.parse(response.message.content));

การดึงข้อมูลจากข้อความและภาพ

  • การดึงข้อมูลข้อความเกี่ยวกับสัตว์เลี้ยง

    • structured outputs สามารถใช้ดึงข้อมูลที่ต้องการจากข้อความได้
    • ตัวอย่างนิยามโมเดล Pydantic คือ Pet และ PetList เพื่อคืนค่าข้อมูลสัตว์เลี้ยงในโครงสร้าง JSON
      • Pet: name, animal, age, color, favorite_toy
      • PetList: อาร์เรย์ pets
    • ข้อความอินพุตมีข้อมูลของแมวสองตัว
      • Luna: อายุ 5 ปี ขนสีเทา ชอบ yarn
      • Loki: อายุ 2 ปี สีดำ ชอบ tennis balls
    • เอาต์พุตจะถูกตรวจสอบเป็นรายการของอ็อบเจ็กต์ Pet ตามสคีมาที่กำหนด
  • คำอธิบายภาพด้วยวิชันโมเดล

    • structured outputs สามารถใช้ร่วมกับ vision model ได้
    • ตัวอย่างใช้ llama3.2-vision วิเคราะห์ภาพและคืนค่าผลลัพธ์ตามสคีมา ImageDescription
    • สคีมามีฟิลด์ต่อไปนี้
      • summary
      • objects
      • scene
      • colors
      • time_of_day
      • setting
      • text_content
    • คำขอตัวอย่างสั่งให้วิเคราะห์วัตถุ ฉาก สี และข้อความที่ตรวจจับได้ในภาพ
    • ใน options มีการตั้ง temperature เป็น 0 เพื่อให้ใช้เอาต์พุตที่ เป็นแบบกำหนดแน่นอนมากขึ้น
    • เอาต์พุตตัวอย่างจะสรุปฉากต้นปาล์มริมชายหาด และคืนค่าวัตถุ tree กับ beach รวมถึงสี ช่วงเวลา และการตั้งค่าแบบกลางแจ้งในฟิลด์ที่มีโครงสร้าง

การใช้ OpenAI-compatible API

  • ตัวอย่างแบบ OpenAI-compatible ตั้งค่าไคลเอนต์ OpenAI ด้วย base_url="http://localhost:11434/v1"; และ api_key="ollama"
  • ใช้ client.beta.chat.completions.parse() โดยส่งโมเดล Pydantic PetList ผ่าน response_format
  • ตรวจสอบ completion.choices[0].message ในการตอบกลับ และหากมี parsed ก็จะแสดงผลลัพธ์ที่พาร์สแล้ว
  • หากมี refusal ก็จะแสดงผลการปฏิเสธ และจัดการ openai.LengthFinishReasonError เป็นกรณีที่มีโทเคนมากเกินไป

การตั้งค่าเพื่อผลลัพธ์ที่เสถียรและแผนในอนาคต

  • แนะนำให้ใช้ Pydantic ใน Python หรือ Zod ใน JavaScript สำหรับนิยาม response schema
  • ควรเพิ่ม “return as JSON” ในพรอมป์ต์เพื่อช่วยให้โมเดลเข้าใจคำขอ
  • หากต้องการเอาต์พุตที่กำหนดแน่นอนมากขึ้น ให้ตั้ง temperature เป็น 0
  • แผนในอนาคตมีดังนี้
    • เปิดเผย logits เพื่อการสร้างแบบควบคุม
    • ปรับปรุงประสิทธิภาพและความแม่นยำของ structured outputs
    • เร่งความเร็วด้วย GPU สำหรับการสุ่มตัวอย่าง
    • รองรับรูปแบบเพิ่มเติมนอกเหนือจาก JSON Schema

1 ความคิดเห็น

 
GN⁺ 2024-12-09
ความคิดเห็นจาก Hacker News
  • หากต้องการข้อจำกัดของเอาต์พุตที่เข้มงวดกว่า llama.cpp รองรับ GBNF
    https://github.com/ggerganov/llama.cpp/blob/master/grammars/...

    • ดูเหมือนจะใช้สิ่งนั้นอยู่พอดี
    • สงสัยว่าคุณภาพเอาต์พุตสำหรับไวยากรณ์แบบกำหนดเองน่าพอใจหรือไม่
      ถ้ามองแบบง่าย ๆ เพราะโมเดลเห็น JSON มามากกว่ามาก จึงน่าจะสร้าง JSON ได้ดีกว่ารูปแบบอื่น ๆ
    • สงสัยว่าแข็งแกร่งกว่าในแง่ไหน
  • เป็นข่าวดี
    ตอนสร้างข้อมูล CSV เคยกังวลว่าจะเขียนพรอมป์ทั่วไปอย่างไรไม่ให้มีข้อความไม่จำเป็นอย่าง "Here is your data" หรือ "Please note blah blah" ติดมาข้างหน้าข้างหลัง ตอนนี้ดีใจที่สามารถกำหนดรูปแบบการคืนค่าที่ต้องการได้อย่างแม่นยำ แล้วส่ง เอาต์พุตแบบมีโครงสร้าง ไปเป็น CSV ได้เลย

    • ถึงอย่างนั้นก็ยังต้องใส่ คำสั่งให้สร้าง CSV เพื่อให้พรอมป์อยู่ในบริบทที่ถูกต้อง
      ไม่เช่นนั้นอาจได้เอาต์พุตที่ในเชิงเทคนิคเป็นรูปแบบ CSV แต่ไม่มีความหมาย อาจเป็นกรณีที่จริง ๆ โมเดลตั้งใจจะเขียนคำตอบเป็นย่อหน้า แต่ token sampler เลือกโทเค็นความน่าจะเป็นต่ำที่โมเดลไม่ได้อยากพูดนัก
    • ในหลายกรณี การเติมจุดเริ่มต้นของเอาต์พุตไว้ล่วงหน้าด้วย \n แล้วให้หยุดที่ ก็ช่วยป้องกันปัญหานี้ได้
  • ใช้งานได้ ผมใส่ประโยคด้านล่างลงใน gemma2:2b แล้วได้ JSON ตามที่ต้องการ
    You have spent 190 at Fresh Mart. Current balance: 5098
    ผลลัพธ์คือ {"amount": 190, "balance": 5098, "category": "Shopping", "place": "Fresh Mart"}

    • เป็น JSON ที่ค่อนข้างไม่สม่ำเสมอ แต่ก็เป็นโมเดลขนาดเล็กมาก แถมยังเป็น gemma ก็พอเข้าใจได้
  • น่าทึ่งจริง ๆ นี่เป็นหนึ่งในฟีเจอร์ที่อยากได้พอดี
    เหตุผลที่ ollama ดีคือมันให้ความรู้สึกเหมือนใช้ LLM เป็นโปรแกรม UNIX อีกตัวหนึ่ง และทำให้รู้สึกว่า LLM อยู่ใน UNIX ได้อย่างเป็นธรรมชาติ
    แต่สงสัยว่ามีใครรันบน AMD GPU ได้ดีบ้างหรือเปล่า ได้ยินว่ายากกว่า แต่ปีหน้าเวลาซื้อการ์ดอยากสนับสนุนคู่แข่ง

    • ทำได้ แม้แต่ GPU ในตัวก็ได้
      ผมจัดสรรหน่วยความจำแชร์ 16GB ผ่าน BIOS บนมินิพีซีที่มี 780M แล้วก็รันได้ค่อนข้างดี
  • สงสัยว่าข้อจำกัดแบบนี้ส่งผลต่อ คุณภาพเอาต์พุตของ LLM อย่างไร
    ในบางกรณี ถ้าคุณภาพเอาต์พุตดีกว่า ก็อยากเลือกพาร์ส Markdown หรือข้อความธรรมดามากกว่า

    • ตอนใช้โมเดล OpenAI ถ้ารับค่าใช้จ่ายโทเค็นเพิ่มเติมได้ กลยุทธ์ รันสองรอบ ใช้ได้ดีมาก
      รอบแรกใช้โมเดลหนักกับภาษาธรรมชาติ ให้จัดการการให้เหตุผลเป็นส่วน ๆ ใน Markdown และให้คำตอบสุดท้ายเป็นภาษาธรรมชาติ ถ้าเป็นไปได้ให้ติดป้ายชัดเจนด้วยหัวข้อ Markdown
      รอบที่สองใช้โมเดลที่ถูกกว่าและเร็วกว่า แปลงคำตอบนั้นเป็นรูปแบบเอาต์พุตแบบมีโครงสร้าง เพื่อให้ส่วนที่ไม่ใช่ LLM ของไปป์ไลน์นำไปใช้
      โดยพื้นฐานคือใช้โหมด JSON schema เพื่อสร้างขอบเขตที่สะอาดรอบ ๆ ส่วนภาษาธรรมชาติที่กำกวม และให้ LLM ทำหน้าที่เป็นพรีโปรเซสเซอร์ที่จับเอาต์พุตของตัวเองให้อยู่ในรูปแบบที่ใช้งานได้
    • ขึ้นอยู่กับว่าโมเดลถูก fine-tune มาสำหรับ เอาต์พุต JSON มากแค่ไหน
      และต้องบอกสคีมาให้โมเดลรู้ด้วย ถ้าไม่บอกจะเกิดปัญหา tokenization แปลก ๆ มากขึ้น
      ตัวอย่างเช่น หากสคีมาคาดหวังคีย์ JSON "foobarbaz" และการทำ BPE tokenization มาตรฐานได้เป็น ["foobar", "baz"] token mask ที่ไลบรารี constrained output ปัจจุบันสร้างขึ้น อาจอนุญาตให้โมเดลเลือกได้ระหว่าง "f", "foo", "foobar" ถ้าโมเดลเลือก "foo" ข้อจำกัดจะบังคับโทเค็นถัดไป เช่น "bar" และ "baz" จากนั้นโมเดลจะเห็น ["foo", "bar", "baz"] แทนที่จะเป็น ["foobar", "baz"] ทำให้สับสน [0]
      ถ้าโมเดลรู้จากพรอมป์ว่า "foobarbaz" เป็นหนึ่งในคีย์ของสคีมา โดยทั่วไปมันจะชอบ "foobar" มากกว่า "foo"
      [0] ในโมเดลรุ่นใหม่ ๆ โทเค็นเหล่านี้เกี่ยวข้องกันเพราะ normalization แต่ไม่ใช่สิ่งเดียวกัน
    • แล้วแต่สถานการณ์ แต่ในแง่ การให้เหตุผล มีผลกระทบเชิงลบ และส่วนใหญ่แล้วความแตกต่างไม่ได้ใหญ่มาก
      ขึ้นอยู่มากว่า LLM และพรอมป์เดิมมีแนวโน้มจะสร้างคำตอบ JSON อยู่แล้วหรือไม่ ยิ่งบังคับ LLM มากเท่าไร โอกาสที่จะสร้างอินพุตปกติก็ยิ่งลดลง
      ในโมเดลขนาดเล็ก จะไปถึงขอบของพื้นที่ที่มีพลังทำนายที่มีความหมายได้เร็วกว่า และเอาต์พุตจะเริ่มใกล้เคียงสัญญาณรบกวนแบบสุ่ม
      นี่ไม่ใช่การวัดอย่างเข้มงวด เป็นการประเมินจากความรู้สึกหลังใช้เวลากับหลายโปรเจกต์ LLM มานาน ผมยังไม่ได้ลองเครื่องมือเฉพาะเหล่านี้ แต่ก่อนหน้านี้ ollama ก็สามารถรับประกันเอาต์พุต JSON ด้วยวิธีที่ดูคล้ายเทคนิคแบบนี้ได้ และผมเคยทำงานร่วมกับพาร์ตเนอร์เกี่ยวกับสิ่งคล้าย jsonformer สำหรับ oobabooga ซึ่งเป็นเครื่องมือรันไทม์ LLM อีกตัวหนึ่งด้วย
    • กำลังติดตามงานวิจัยที่เกี่ยวข้องอยู่เรื่อย ๆ กำลังดูว่าจะปรับปรุง sampling โดยรวมทั้งด้านความเร็วและความแม่นยำได้อย่างไร
      หวังว่าหากมีการเปลี่ยนแปลงแบบนั้นเข้ามา จะทำให้สร้างโครงสร้างทั่วไปที่ไม่จำกัดแค่ JSON ได้ด้วย
    • ผมยอมบอกได้ว่าตัวเองคิดผิดโดยสิ้นเชิงเกี่ยวกับประโยชน์ของเครื่องมืออย่าง instructor
      มันเผาโทเค็นได้ง่าย แต่ถ้างานที่ทำคุ้มกับต้นทุนนั้น ก็สามารถดันไปได้ค่อนข้างแรง แม้จะไม่ใช่คุณภาพสูงสุดแบบเด็ดขาด แต่เครื่องมือที่ให้ผลระดับ 95% ได้โดยแทบไม่ต้องพยายาม ก็คุ้มพอที่จะมีไว้ในกล่องเครื่องมือ
  • สงสัยว่าใช้กับโมเดลใดก็ได้ที่รองรับหรือเปล่า
    ฮาร์ดแวร์ของผมรันได้เสถียรแค่ โมเดล 1B~3B เลยถามดู

    • ผมเป็นผู้เขียนบล็อกโพสต์ ควรใช้ได้กับโมเดลใดก็ได้
      ในโมเดลขนาดเล็ก ผลลัพธ์อาจแกว่งไปมา แต่การขอให้ “คืนค่า x เป็น JSON” มักช่วยเพิ่มความแม่นยำได้
  • PR ที่เกี่ยวกับฟีเจอร์นี้เปิดค้างมาเกือบ 1 ปี
    น่าเสียดายนิดหน่อยที่ maintainer เงียบเกินไป

    • ผมเป็นผู้เขียนบทความและเป็นหนึ่งใน maintainer เห็นด้วยครับ maintainer ตอบสนองช้า และโดยรวมอยากส่งเสริมให้มีการมีส่วนร่วมมากขึ้น
      หวังว่าปีหน้าจะดูแลและ merge PR จากชุมชนได้ดีกว่านี้
    • ดูจากบรรยากาศแล้วเหมือนกำลังไปทางที่บริษัทเป็นศูนย์กลาง และดูเหมือนมองทุกอย่างผ่านเลนส์นั้นกับมุมมอง การเพิ่มรายได้ให้สูงสุด
    • ผมเองก็อยากร่วมมือช่วยสร้างสิ่งนี้เหมือนกัน แต่บทสนทนาก็หยุดไปกะทันหัน
  • ผมยังใช้ oobabooga อยู่ ด้วยการรองรับ exlv2 ทำให้รัน inference บนการ์ด 3090 คู่ได้มีประสิทธิภาพกว่ามาก

    • ผมไม่ได้แตะ ooba มาพักหนึ่งแล้ว เลยสงสัยว่าสถานการณ์ของ exl2 กับวิธี quantization แบบไม่สม่ำเสมออย่าง q3k_s เป็นอย่างไรบ้าง
      เท่าที่จำได้ แม้ exl2 จะเร็วกว่า แต่โดยเฉพาะที่ bit depth ต่ำ ๆ ดูเหมือนว่า quantization แบบ gptq จะดีกว่าในแง่ความแม่นยำ
  • สงสัยว่ามีมูลค่าเพิ่มอะไรเมื่อเทียบกับ outlines
    https://www.souzatharsis.com/tamingLLMs/notebooks/structured...

    • ผมเป็นผู้เขียนบล็อกนี้เอง การทำงานปัจจุบันใช้ llama.cpp GBNF จึงสามารถทำออกมาได้อย่างรวดเร็ว มูลค่าเพิ่มที่ใหญ่ที่สุดในตอนนี้คือการที่ฟีเจอร์ถูกปล่อยออกมาแล้ว
      หวังว่าจะอัปเดตการสุ่มตัวอย่างให้รองรับรูปแบบได้มากขึ้น เพิ่มความแม่นยำ และปรับปรุงประสิทธิภาพ โดยอิงจากงานวิจัยล่าสุดเกี่ยวกับ outlines/xgrammar
  • สงสัยว่ามีวิธีที่ดีที่สุดในการให้ อินพุตแบบมีโครงสร้าง แก่ LLM หรือไม่
    เช่น กรณีใส่ประโยค 100 ประโยคเข้าไป แล้วให้จัดหมวดหมู่แต่ละประโยคได้หลายวิธี การรับข้อมูลแบบมีโครงสร้างนั้นง่าย แต่แนวทางของผมที่ใส่เลขบรรทัดไว้ข้างหน้ารู้สึกเทอะทะ

    • โมเดลถูกฝึกมาด้วย Markdown, JSON และภาษาโปรแกรมหลายภาษา ดังนั้นหนึ่งในรูปแบบเหล่านั้นน่าจะใช้ได้
      แต่ในกรณีนี้ วิธีที่ดีที่สุดน่าจะเป็นการส่งประโยคทีละประโยค เพื่อไม่ให้โมเดลสับสน
      ถ้าจัด prompt เป็นรูปแบบ "จงจัดหมวดหมู่ประโยคต่อไปนี้ กฎคือ ..." + ประโยค ก็จะใช้ประโยชน์จาก prefix cache ได้ และประสิทธิภาพอาจดีกว่าการถามทีเดียวทั้งหมดด้วย
      แน่นอนว่าวิธีนี้เป็นไปได้เมื่อมี prefix cache และไม่ได้คิดเงินตามจำนวนโทเคนอินพุต ทุกวันนี้ผู้ให้บริการส่วนใหญ่จะให้ใช้ในราคาที่ต่ำลงหากแสดงเจตนาว่าต้องการใช้ prefix cache