Buckets:
| # 구조화된 생성으로 근거 강조 표시가 있는 RAG 시스템 구축하기 | |
| _작성자: [Aymeric Roucher](https://huggingface.co/m-ric), 번역: [유용상](https://huggingface.co/4n3mone)_ | |
| **구조화된 생성**(Structured generation)은 LLM 출력이 특정 패턴을 따르도록 강제하는 방법입니다. | |
| 이 방법은 여러 가지 용도로 사용될 수 있습니다: | |
| - ✅ 특정 키가 있는 딕셔너리 출력 | |
| - 📏 출력이 N글자 이상이 되도록 보장 | |
| - ⚙️ 더 일반적으로, 다운스트림 처리를 위해 출력이 특정 정규 표현식 패턴을 따르도록 강제 | |
| - 💡 검색 증강 생성(RAG)에서 답변을 뒷받침하는 소스를 강조 표시 | |
| 이 노트북은 마지막 예시를 구체적으로 보여줍니다. | |
| **➡️ 우리는 답변을 제공할 뿐만 아니라 이 답변의 근거가 되는 스니펫을 강조 표시하는 RAG 시스템을 구축합니다.** | |
| _RAG에 대한 소개가 필요하다면, [이 쿡북](advanced_rag)을 확인해 보세요._ | |
| 이 노트북은 먼저 프롬프트를 통한 구조화된 생성의 단순한 접근 방식을 보여주고 그 한계를 강조한 다음, 더 효율적인 구조화된 생성을 위한 제한된 디코딩(constrained decoding)을 시연합니다. | |
| 이 노트북은 HuggingFace Inference Endpoints를 활용합니다 (예제는 [서버리스](https://huggingface.co/docs/api-inference/quicktour) 엔드포인트를 사용하지만, [전용](https://huggingface.co/docs/inference-endpoints/en/guides/access) 엔드포인트로 변경할 수 있습니다), 또한 [outlines](https://github.com/outlines-dev/outlines)라는 구조화된 텍스트 생성 라이브러리를 사용한 로컬 추론 예제도 보여줍니다. | |
| ```python | |
| !pip install pandas json huggingface_hub pydantic outlines accelerate -q | |
| ``` | |
| ```python | |
| import pandas as pd | |
| import json | |
| from huggingface_hub import InferenceClient | |
| pd.set_option("display.max_colwidth", None) | |
| ``` | |
| ```python | |
| repo_id = "mistralai/Mistral-Nemo-Instruct-2407" | |
| llm_client = InferenceClient(model=repo_id, timeout=120) | |
| # Test your LLM client | |
| llm_client.text_generation(prompt="대한민국의 수도는?", max_new_tokens=50) | |
| ``` | |
| ## 모델에 프롬프트 제공하기 | |
| 모델에서 구조화된 출력을 얻으려면, 충분히 성능이 좋은 모델에 적절한 지시사항을 포함한 프롬프트를 제공하면 됩니다. 대부분의 경우 이 방법이 잘 작동할 것입니다. | |
| 이번 경우, 우리는 RAG 모델이 답변뿐만 아니라 신뢰도 점수와 근거가 되는 스니펫도 함께 생성하기를 원합니다. | |
| 이러한 출력을 JSON 형식의 딕셔너리로 생성하면, 나중에 쉽게 처리할 수 있습니다 (여기서는 근거가 되는 스니펫을 강조하여 표시할 예정입니다). | |
| ```python | |
| RELEVANT_CONTEXT = """ | |
| 문서: | |
| 오늘 서울의 날씨가 정말 좋네요. | |
| Transformers에서 정지 시퀀스를 정의하려면 파이프라인 또는 모델에 stop_sequence 인수를 전달해야 합니다. | |
| """ | |
| ``` | |
| ```python | |
| RAG_PROMPT_TEMPLATE_JSON= """문서를 기반으로 사용자 쿼리에 응답합니다. | |
| 다음은 문서입니다: {context} | |
| 답변을 JSON 형식으로 제공하고, 답변의 직접적 근거가 된 문서의 모든 관련 짧은 소스 스니펫과 신뢰도 점수를 0에서 1 사이의 부동 소수점으로 제공해야 합니다. | |
| 근거 스니펫은 전체 문장이 아닌 기껏해야 몇 단어 정도로 매우 짧아야 합니다! 그리고 문맥에서 정확히 동일한 문구와 철자를 사용하여 추출해야 합니다. | |
| 답변은 다음과 같이 작성해야 하며, “Answer:” 및 “End of answer.” 를 포함해야 합니다. | |
| Answer: | |
| {{ | |
| “answer": 정답 문장, | |
| “confidence_score": 신뢰도 점수, | |
| “source_snippets": [“근거_1”, “근거_2”, ...] | |
| }} | |
| End of answer. | |
| 이제 시작하세요! | |
| 다음은 사용자 질문입니다: {user_query}. | |
| Answer: | |
| """ | |
| ``` | |
| ```python | |
| USER_QUERY = "Transformers에서 정지 시퀀스를 어떻게 정의하나요?" | |
| ``` | |
| ```python | |
| >>> prompt = RAG_PROMPT_TEMPLATE_JSON.format( | |
| ... context=RELEVANT_CONTEXT, user_query=USER_QUERY | |
| ... ) | |
| >>> print(prompt) | |
| ``` | |
| 문서를 기반으로 사용자 쿼리에 응답합니다. | |
| 다음은 문서입니다: | |
| 문서: | |
| 오늘 서울의 날씨가 정말 좋네요. | |
| Transformers에서 정지 시퀀스를 정의하려면 파이프라인 또는 모델에 stop_sequence 인수를 전달해야 합니다. | |
| 답변을 JSON 형식으로 제공하고, 답변의 직접적 근거가 된 문서의 모든 관련 짧은 소스 스니펫과 신뢰도 점수를 0에서 1 사이의 부동 소수점으로 제공해야 합니다. | |
| 근거 스니펫은 전체 문장이 아닌 기껏해야 몇 단어 정도로 매우 짧아야 합니다! 그리고 문맥에서 정확히 동일한 문구와 철자를 사용하여 추출해야 합니다. | |
| 답변은 다음과 같이 작성해야 하며, “Answer:” 및 “End of answer.” 를 포함해야 합니다. | |
| Answer: | |
| { | |
| “answer": 정답 문장, | |
| “confidence_score": 신뢰도 점수, | |
| “source_snippets": [“근거_1”, “근거_2”, ...] | |
| } | |
| End of answer. | |
| 이제 시작하세요! | |
| 다음은 사용자 질문입니다: Transformers에서 정지 시퀀스를 어떻게 정의하나요?. | |
| Answer: | |
| ```python | |
| >>> answer = llm_client.text_generation( | |
| ... prompt, | |
| ... max_new_tokens=256, | |
| ... ) | |
| >>> answer = answer.split("End of answer.")[0] | |
| >>> print(answer) | |
| ``` | |
| { | |
| "answer": "Transformers에서 정지 시퀀스를 정의하려면 파이프라인 또는 모델에 stop_sequence 인수를 전달해야 합니다.", | |
| "confidence_score": 0.95, | |
| "source_snippets": ["정지 시퀀스를 정의하려면 파이프라인 또는 모델에 stop_sequence 인수를 전달해야 합니다."] | |
| } | |
| LLM의 출력은 딕셔너리의 문자열 표현입니다. 따라서 `literal_eval`을 사용하여 이를 딕셔너리로 로드합시다. | |
| ```python | |
| from ast import literal_eval | |
| parsed_answer = literal_eval(answer) | |
| ``` | |
| ```python | |
| >>> def highlight(s): | |
| ... return "\x1b[1;32m" + s + "\x1b[0m" | |
| >>> def print_results(answer, source_text, highlight_snippets): | |
| ... print("Answer:", highlight(answer)) | |
| ... print("\n\n", "=" * 10 + " Source documents " + "=" * 10) | |
| ... for snippet in highlight_snippets: | |
| ... source_text = source_text.replace(snippet.strip(), highlight(snippet.strip())) | |
| ... print(source_text) | |
| >>> print_results( | |
| ... parsed_answer["answer"], RELEVANT_CONTEXT, parsed_answer["source_snippets"] | |
| ... ) | |
| ``` | |
| Answer: [1;32mTransformers에서 정지 시퀀스를 정의하려면 파이프라인 또는 모델에 stop_sequence 인수를 전달해야 합니다.[0m | |
| ========== Source documents ========== | |
| 문서: | |
| 오늘 서울의 날씨가 정말 좋네요. | |
| Transformers에서 [1;32m정지 시퀀스를 정의하려면 파이프라인 또는 모델에 stop_sequence 인수를 전달해야 합니다.[0m | |
| 잘 작동합니다! 🥳 | |
| 하지만 성능이 낮은 모델을 사용하는 경우는 어떨까요? | |
| 성능이 떨어지는 모델의 불안정한 출력을 시뮬레이션하기 위해, temperature 값을 높여보겠습니다. | |
| ```python | |
| >>> answer = llm_client.text_generation( | |
| ... prompt, | |
| ... max_new_tokens=250, | |
| ... temperature=1.6, | |
| ... return_full_text=False, | |
| ... ) | |
| >>> print(answer) | |
| ``` | |
| { | |
| "answer": adjectistiques Banco Comambique-howiktenल्ल 없을Ela Nal realisticINTEn обор reminding frustPolit lMer Maria Banco Comambique-howiktenल्ल 없을Ela Nal realisticINTEn обор музы inférieurke Zendaya alguna7 Mons ram incColumn Orth manages Richie HackAUcasismo | |
| 출력이 올바른 JSON 형식조차 아닌 것을 확인할 수 있습니다. | |
| ## 👉 제한된 디코딩(Constrained decoding) | |
| JSON 출력을 강제하기 위해, 우리는 **제한된 디코딩**을 사용해야 합니다. 여기서 LLM이 **문법**이라고 불리는 일련의 규칙에 맞는 토큰만 출력하도록 강제합니다. | |
| 이 문법은 Pydantic 모델, JSON 스키마 또는 정규 표현식을 사용하여 정의할 수 있습니다. 그러면 AI는 지정된 문법에 맞는 응답을 생성합니다. | |
| 예를 들어, 여기서는 [Pydantic 타입](https://docs.pydantic.dev/latest/api/types/)을 따릅니다. | |
| ```python | |
| from pydantic import BaseModel, confloat, StringConstraints | |
| from typing import List, Annotated | |
| class AnswerWithSnippets(BaseModel): | |
| answer: Annotated[str, StringConstraints(min_length=10, max_length=100)] | |
| confidence: Annotated[float, confloat(ge=0.0, le=1.0)] | |
| source_snippets: List[Annotated[str, StringConstraints(max_length=30)]] | |
| ``` | |
| 생성된 스키마가 요구 사항을 올바르게 나타내는지 확인해 보세요. | |
| ```python | |
| AnswerWithSnippets.schema() | |
| ``` | |
| 클라이언트의 `text_generation` 메서드를 사용하거나 `post` 메서드를 사용할 수 있습니다. | |
| ```python | |
| >>> # Using text_generation | |
| >>> answer = llm_client.text_generation( | |
| ... prompt, | |
| ... grammar={"type": "json", "value": AnswerWithSnippets.schema()}, | |
| ... max_new_tokens=250, | |
| ... temperature=1.6, | |
| ... return_full_text=False, | |
| ... ) | |
| >>> print(answer) | |
| >>> # Using post | |
| >>> data = { | |
| ... "inputs": prompt, | |
| ... "parameters": { | |
| ... "temperature": 1.6, | |
| ... "return_full_text": False, | |
| ... "grammar": {"type": "json", "value": AnswerWithSnippets.schema()}, | |
| ... "max_new_tokens": 250, | |
| ... }, | |
| ... } | |
| >>> answer = json.loads(llm_client.post(json=data))[0]["generated_text"] | |
| >>> print(answer) | |
| ``` | |
| { | |
| "answer": " neces恨bay внеpok Archives-Common Propsogs’organpern 공격forschfläche elicous neces恨bay внеpok món-�","confidence": 1,"source_snippets": ["Washington Roman Humналеualion", "_styleImplementedAugust lire", | |
| ""] | |
| } | |
| { | |
| "answer": " بخopuerto կար因數 kavuts mi Firefox Penguins er sdபெர erinnert publiée 물리 DK\({}^{\ Cis بخopuerto կար因數" | |
| , | |
| "confidence": 0.7825484027713585 | |
| , | |
| "source_snippets": [ | |
| "Transformerграни moisady отгaನ", ", migrations ceproductionautal", | |
| "Listeners accelerating loocae" | |
| ] | |
| } | |
| ✅ 높은 temperature 설정으로 인해 답변 내용은 여전히 말이 되지 않지만, 생성된 출력 텍스트는 이제 우리가 문법에서 정의한 정확한 키와 자료형을 가진 올바른 JSON 형식입니다! | |
| 이제 이 출력물을 추가 처리를 위해 파싱할 수 있습니다. | |
| ### Outlines를 사용해서 로컬 환경에서 문법 활용하기 | |
| [Outlines](https://github.com/outlines-dev/outlines/)는 Hugging Face의 Inference API에서 출력 생성을 제한하기 위해 내부적으로 실행되는 라이브러리입니다. 이를 로컬 환경에서도 사용할 수 있습니다. | |
| 이 라이브러리는 [로짓(logits)에 편향(bias)을 적용하는 방식](https://github.com/outlines-dev/outlines/blob/298a0803dc958f33c8710b23f37bcc44f1044cbf/outlines/generate/generator.py#L143)으로 작동하여, 사용자가 정의한 제약 조건에 부합하는 선택지만 강제로 선택되도록 합니다. | |
| ```python | |
| schema_as_str | |
| ``` | |
| ```python | |
| import outlines | |
| repo_id = "Qwen/Qwen2-7B-Instruct" | |
| # 로컬에서 모델 로드하기 | |
| model = outlines.models.transformers(repo_id) | |
| schema_as_str = json.dumps(AnswerWithSnippets.schema()) | |
| generator = outlines.generate.json(model, schema_as_str) | |
| # Use the `generator` to sample an output from the model | |
| result = generator(prompt) | |
| print(result) | |
| ``` | |
| 제약 생성(constrained generation)을 사용하여 [Text-Generation-Inference](https://huggingface.co/docs/text-generation-inference/en/index)를 활용할 수도 있습니다 (자세한 내용과 예시는 [문서](https://huggingface.co/docs/text-generation-inference/en/conceptual/guidance)를 참조하세요). | |
| 지금까지 우리는 특정 RAG 사용 사례를 보여주었지만, 제약 생성은 그 이상으로 많은 도움이 됩니다. | |
| 예를 들어, [LLM judge](llm_judge) 워크플로우에서도 제약 생성을 사용하여 다음과 같은 JSON을 출력할 수 있습니다: | |
| ```py | |
| { | |
| "score": 1, | |
| "rationale": "The answer does not match the true answer at all.", | |
| "confidence_level": 0.85 | |
| } | |
| ``` | |
| 오늘은 여기까지입니다. 끝까지 따라와 주셔서 감사드립니다! 👏 | |
Xet Storage Details
- Size:
- 12.4 kB
- Xet hash:
- aa8d4be0e6e2a8ab7bebc7fd6054799e35ec04556ed8848a1c9cd24dda3fead1
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.