Skip to content

Commit f1ebff1

Browse files
committed
Add first-pass implementation of LLM ReAct pattern
- <https://arxiv.org/pdf/2210.03629> - <https://til.simonwillison.net/llms/python-react-pattern>
1 parent 5ee6005 commit f1ebff1

File tree

3 files changed

+61
-16
lines changed

3 files changed

+61
-16
lines changed

TODO.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
- ChatGPT
4343
- Huggingface
4444
- Ollama
45+
- Tool function exception handling
4546
- Clipboard integration
4647
- Cut
4748
- Fix "put" behavior to copy formulas around to selection
@@ -85,6 +86,7 @@
8586
- Namespaced formula functions
8687
- Make parsing faster
8788
- Hand-written recursive descent
89+
- Add <> as alternative for !=
8890
- Local storage
8991
- Save sheets
9092
- Load sheets

src/Llm.svelte

Lines changed: 40 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -66,44 +66,49 @@
6666
let responsePromise = $state();
6767
let modelName = $state("Gemini");
6868
let template =
69-
$state(`You modify a spreadsheet by executing JavaScript code. You output JavaScript code in Markdown blocks. You do not output any explanation or comments. You are concise and succinct. You are a technical expert with extensive experience with JavaScript and data science. You query the spreadsheet for more information if it would improve your response.
69+
$state(`You modify a spreadsheet by executing JavaScript code. You output JavaScript code to run in Markdown blocks. You are a broadly knowledgeable, technical expert.
70+
71+
First you plan. Then, you operate in a loop of think, query/action, PAUSE, result (sent by the user). You loop until the plan is complete, or until the user gives new instructions. Only the first message contains a plan, steps of the loop do not. If the user gives new instructions, make a new plan.
72+
73+
74+
You can run the following functions:
75+
- \${Object.entries(llmToolFunctions).map(([name, f]) => {
76+
const args = f.toString().replaceAll("\\n", " ").replaceAll(/ */g, " ").match(/\\([^)]*\\)/)?.[0] ?? "";
77+
return \`llmToolFunctions.\${name}\${args} \${f.description ?? ""}\`;
78+
}).join("\\n- ")}
79+
7080
7181
Formulas begin with an equals sign (\\\`=\\\`), and can contain:
7282
- Numbers such as \\\`123\\\` and \\\`-3.21\\\`
7383
- Strings such as \\\`"asdf"\\\` and \\\`"multi\\\\nline"\\\`
7484
- Singleton references in R1C1 notation such as \\\`R10C3\\\` (zero-indexed) and \\\`RC0\\\` for absolute references, \\\`R[-1]c[2]\\\` for relative references, and \\\`RC\\\` for self-references
85+
- Row and column indices are zero-indexed
7586
- Negative absolute references start from the end of a row or column, such as \\\`R-1C-1\\\` to select the cell in the bottom right corner of the sheet, and \\\`R1C0:R1C-1\\\` to select all of row 1
7687
- Ranges such as \\\`R[-3]C:R[-1]C\\\`
7788
- References and ranges across sheets like \\\`S1!R1C1\\\` and \\\`S[1]!R2C2:R2C-1\\\` and \\\`S-1R2C3\\\` (the exclamation point is optional)
89+
- Sheet indices are zero-indexed
7890
- Function calls (case insensitive) containing expressions as arguments such as \\\`sum(RC0:RC[-1])\\\`, \\\`sLiDeR(0, 10, 1)\\\`, and \\\`DOLLARS(PRODUCT(1 * 2 + 3, 4, 3, R[-1]C))\\\`
79-
- Optionally parenthesized binary operations combining any of the expressions above such as \\\`(RC[-2] + RC[-3]) * 100\\\` and \\\`1 + -2 + 3 ** 5\\\`
91+
- Optionally parenthesized binary operations combining any of the expressions above using standard JavaScript arithmetic operations such as \\\`(RC[-2] + RC[-3]) * 100\\\` and \\\`1 + -2 + 3 ** 5\\\`
8092
8193
Formula function definitions have access to a \\\`this\\\` object with:
8294
- this.row and this.col - readonly
8395
- this.set(value)
8496
- this.element - writable with the HTML DOMElement that will be displayed in the cell (e.g., buttons, checkboxes, canvas, SVG, etc.)
8597
- this.style - writable with the CSS style string for the containing \\\`<td>\\\`
8698
87-
You define any formula functions you use that do not already exist. To define formula functions, they must be assigned like: "functions.formula_name = function() {}" in a call to \\\`addFunction\\\`.
88-
89-
90-
The currently available formula functions are all of the JavaScript Math.* functions and: \${Object.keys(formulaFunctions).filter(k => !(k in Math)).join(", ")}.
91-
92-
You can run the following functions:
93-
- \${Object.entries(llmToolFunctions).map(([name, f]) => {
94-
const args = f.toString().replaceAll("\\n", " ").replaceAll(/ */g, " ").match(/\\([^)]*\\)/)?.[0] ?? "";
95-
return \`llmToolFunctions.\${name}\${args} \${f.description ?? ""}\`;
96-
}).join("\\n- ")}
97-
98-
Querying can only be used to get data from the environment. It cannot be used to ask the user questions. You can query multiple things at once.
99+
You add any formula functions you use if they do not already exist.
99100
100101
101102
Example:
102103
103104
User:
104-
Find the receipt items that contain seafood items and make them red.
105+
Find the receipt items that contain seafood and make them red.
105106
106107
Model:
108+
Plan: First, I should learn about the current sheets through a query. Then, I should find the items with seafood using another query. Then, I should check if there is already a formula to make items red. If not, I should add one. Finally, I should modify the formulas for the seafood item cells to wrap them in calls to the new red formula.
109+
110+
Thought: I should learn more about the current sheets using a query.
111+
107112
\\\`\\\`\\\`javascript
108113
const sheets = llmToolFunctions.getSheets();
109114
llmToolFunctions.query("Sheets", sheets);
@@ -116,21 +121,41 @@ sheets.forEach((sheet, sheetIndex) => {
116121
llmToolFunctions.query("First rows", firstRows);
117122
\\\`\\\`\\\`
118123
124+
PAUSE
125+
119126
User:
120127
Sheets: [{"name": "Sheet 1", "rows": 10, "cols": 3}, {"name": "Receipt", "rows": 6, "cols": 2}]
121128
First rows: {"Sheet 1": [null, null, null], "Receipt": ["=BOLD(\\\\"Item\\\\")", "=BOLD(\\\\"Cost\\\\")"]}
122129
123130
Model:
131+
Thought: I should find the cells that might contain seafood. I now know that I can identify them by the "Item" column.
132+
124133
\\\`\\\`\\\`javascript
125134
llmToolFunctions.query("Items", new Array(6).fill().map(
126135
(_, i) => llmToolFunctions.getCellFormula(1, i, 0)
127136
));
128137
\\\`\\\`\\\`
129138
139+
PAUSE
140+
130141
User:
131142
Items: ["=BOLD(\\\\"Items\\\\")", "shrimp", "chicken", "vegetables", "scallops", "cups"]
132143
133144
Model:
145+
Thought: Now that I know which rows contain seafood, I should check to see if there is already a formula to make items red.
146+
147+
\\\`\\\`\\\`javascript
148+
llmToolFunctions.query("Formulas", llmToolFunctions.getFormulaFunctionsList());
149+
\\\`\\\`\\\`
150+
151+
PAUSE
152+
153+
User:
154+
Formulas: [ "abs", "acos", ..., "average", "rand", "slider", "bold", "center", "dollars", "sparkbars", "checkbox" ]
155+
156+
Model:
157+
Thought: Since there is no formula to make items red, I should add one. And I can make the seafood item cells red to complete my task.
158+
134159
\\\`\\\`\\\`javascript
135160
llmToolFunctions.addFunction(\\\`
136161
functions.red = function (s) {

src/llm.svelte.js

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,14 @@ llmToolFunctions.newSheet = function (name, cells) {
1111
llmToolFunctions.newSheet.description =
1212
"takes a 2D array of strings containing formulas";
1313

14+
llmToolFunctions.addRow = function (sheetIndex, offset) {
15+
this.globals.sheets[sheetIndex]?.addRows(1, offset);
16+
};
17+
18+
llmToolFunctions.addCol = function (sheetIndex, offset) {
19+
this.globals.sheets[sheetIndex]?.addCols(1, offset);
20+
};
21+
1422
llmToolFunctions.addFunction = function (code) {
1523
this.globals.formulaCode += `\n${code}\n`;
1624
};
@@ -29,6 +37,10 @@ llmToolFunctions.setCellFormula = function (sheetIndex, row, col, formula) {
2937
this.globals.sheets[sheetIndex].cells[row][col].formula = formula;
3038
};
3139

40+
llmToolFunctions.getFormulaFunctionsList = function () {
41+
return Object.keys(functions);
42+
};
43+
3244
llmToolFunctions.getFormulaFunction = function (name) {
3345
return functions[name].toString();
3446
};
@@ -59,6 +71,10 @@ llmModels.Gemini = {
5971
"Content-Type": "application/json",
6072
},
6173
body: JSON.stringify({
74+
generation_config: {
75+
temperature: 0.3,
76+
stop_sequences: ["PAUSE"],
77+
},
6278
system_instruction: {
6379
parts: conversation
6480
.filter(({ role }) => role == "system")
@@ -89,6 +105,8 @@ llmModels.Gemini = {
89105
console.error(e);
90106
throw e;
91107
});
92-
return response?.candidates?.[0]?.content?.parts?.map(({ text }) => text);
108+
return response?.candidates?.[0]?.content?.parts
109+
?.map(({ text }) => text.trim())
110+
.filter((text) => text);
93111
},
94112
};

0 commit comments

Comments
 (0)