|
| 1 | +# Chain of thought |
| 2 | + |
| 3 | + |
| 4 | +Chain of thought is a prompting technique introduced in the paper [``Chain-of-Thought Prompting Elicits Reasoning in Large Language Models''](https://arxiv.org/abs/2201.11903) where throught prompting the authors generate a series of intermediate reasoning steps which improves the ability of LLMs to perform complex reasoning. |
| 5 | + |
| 6 | +In this guide, we use [outlines](https://outlines-dev.github.io/outlines/) to apply chain of thought through structured output. |
| 7 | + |
| 8 | +We use [llama.cpp](https://github.com/ggerganov/llama.cpp) using the [llama-cpp-python](https://github.com/abetlen/llama-cpp-python) library. Outlines supports llama-cpp-python, but we need to install it ourselves: |
| 9 | + |
| 10 | +```shell |
| 11 | +pip install llama-cpp-python |
| 12 | +``` |
| 13 | + |
| 14 | +We pull a quantized GGUF model, in this guide we pull [Hermes-2-Pro-Llama-3-8B](https://huggingface.co/NousResearch/Hermes-2-Theta-Llama-3-8B-GGUF) by [NousResearch](https://nousresearch.com/) from [HuggingFace](https://huggingface.co/): |
| 15 | + |
| 16 | +```shell |
| 17 | +wget https://hf.co/NousResearch/Hermes-2-Pro-Llama-3-8B-GGUF/resolve/main/Hermes-2-Pro-Llama-3-8B-Q4_K_M.gguf |
| 18 | +``` |
| 19 | + |
| 20 | +We initialize the model: |
| 21 | + |
| 22 | +```python |
| 23 | +from llama_cpp import Llama |
| 24 | +from outlines import generate, models |
| 25 | + |
| 26 | +llm = Llama( |
| 27 | + "/path/to/model/Hermes-2-Pro-Llama-3-8B-Q4_K_M.gguf", |
| 28 | + tokenizer=llama_cpp.llama_tokenizer.LlamaHFTokenizer.from_pretrained( |
| 29 | + "NousResearch/Hermes-2-Pro-Llama-3-8B" |
| 30 | + ), |
| 31 | + n_gpu_layers=-1, |
| 32 | + flash_attn=True, |
| 33 | + n_ctx=8192, |
| 34 | + verbose=False |
| 35 | +) |
| 36 | +model = models.LlamaCpp(llm) |
| 37 | +``` |
| 38 | + |
| 39 | +## Chain of thought |
| 40 | + |
| 41 | +We first define our Pydantic class for a reasoning step: |
| 42 | + |
| 43 | +```python |
| 44 | +from pydantic import BaseModel, Field |
| 45 | + |
| 46 | +class Reasoning_Step(BaseModel): |
| 47 | + reasoning_step: str = Field(..., description="Reasoning step") |
| 48 | +``` |
| 49 | + |
| 50 | +We then define the Pydantic class for reasoning which will consist on a list of reasoning steps and a conclusion, and we get its JSON schema: |
| 51 | + |
| 52 | +```python |
| 53 | +from typing import List |
| 54 | + |
| 55 | +from typing import List |
| 56 | + |
| 57 | +class Reasoning(BaseModel): |
| 58 | + reasoning: List[Reasoning_Step] = Field(..., description="List of reasoning steps") |
| 59 | + conclusion: str = Field(..., description="Conclusion") |
| 60 | + |
| 61 | +json_schema = Reasoning.model_json_schema() |
| 62 | +``` |
| 63 | + |
| 64 | +We could generate using the json schema but for a change we will use the regex: |
| 65 | + |
| 66 | +```python |
| 67 | +from outlines.integrations.utils import convert_json_schema_to_str |
| 68 | +from outlines.fsm.json_schema import build_regex_from_schema |
| 69 | + |
| 70 | +json_schema = Reasoning.model_json_schema() |
| 71 | +schema_str = convert_json_schema_to_str(json_schema=json_schema) |
| 72 | +regex_str = build_regex_from_schema(schema_str) |
| 73 | +``` |
| 74 | + |
| 75 | +We then need to adapt our prompt to the [Hermes prompt format for JSON schema](https://github.com/NousResearch/Hermes-Function-Calling?tab=readme-ov-file#prompt-format-for-json-mode--structured-outputs): |
| 76 | + |
| 77 | +```python |
| 78 | +def generate_hermes_prompt(user_prompt): |
| 79 | + return ( |
| 80 | + "<|im_start|>system\n" |
| 81 | + "You are a world class AI model who answers questions in JSON " |
| 82 | + f"Here's the json schema you must adhere to:\n<schema>\n{schema}\n</schema><|im_end|>\n" |
| 83 | + "<|im_start|>user\n" |
| 84 | + + user_prompt |
| 85 | + + "<|im_end|>" |
| 86 | + + "\n<|im_start|>assistant\n" |
| 87 | + "<schema>" |
| 88 | + ) |
| 89 | +``` |
| 90 | + |
| 91 | +For a given user prompt, for example: |
| 92 | + |
| 93 | +```python |
| 94 | +user_prompt = "9.11 and 9.9 -- which is bigger?" |
| 95 | +``` |
| 96 | + |
| 97 | +We can use `generate.regex` by passing the Pydantic class we previously defined, and call the generator with the Hermes prompt: |
| 98 | + |
| 99 | +```python |
| 100 | +generator = generate.regex(model, regex_str) |
| 101 | +prompt = generate_hermes_prompt(user_prompt) |
| 102 | +response = generator(prompt, max_tokens=1024, temperature=0, seed=42) |
| 103 | +``` |
| 104 | + |
| 105 | +We obtain the reasoning steps as well as the conclusion |
| 106 | + |
| 107 | +```python |
| 108 | +import json |
| 109 | + |
| 110 | +json_response = json.loads(response) |
| 111 | + |
| 112 | +print(json_response["reasoning"]) |
| 113 | +print(json_response["conclusion"]) |
| 114 | +# [{'reasoning_step': 'Both 9.11 and 9.9 are decimal numbers.'}, |
| 115 | +# {'reasoning_step': 'When comparing decimal numbers, we look at the numbers after the decimal point.'}, |
| 116 | +# {'reasoning_step': 'In this case, 9.11 has the number 1 after the decimal point, while 9.9 has the number 9.'}, |
| 117 | +# {'reasoning_step': 'Since 1 is greater than 9, 9.11 is greater than 9.9.'}] |
| 118 | +# '9.11 is bigger.' |
| 119 | +``` |
| 120 | + |
| 121 | +We notice that the 4th reasoning step is wrong ``Since 1 is greater than 9, 9.11 is greater than 9.9.'', so we could probably give the model some examples for this particular task. |
| 122 | + |
| 123 | +This example was originally contributed by [Alonso Silva](https://github.com/alonsosilvaallende). |
0 commit comments