-
-
Notifications
You must be signed in to change notification settings - Fork 602
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fixed up new prompting techniques in decomp + thought generation category
- Loading branch information
Showing
21 changed files
with
1,684 additions
and
159 deletions.
There are no files selected for viewing
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,141 @@ | ||
--- | ||
title: "" | ||
description: "" | ||
keywords: "" | ||
description: "DECOMP involves using a LLM to break down a complicated task into sub tasks that it has been provided with" | ||
--- | ||
|
||
[wip] | ||
Decomposed Prompting<sup><a href="https://arxiv.org/pdf/2210.02406">1</a></sup> leverages a Language Model (LLM) to deconstruct a complex task into a series of manageable sub-tasks. Each sub-task is then processed by specific functions, enabling the LLM to handle intricate problems more effectively and systematically. | ||
|
||
In the code snippet below, we define a series of data models and functions to implement this approach. | ||
|
||
The `derive_action_plan` function generates an action plan using the LLM, which is then executed step-by-step. Each action can be | ||
|
||
1. InitialInput: Which represents the chunk of the original prompt we need to process | ||
2. Split : An operation to split strings using a given separator | ||
3. StrPos: An operation to help extract a string given an index | ||
4. Merge: An operation to join a list of strings together using a given character | ||
|
||
We can implement this using `instructor` as seen below. | ||
|
||
```python hl_lines="57-58" | ||
import instructor | ||
from openai import OpenAI | ||
from pydantic import BaseModel, Field | ||
from typing import Union | ||
|
||
client = instructor.from_openai(OpenAI()) | ||
|
||
|
||
class Split(BaseModel): | ||
split_char: str = Field( | ||
description="""This is the character to split | ||
the string with""" | ||
) | ||
|
||
def split_chars(self, s: str, c: str): | ||
return s.split(c) | ||
|
||
|
||
class StrPos(BaseModel): | ||
index: int = Field( | ||
description="""This is the index of the character | ||
we wish to return""" | ||
) | ||
|
||
def get_char(self, s: list[str], i: int): | ||
return [c[i] for c in s] | ||
|
||
|
||
class Merge(BaseModel): | ||
merge_char: str = Field( | ||
description="""This is the character to merge the | ||
inputs we plan to pass to this function with""" | ||
) | ||
|
||
def merge_string(self, s: list[str]): | ||
return self.merge_char.join(s) | ||
|
||
|
||
class Action(BaseModel): | ||
id: int = Field( | ||
description="""Unique Incremental id to identify | ||
this action with""" | ||
) | ||
action: Union[Split, StrPos, Merge] | ||
|
||
|
||
class ActionPlan(BaseModel): | ||
initial_data: str | ||
plan: list[Action] | ||
|
||
|
||
def derive_action_plan(task_description: str) -> ActionPlan: | ||
return client.chat.completions.create( | ||
messages=[ | ||
{ | ||
"role": "system", | ||
"content": """Generate an action plan to help you complete | ||
the task outlined by the user""", | ||
}, | ||
{"role": "user", "content": task_description}, | ||
], | ||
response_model=ActionPlan, | ||
max_retries=3, | ||
model="gpt-4o", | ||
) | ||
|
||
|
||
if __name__ == "__main__": | ||
task = """Concatenate the second letter of every word in Jack | ||
Ryan together""" | ||
plan = derive_action_plan(task) | ||
print(plan.model_dump_json(indent=2)) | ||
""" | ||
{ | ||
"initial_data": "Jack Ryan", | ||
"plan": [ | ||
{ | ||
"id": 1, | ||
"action": { | ||
"split_char": " " | ||
} | ||
}, | ||
{ | ||
"id": 2, | ||
"action": { | ||
"index": 1 | ||
} | ||
}, | ||
{ | ||
"id": 3, | ||
"action": { | ||
"merge_char": "" | ||
} | ||
} | ||
] | ||
} | ||
""" | ||
|
||
curr = plan.initial_data | ||
cache = {} | ||
|
||
for action in plan.plan: | ||
if isinstance(action.action, Split) and isinstance(curr, str): | ||
curr = action.action.split_chars(curr, action.action.split_char) | ||
elif isinstance(action.action, StrPos) and isinstance(curr, list): | ||
curr = action.action.get_char(curr, action.action.index) | ||
elif isinstance(action.action, Merge) and isinstance(curr, list): | ||
curr = action.action.merge_string(curr) | ||
else: | ||
raise ValueError("Unsupported Operation") | ||
|
||
print(action, curr) | ||
#> id=1 action=Split(split_char=' ') ['Jack', 'Ryan'] | ||
#> id=2 action=StrPos(index=1) ['a', 'y'] | ||
#> id=3 action=Merge(merge_char='') ay | ||
|
||
print(curr) | ||
#> ay | ||
``` | ||
|
||
### References | ||
|
||
<sup id="ref-1">1</sup>: [Decomposed Prompting: A Modular Approach for Solving Complex Tasks](https://arxiv.org/pdf/2210.02406) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,103 @@ | ||
--- | ||
title: "" | ||
description: "" | ||
keywords: "" | ||
description: "Faithful Chain of Thought aims to use multiple reasoning steps to improve the quality of the final outputs" | ||
--- | ||
|
||
[wip] | ||
Faithful Chain of Thought<sup><a href="https://arxiv.org/pdf/2301.13379">1</a></sup> improves the faithfulness of reasoning chains generated by Language Models by breaking it up into two stages | ||
|
||
1. **Translation** : We first translate a user query into a series of reasoning steps. These are a task specific set of steps that we can execute deterministically. | ||
2. **Problem Solving**: We execute our steps and arrive at a final answer that we can derive. This ensures that our Chain Of Thought is able to derive a answer that is consistent with the reasoning steps. | ||
|
||
They list a few examples in the paper of what these task-specific steps could be | ||
|
||
1. **Math Word Problems** : Python Code that can be executed by an interpreter to derive a final answer | ||
2. **Multi-Hop QA** : This is a multi-step reasoning process. To solve this, they use a mix of python and Datalog ( which is a relation and log programming language ) to arrive at a final answer | ||
3. **Planning** : When trying to generate a plan to solve a user query, they generate a list of symbolic goals in a Programming Language and then call a PDDL Planner to obtain a plan to solve the user's query | ||
|
||
![](../../img/faithful_cot_example.png) | ||
|
||
In the example below, we show how you can use a LLM to generate python code that can be executed by an Interpreter to arrive at a final answer. | ||
|
||
We can implement it in `instructor` as seen below | ||
|
||
```python hl_lines="30-45" | ||
import instructor | ||
from openai import OpenAI | ||
from pydantic import BaseModel, Field | ||
|
||
client = instructor.from_openai(OpenAI()) | ||
|
||
|
||
class ReasoningStep(BaseModel): | ||
id: int = Field(description="Unique ID") | ||
rationale: list[str] = Field( | ||
description="""Specific sections from prior reasoning | ||
steps or the context that ground this reasoning step""" | ||
) | ||
dependencies: list[int] = Field( | ||
description="""IDs of prior reasoning steps that this | ||
reasoning step depends on""" | ||
) | ||
eval_string: str = Field( | ||
description="""Python Code to execute to generate the | ||
final evaluation""" | ||
) | ||
|
||
|
||
def generate_reasoning_steps(query: str) -> list[ReasoningStep]: | ||
return client.chat.completions.create( | ||
messages=[ | ||
{ | ||
"role": "system", | ||
"content": """ | ||
You are a world class AI who excels at | ||
generating reasoning steps to answer a | ||
question. You will be given a question | ||
and you will generate a list of reasoning | ||
steps that are needed to answer the | ||
question. | ||
At each point you should either | ||
- declare a variable to be referenced | ||
later on | ||
- combine multiple variables together to | ||
generate a new result that you should | ||
store in another variable | ||
The final answer should be stored in a | ||
variable called `answer`. | ||
""", | ||
}, | ||
{"role": "user", "content": query}, | ||
], | ||
model="gpt-4o", | ||
response_model=list[ReasoningStep], | ||
) | ||
|
||
|
||
if __name__ == "__main__": | ||
steps = generate_reasoning_steps( | ||
"""If there are 3 cars in the parking lot and 2 more | ||
cars arrive, how many cars are in the parking lot | ||
after another 2 more arrive?""" | ||
) | ||
|
||
code = "\n".join([step.eval_string for step in steps]) | ||
print(code) | ||
""" | ||
initial_cars = 3 | ||
arriving_cars = 2 | ||
cars_after_first_arrival = initial_cars + arriving_cars | ||
final_car_count = cars_after_first_arrival + 2 | ||
answer = final_car_count | ||
""" | ||
exec(code) | ||
|
||
local_vars = {} | ||
exec(code, {}, local_vars) | ||
print(local_vars.get("answer")) | ||
#> 7 | ||
``` | ||
|
||
### References | ||
|
||
<sup id="ref-1">1</sup>: [Faithful Chain-of-Thought Reasoning](https://arxiv.org/pdf/2301.13379) |
Oops, something went wrong.