add thinking
This commit is contained in:
parent
e7323f7215
commit
ff0bca2d67
|
|
@ -1,7 +1,347 @@
|
||||||
# Chain-of-Thought
|
# Extended Thinking
|
||||||
|
|
||||||
The simplest implementation is to tell the AI to "think step by step" or to provide examples of step-by-step reasoning in the prompt. This guides the AI to break down its thinking.
|
This project demonstrates an extended thinking mode implementation that enables LLMs to solve complex reasoning problems by thinking step-by-step. It's designed to improve problem-solving accuracy through deliberate reasoning.
|
||||||
|
|
||||||
Further more thinking models like sonnet 3.7, O1 natively support.
|
## Features
|
||||||
|
|
||||||
However, we can also simulate without thinking model.
|
- Improves model reasoning on complex problems
|
||||||
|
- Works with models like Claude 3.7 Sonnet that support extended thinking
|
||||||
|
- Solves problems that direct prompting often fails on
|
||||||
|
- Provides detailed reasoning traces for verification
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
1. Install the required packages:
|
||||||
|
```bash
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Set up your API key:
|
||||||
|
```bash
|
||||||
|
export ANTHROPIC_API_KEY="your-api-key-here"
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Run a test problem to see thinking mode in action:
|
||||||
|
```bash
|
||||||
|
python main.py
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Try your own reasoning problem:
|
||||||
|
```bash
|
||||||
|
python main.py --"Your complex reasoning problem here"
|
||||||
|
```
|
||||||
|
|
||||||
|
## How It Works
|
||||||
|
|
||||||
|
The implementation uses a self-looping Chain of Thought node that allows an LLM to think through complex problems step by step:
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart LR
|
||||||
|
cot[ChainOfThoughtNode] -->|"continue"| cot
|
||||||
|
```
|
||||||
|
|
||||||
|
Each time the node loops, it:
|
||||||
|
1. Reads the problem and previous thoughts
|
||||||
|
2. Generates the next thought or final solution
|
||||||
|
3. Decides whether more thinking is needed
|
||||||
|
|
||||||
|
This approach helps LLMs solve problems that would be difficult with a single-pass approach.
|
||||||
|
|
||||||
|
## Comparison with Different Approaches
|
||||||
|
|
||||||
|
- **Standard prompting**: Telling the AI to "think step by step" or providing examples helps, but the thinking is usually not significant enough
|
||||||
|
- **Extended thinking models**: Models like Claude 3.7 Sonnet, GPT-4o, and Deepseek R1 natively support extended thinking with much better results
|
||||||
|
- **This implementation**: Explores how to achieve extended thinking with non-thinking models
|
||||||
|
|
||||||
|
## Example Thinking Process
|
||||||
|
|
||||||
|
> **Problem**: Break a stick, then break the longer piece again. What's the probability of forming a triangle?
|
||||||
|
|
||||||
|
This problem demonstrates why extended thinking is valuable:
|
||||||
|
|
||||||
|
- **Standard models without thinking**: Get the wrong answer
|
||||||
|
- **Models with extended thinking**: Find the correct answer (0.386)
|
||||||
|
|
||||||
|
For comparison:
|
||||||
|
- [Claude 3.7 Sonnet (without thinking)](https://claude.ai/share/31bf938c-94dd-42f6-bfac-e82ba3616dbc): Wrong answer
|
||||||
|
- [GPT-4o with thinking](https://chatgpt.com/share/67dcb1bf-ceb0-8000-823a-8ce894032e37): Correct answer after 1.5 min
|
||||||
|
- [Claude 3.7 Sonnet with thinking](https://claude.ai/share/0863f9fd-ae75-4a0c-84ee-f7443d2fcf4a): Correct answer after 4.5 min
|
||||||
|
|
||||||
|
Below is an example of how Claude 3.7 Sonnet uses thinking mode to solve this complex problem, and get the correct result:
|
||||||
|
|
||||||
|
```
|
||||||
|
🤔 Processing question: Break a stick, then break the longer piece again. What's the probability of forming a triangle?
|
||||||
|
|
||||||
|
Let me understand the problem. We have a stick of some length, which we can call 1 for simplicity. We break it at a random point, creating two pieces. Then we break the longer piece again at a random point. This gives us three pieces. We want to find the probability that these three pieces can form a triangle.
|
||||||
|
|
||||||
|
For three segments to form a triangle, the sum of the lengths of any two sides must be greater than the length of the third side. So we need to check:
|
||||||
|
a + b > c
|
||||||
|
a + c > b
|
||||||
|
b + c > a
|
||||||
|
|
||||||
|
Where a, b, and c are the lengths of the three pieces.
|
||||||
|
|
||||||
|
Next thought needed: True
|
||||||
|
Total thoughts estimate: 8
|
||||||
|
--------------------------------------------------
|
||||||
|
|
||||||
|
Thought 2: Let me set up the problem mathematically. Let's say the original stick has length 1.
|
||||||
|
|
||||||
|
First break: We break the stick at a point X, where X is chosen uniformly from [0,1]. This gives us two pieces with lengths X and 1-X.
|
||||||
|
|
||||||
|
Second break: We break the longer piece at a random point. If X ≤ 0.5, then 1-X is the longer piece. If X > 0.5, then X is the longer piece.
|
||||||
|
|
||||||
|
Case 1: If X ≤ 0.5, the longer piece is 1-X. We break it at point Y(1-X), where Y is chosen uniformly from [0,1]. This gives pieces of lengths:
|
||||||
|
- X (from first break)
|
||||||
|
- Y(1-X) (from second break)
|
||||||
|
- (1-Y)(1-X) (remaining from second break)
|
||||||
|
|
||||||
|
Case 2: If X > 0.5, the longer piece is X. We break it at point YX, where Y is chosen uniformly from [0,1]. This gives pieces of lengths:
|
||||||
|
- 1-X (from first break)
|
||||||
|
- YX (from second break)
|
||||||
|
- (1-Y)X (remaining from second break)
|
||||||
|
|
||||||
|
For each case, I need to determine when these three pieces can form a triangle, then calculate the probability.
|
||||||
|
|
||||||
|
Next thought needed: True
|
||||||
|
Total thoughts estimate: 8
|
||||||
|
--------------------------------------------------
|
||||||
|
|
||||||
|
Thought 3: Let's analyze when the three pieces can form a triangle for both cases.
|
||||||
|
|
||||||
|
For a triangle to form, each side must be less than the sum of the other two sides. So we need:
|
||||||
|
|
||||||
|
Case 1 (X ≤ 0.5): We have pieces of lengths X, Y(1-X), and (1-Y)(1-X)
|
||||||
|
|
||||||
|
The triangle inequality conditions are:
|
||||||
|
- X + Y(1-X) > (1-Y)(1-X)
|
||||||
|
- X + (1-Y)(1-X) > Y(1-X)
|
||||||
|
- Y(1-X) + (1-Y)(1-X) > X
|
||||||
|
|
||||||
|
Simplifying:
|
||||||
|
- X + Y(1-X) > (1-Y)(1-X)
|
||||||
|
- X + (1-Y)(1-X) > Y(1-X)
|
||||||
|
- (1-X) > X (since Y(1-X) + (1-Y)(1-X) = 1-X)
|
||||||
|
|
||||||
|
The third condition is always true when X ≤ 0.5 (which is our assumption for Case 1).
|
||||||
|
|
||||||
|
Let's work on the first condition:
|
||||||
|
X + Y(1-X) > (1-Y)(1-X)
|
||||||
|
X + Y(1-X) > (1-X) - Y(1-X)
|
||||||
|
X + 2Y(1-X) > (1-X)
|
||||||
|
X + 2Y(1-X) > 1-X
|
||||||
|
2X + 2Y(1-X) > 1
|
||||||
|
2X + 2Y - 2YX > 1
|
||||||
|
|
||||||
|
Similarly for the second condition:
|
||||||
|
X + (1-Y)(1-X) > Y(1-X)
|
||||||
|
X + (1-X) - Y(1-X) > Y(1-X)
|
||||||
|
X + (1-X) > 2Y(1-X)
|
||||||
|
1 > 2Y(1-X)
|
||||||
|
1/(2(1-X)) > Y
|
||||||
|
|
||||||
|
So for Case 1, we need:
|
||||||
|
- 2X + 2Y - 2YX > 1
|
||||||
|
- Y < 1/(2(1-X))
|
||||||
|
|
||||||
|
Next thought needed: True
|
||||||
|
Total thoughts estimate: 8
|
||||||
|
--------------------------------------------------
|
||||||
|
|
||||||
|
Thought 4: I need to continue analyzing Case 1 and then move on to Case 2.
|
||||||
|
|
||||||
|
For Case 1 (X ≤ 0.5), we have the constraints:
|
||||||
|
- 2X + 2Y - 2YX > 1
|
||||||
|
- Y < 1/(2(1-X))
|
||||||
|
|
||||||
|
Let's rewrite the first constraint:
|
||||||
|
2X + 2Y - 2YX > 1
|
||||||
|
2Y(1-X) > 1 - 2X
|
||||||
|
Y > (1 - 2X)/(2(1-X))
|
||||||
|
|
||||||
|
So for Case 1, we need:
|
||||||
|
- Y > (1 - 2X)/(2(1-X))
|
||||||
|
- Y < 1/(2(1-X))
|
||||||
|
|
||||||
|
This means Y must be in the range ((1 - 2X)/(2(1-X)), 1/(2(1-X))). For this range to be valid, we need:
|
||||||
|
(1 - 2X)/(2(1-X)) < 1/(2(1-X))
|
||||||
|
1 - 2X < 1
|
||||||
|
-2X < 0
|
||||||
|
X > 0
|
||||||
|
|
||||||
|
Which is true for our domain. So we have a valid range for Y when X ≤ 0.5.
|
||||||
|
|
||||||
|
Now let's analyze Case 2 (X > 0.5): We have pieces of lengths (1-X), YX, and (1-Y)X
|
||||||
|
|
||||||
|
The triangle inequality conditions are:
|
||||||
|
- (1-X) + YX > (1-Y)X
|
||||||
|
- (1-X) + (1-Y)X > YX
|
||||||
|
- YX + (1-Y)X > (1-X)
|
||||||
|
|
||||||
|
Simplifying:
|
||||||
|
- (1-X) + YX > (1-Y)X
|
||||||
|
- (1-X) + (1-Y)X > YX
|
||||||
|
- X > (1-X) (since YX + (1-Y)X = X)
|
||||||
|
|
||||||
|
The third condition is always true when X > 0.5 (which is our assumption for Case 2).
|
||||||
|
|
||||||
|
Next thought needed: True
|
||||||
|
Total thoughts estimate: 8
|
||||||
|
--------------------------------------------------
|
||||||
|
|
||||||
|
Thought 5: Continuing with Case 2 (X > 0.5), let's simplify the remaining triangle inequality conditions:
|
||||||
|
|
||||||
|
First condition:
|
||||||
|
(1-X) + YX > (1-Y)X
|
||||||
|
(1-X) + YX > X - YX
|
||||||
|
(1-X) + 2YX > X
|
||||||
|
1 - X + 2YX > X
|
||||||
|
1 + 2YX > 2X
|
||||||
|
2YX > 2X - 1
|
||||||
|
Y > (2X - 1)/(2X)
|
||||||
|
|
||||||
|
Second condition:
|
||||||
|
(1-X) + (1-Y)X > YX
|
||||||
|
(1-X) + X - YX > YX
|
||||||
|
(1-X) + X > 2YX
|
||||||
|
1 > 2YX
|
||||||
|
1/(2X) > Y
|
||||||
|
|
||||||
|
So for Case 2, we need:
|
||||||
|
- Y > (2X - 1)/(2X)
|
||||||
|
- Y < 1/(2X)
|
||||||
|
|
||||||
|
This means Y must be in the range ((2X - 1)/(2X), 1/(2X)). For this range to be valid, we need:
|
||||||
|
(2X - 1)/(2X) < 1/(2X)
|
||||||
|
2X - 1 < 1
|
||||||
|
2X < 2
|
||||||
|
X < 1
|
||||||
|
|
||||||
|
Which is true for our domain. So we have a valid range for Y when X > 0.5.
|
||||||
|
|
||||||
|
Now I need to calculate the probability. For both cases, I need to find the area of the region in the X-Y plane where the triangle can be formed, divided by the total area.
|
||||||
|
|
||||||
|
Next thought needed: True
|
||||||
|
Total thoughts estimate: 8
|
||||||
|
--------------------------------------------------
|
||||||
|
|
||||||
|
Thought 6: Now I need to calculate the probability by finding the area in the X-Y plane where the triangle can be formed.
|
||||||
|
|
||||||
|
For Case 1 (X ≤ 0.5):
|
||||||
|
- Y must be between (1 - 2X)/(2(1-X)) and 1/(2(1-X))
|
||||||
|
|
||||||
|
The probability for this case is the area of the region divided by the total area of the X-Y plane for X ≤ 0.5, which is 0.5 × 1 = 0.5.
|
||||||
|
|
||||||
|
Area = ∫₀^(0.5) [1/(2(1-X)) - (1 - 2X)/(2(1-X))] dX
|
||||||
|
= ∫₀^(0.5) [(1 - (1 - 2X))/(2(1-X))] dX
|
||||||
|
= ∫₀^(0.5) [2X/(2(1-X))] dX
|
||||||
|
= ∫₀^(0.5) [X/(1-X)] dX
|
||||||
|
|
||||||
|
This integral equals -X - ln(1-X) evaluated from 0 to 0.5, which is:
|
||||||
|
[-0.5 - ln(0.5)] - [0 - ln(1)] = -0.5 - (-0.693) = -0.5 + 0.693 = 0.193
|
||||||
|
|
||||||
|
So the probability for Case 1 is 0.193/0.5 = 0.386.
|
||||||
|
|
||||||
|
For Case 2 (X > 0.5):
|
||||||
|
- Y must be between (2X - 1)/(2X) and 1/(2X)
|
||||||
|
|
||||||
|
The probability for this case is the area of the region divided by the total area of the X-Y plane for X > 0.5, which is 0.5 × 1 = 0.5.
|
||||||
|
|
||||||
|
Area = ∫₀.₅^1 [1/(2X) - (2X - 1)/(2X)] dX
|
||||||
|
= ∫₀.₅^1 [(1 - (2X - 1))/(2X)] dX
|
||||||
|
= ∫₀.₅^1 [(2 - 2X)/(2X)] dX
|
||||||
|
= ∫₀.₅^1 [(1 - X)/X] dX
|
||||||
|
= ∫₀.₅^1 [1/X - 1] dX
|
||||||
|
= [ln|X| - X]₀.₅^1
|
||||||
|
= [ln(1) - 1] - [ln(0.5) - 0.5] = -1 - (-0.693 - 0.5) = -1 + 0.693 + 0.5 = 0.193
|
||||||
|
|
||||||
|
So the probability for Case 2 is also 0.193/0.5 = 0.386.
|
||||||
|
|
||||||
|
The total probability is the sum of the probabilities for both cases: 0.386 + 0.386 = 0.772.
|
||||||
|
|
||||||
|
Wait, I should double-check my calculations. Let me reconsider the integrals.
|
||||||
|
|
||||||
|
Next thought needed: True
|
||||||
|
Total thoughts estimate: 8
|
||||||
|
--------------------------------------------------
|
||||||
|
|
||||||
|
Thought 7: Let me double-check the calculations for both cases more carefully.
|
||||||
|
|
||||||
|
For Case 1 (X ≤ 0.5), the constraints are:
|
||||||
|
- Y > (1 - 2X)/(2(1-X))
|
||||||
|
- Y < 1/(2(1-X))
|
||||||
|
|
||||||
|
The area for which the triangle can be formed is:
|
||||||
|
∫₀^(0.5) [1/(2(1-X)) - (1 - 2X)/(2(1-X))] dX
|
||||||
|
|
||||||
|
Simplifying the integrand:
|
||||||
|
[1 - (1 - 2X)]/(2(1-X)) = 2X/(2(1-X)) = X/(1-X)
|
||||||
|
|
||||||
|
So we have:
|
||||||
|
∫₀^(0.5) X/(1-X) dX
|
||||||
|
|
||||||
|
This can be rewritten as:
|
||||||
|
∫₀^(0.5) [1/(1-X) - 1] dX = ∫₀^(0.5) 1/(1-X) dX - ∫₀^(0.5) 1 dX
|
||||||
|
= -ln|1-X|₀^(0.5) - X₀^(0.5)
|
||||||
|
= -ln(0.5) - 0.5 - (-ln(1) - 0)
|
||||||
|
= -ln(0.5) - 0.5
|
||||||
|
= 0.693 - 0.5
|
||||||
|
= 0.193
|
||||||
|
|
||||||
|
So the probability for Case 1 is 0.193/0.5 = 0.386.
|
||||||
|
|
||||||
|
For Case 2 (X > 0.5), the constraints are:
|
||||||
|
- Y > (2X - 1)/(2X)
|
||||||
|
- Y < 1/(2X)
|
||||||
|
|
||||||
|
The area for which the triangle can be formed is:
|
||||||
|
∫₀.₅^1 [1/(2X) - (2X - 1)/(2X)] dX
|
||||||
|
|
||||||
|
Simplifying the integrand:
|
||||||
|
[1 - (2X - 1)]/(2X) = (2 - 2X)/(2X) = (1 - X)/X
|
||||||
|
|
||||||
|
So we have:
|
||||||
|
∫₀.₅^1 (1 - X)/X dX = ∫₀.₅^1 (1/X - 1) dX
|
||||||
|
= ln|X|₀.₅^1 - X₀.₅^1
|
||||||
|
= ln(1) - ln(0.5) - (1 - 0.5)
|
||||||
|
= 0 - (-0.693) - 0.5
|
||||||
|
= 0.693 - 0.5
|
||||||
|
= 0.193
|
||||||
|
|
||||||
|
So the probability for Case 2 is also 0.193/0.5 = 0.386.
|
||||||
|
|
||||||
|
The total probability is the sum of both: 0.386 + 0.386 = 0.772.
|
||||||
|
|
||||||
|
This confirms our previous calculation. However, I need to make sure this is correct by thinking about it differently. The total area in the X-Y plane is 1 (as both X and Y range from 0 to 1). The area where triangles can form is 0.193 + 0.193 = 0.386. So the probability should be 0.386/1 = 0.386, not 0.772.
|
||||||
|
|
||||||
|
Wait, that's not right. I need to be more careful about the sample space...
|
||||||
|
|
||||||
|
Next thought needed: True
|
||||||
|
Total thoughts estimate: 8
|
||||||
|
--------------------------------------------------
|
||||||
|
|
||||||
|
=== FINAL SOLUTION ===
|
||||||
|
Thought 8: I need to clarify my understanding of the problem and the probability calculation.
|
||||||
|
|
||||||
|
The problem involves two random breaks: first breaking a stick at a random point X, and then breaking the longer piece at a random point Y (proportional to the length of that piece). This gives us a sample space where both X and Y are uniformly distributed between 0 and 1, giving a total area of 1×1 = 1.
|
||||||
|
|
||||||
|
I've calculated the areas where triangles can form in two cases:
|
||||||
|
- Case 1 (X ≤ 0.5): Area = 0.193
|
||||||
|
- Case 2 (X > 0.5): Area = 0.193
|
||||||
|
|
||||||
|
The total area where triangles can form is 0.193 + 0.193 = 0.386.
|
||||||
|
|
||||||
|
Since the total sample space has area 1, the probability is 0.386/1 = 0.386.
|
||||||
|
|
||||||
|
Wait - I see my mistake in Thought 6 and 7. I incorrectly divided by 0.5 (the range of X in each case), but I should divide by the total area of the sample space, which is 1.
|
||||||
|
|
||||||
|
So the final probability is 0.386, or approximately 25/65 ≈ 0.385.
|
||||||
|
|
||||||
|
After further reflection, let me represent this as ln(2) - 1/2, which equals approximately 0.693 - 0.5 = 0.193 for each case, giving a total probability of 2(ln(2) - 1/2) = 2ln(2) - 1 ≈ 0.386.
|
||||||
|
|
||||||
|
Therefore, the probability of forming a triangle is 2ln(2) - 1, which is approximately 0.386 or about 39%.
|
||||||
|
|
||||||
|
======================
|
||||||
|
```
|
||||||
|
|
||||||
|
> Note: Even with thinking mode, models don't always get the right answer, but their accuracy significantly improves on complex reasoning tasks.
|
||||||
|
|
@ -0,0 +1,53 @@
|
||||||
|
# Chain of Thought Node
|
||||||
|
|
||||||
|
## 1. Requirements
|
||||||
|
Create a self-looping Chain of Thought node that can:
|
||||||
|
- Generate thoughts to solve a problem step by step
|
||||||
|
- Revise previous thoughts when necessary
|
||||||
|
- Branch to explore alternative approaches
|
||||||
|
- Track thought numbers and adjust total thoughts dynamically
|
||||||
|
- Generate and verify hypotheses
|
||||||
|
- Provide a final solution when reasoning is complete
|
||||||
|
|
||||||
|
## 2. Flow Design
|
||||||
|
This will be a simple flow with a single node that can call itself repeatedly:
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart LR
|
||||||
|
cot[ChainOfThoughtNode] -->|"continue"| cot
|
||||||
|
```
|
||||||
|
|
||||||
|
## 3. Utilities
|
||||||
|
We'll need one primary utility function:
|
||||||
|
- `call_llm`: Call LLM to generate the next thought based on the problem and previous thoughts
|
||||||
|
|
||||||
|
## 4. Node Design
|
||||||
|
### Shared Store Design
|
||||||
|
```python
|
||||||
|
shared = {
|
||||||
|
"problem": "The problem statement goes here",
|
||||||
|
"thoughts": [], # List of thought objects
|
||||||
|
"current_thought_number": 0,
|
||||||
|
"total_thoughts_estimate": 5, # Initial estimate, can change
|
||||||
|
"solution": None # Final solution when complete
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Each thought in the "thoughts" list will be a dictionary with:
|
||||||
|
```python
|
||||||
|
{
|
||||||
|
"content": "The actual thought text",
|
||||||
|
"thought_number": 1,
|
||||||
|
"is_revision": False,
|
||||||
|
"revises_thought": None,
|
||||||
|
"branch_from_thought": None,
|
||||||
|
"branch_id": None,
|
||||||
|
"next_thought_needed": True
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Chain of Thought Node
|
||||||
|
- `type`: Regular (self-looping)
|
||||||
|
- `prep`: Read the problem and all thoughts so far from shared store
|
||||||
|
- `exec`: Call LLM to generate next thought or solution
|
||||||
|
- `post`: Update shared store with the new thought and decide whether to continue or finish
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
from pocketflow import Flow
|
||||||
|
from nodes import ChainOfThoughtNode
|
||||||
|
|
||||||
|
def create_chain_of_thought_flow():
|
||||||
|
# Create a ChainOfThoughtNode
|
||||||
|
cot_node = ChainOfThoughtNode()
|
||||||
|
|
||||||
|
# Connect the node to itself for the "continue" action
|
||||||
|
cot_node - "continue" >> cot_node
|
||||||
|
|
||||||
|
# Create the flow
|
||||||
|
cot_flow = Flow(start=cot_node)
|
||||||
|
return cot_flow
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
import sys
|
||||||
|
from flow import create_chain_of_thought_flow
|
||||||
|
|
||||||
|
def main():
|
||||||
|
# Default question
|
||||||
|
default_question = "Break a stick, then break the longer piece again. What's the probability of forming a triangle?"
|
||||||
|
|
||||||
|
# Get question from command line if provided with --
|
||||||
|
question = default_question
|
||||||
|
for arg in sys.argv[1:]:
|
||||||
|
if arg.startswith("--"):
|
||||||
|
question = arg[2:]
|
||||||
|
break
|
||||||
|
|
||||||
|
print(f"🤔 Processing question: {question}")
|
||||||
|
|
||||||
|
# Create the flow
|
||||||
|
cot_flow = create_chain_of_thought_flow()
|
||||||
|
|
||||||
|
# Set up shared state
|
||||||
|
shared = {
|
||||||
|
"problem": question,
|
||||||
|
"thoughts": [],
|
||||||
|
"current_thought_number": 0,
|
||||||
|
"total_thoughts_estimate": 10,
|
||||||
|
"solution": None
|
||||||
|
}
|
||||||
|
|
||||||
|
# Run the flow
|
||||||
|
cot_flow.run(shared)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
|
|
@ -0,0 +1,116 @@
|
||||||
|
from pocketflow import Node
|
||||||
|
import yaml
|
||||||
|
from utils import call_llm
|
||||||
|
|
||||||
|
class ChainOfThoughtNode(Node):
|
||||||
|
def prep(self, shared):
|
||||||
|
# Gather problem and previous thoughts
|
||||||
|
problem = shared.get("problem", "")
|
||||||
|
thoughts = shared.get("thoughts", [])
|
||||||
|
current_thought_number = shared.get("current_thought_number", 0)
|
||||||
|
# Increment the current thought number in the shared store
|
||||||
|
shared["current_thought_number"] = current_thought_number + 1
|
||||||
|
total_thoughts_estimate = shared.get("total_thoughts_estimate", 5)
|
||||||
|
|
||||||
|
# Format previous thoughts
|
||||||
|
thoughts_text = "\n".join([
|
||||||
|
f"Thought {t['thought_number']}: {t['content']}" +
|
||||||
|
(f" (Revision of Thought {t['revises_thought']})" if t.get('is_revision') and t.get('revises_thought') else "") +
|
||||||
|
(f" (Branch from Thought {t['branch_from_thought']}, Branch ID: {t['branch_id']})"
|
||||||
|
if t.get('branch_from_thought') else "")
|
||||||
|
for t in thoughts
|
||||||
|
])
|
||||||
|
|
||||||
|
return {
|
||||||
|
"problem": problem,
|
||||||
|
"thoughts_text": thoughts_text,
|
||||||
|
"thoughts": thoughts,
|
||||||
|
"current_thought_number": current_thought_number + 1,
|
||||||
|
"total_thoughts_estimate": total_thoughts_estimate
|
||||||
|
}
|
||||||
|
|
||||||
|
def exec(self, prep_res):
|
||||||
|
problem = prep_res["problem"]
|
||||||
|
thoughts_text = prep_res["thoughts_text"]
|
||||||
|
current_thought_number = prep_res["current_thought_number"]
|
||||||
|
total_thoughts_estimate = prep_res["total_thoughts_estimate"]
|
||||||
|
|
||||||
|
# Create the prompt for the LLM
|
||||||
|
prompt = f"""
|
||||||
|
You are solving a hard problem using Chain of Thought reasoning. Think step-by-step.
|
||||||
|
|
||||||
|
Problem: {problem}
|
||||||
|
|
||||||
|
Previous thoughts:
|
||||||
|
{thoughts_text if thoughts_text else "No previous thoughts yet."}
|
||||||
|
|
||||||
|
Please generate the next thought (Thought {current_thought_number}). You can:
|
||||||
|
1. Continue with the next logical step
|
||||||
|
2. Revise a previous thought if needed
|
||||||
|
3. Branch into a new line of thinking
|
||||||
|
4. Generate a hypothesis if you have enough information
|
||||||
|
5. Verify a hypothesis against your reasoning
|
||||||
|
6. Provide a final solution if you've reached a conclusion
|
||||||
|
|
||||||
|
Current thought number: {current_thought_number}
|
||||||
|
Current estimate of total thoughts needed: {total_thoughts_estimate}
|
||||||
|
|
||||||
|
Format your response as a YAML structure with these fields:
|
||||||
|
- content: Your thought content
|
||||||
|
- next_thought_needed: true/false (true if more thinking is needed)
|
||||||
|
- is_revision: true/false (true if revising a previous thought)
|
||||||
|
- revises_thought: null or number (if is_revision is true)
|
||||||
|
- branch_from_thought: null or number (if branching from previous thought)
|
||||||
|
- branch_id: null or string (a short identifier for this branch)
|
||||||
|
- total_thoughts: number (your updated estimate if changed)
|
||||||
|
|
||||||
|
Only set next_thought_needed to false when you have a complete solution and the content explains the solution.
|
||||||
|
Output in YAML format:
|
||||||
|
```yaml
|
||||||
|
content: |
|
||||||
|
# If you have a complete solution, explain the solution here.
|
||||||
|
# If it's a revision, provide the updated thought here.
|
||||||
|
# If it's a branch, provide the new thought here.
|
||||||
|
next_thought_needed: true/false
|
||||||
|
is_revision: true/false
|
||||||
|
revises_thought: null or number
|
||||||
|
branch_from_thought: null or number
|
||||||
|
branch_id: null or string
|
||||||
|
total_thoughts: number
|
||||||
|
```"""
|
||||||
|
|
||||||
|
response = call_llm(prompt)
|
||||||
|
yaml_str = response.split("```yaml")[1].split("```")[0].strip()
|
||||||
|
thought_data = yaml.safe_load(yaml_str)
|
||||||
|
|
||||||
|
# Add thought number
|
||||||
|
thought_data["thought_number"] = current_thought_number
|
||||||
|
return thought_data
|
||||||
|
|
||||||
|
|
||||||
|
def post(self, shared, prep_res, exec_res):
|
||||||
|
# Add the new thought to the list
|
||||||
|
if "thoughts" not in shared:
|
||||||
|
shared["thoughts"] = []
|
||||||
|
|
||||||
|
shared["thoughts"].append(exec_res)
|
||||||
|
|
||||||
|
# Update total_thoughts_estimate if changed
|
||||||
|
if "total_thoughts" in exec_res and exec_res["total_thoughts"] != shared.get("total_thoughts_estimate", 5):
|
||||||
|
shared["total_thoughts_estimate"] = exec_res["total_thoughts"]
|
||||||
|
|
||||||
|
# If we're done, extract the solution from the last thought
|
||||||
|
if exec_res.get("next_thought_needed", True) == False:
|
||||||
|
shared["solution"] = exec_res["content"]
|
||||||
|
print("\n=== FINAL SOLUTION ===")
|
||||||
|
print(exec_res["content"])
|
||||||
|
print("======================\n")
|
||||||
|
return "end"
|
||||||
|
|
||||||
|
# Otherwise, continue the chain
|
||||||
|
print(f"\n{exec_res['content']}")
|
||||||
|
print(f"Next thought needed: {exec_res.get('next_thought_needed', True)}")
|
||||||
|
print(f"Total thoughts estimate: {shared.get('total_thoughts_estimate', 5)}")
|
||||||
|
print("-" * 50)
|
||||||
|
|
||||||
|
return "continue" # Continue the chain
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
pocketflow>=0.0.1
|
||||||
|
anthropic>=0.15.0 # For Claude API access
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
from anthropic import Anthropic
|
||||||
|
import os
|
||||||
|
|
||||||
|
def call_llm(prompt):
|
||||||
|
client = Anthropic(api_key=os.environ.get("ANTHROPIC_API_KEY", "your-api-key"))
|
||||||
|
response = client.messages.create(
|
||||||
|
model="claude-3-7-sonnet-20250219",
|
||||||
|
max_tokens=1000,
|
||||||
|
messages=[
|
||||||
|
{"role": "user", "content": prompt}
|
||||||
|
]
|
||||||
|
)
|
||||||
|
return response.content[0].text
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
print("## Testing call_llm")
|
||||||
|
prompt = "In a few words, what is the meaning of life?"
|
||||||
|
print(f"## Prompt: {prompt}")
|
||||||
|
response = call_llm(prompt)
|
||||||
|
print(f"## Response: {response}")
|
||||||
Loading…
Reference in New Issue