llm
llm copied to clipboard
New design for logs involving tool calls (and maybe tool classes)
I am going to design the log output next, since that can inform the database schema.
Originally posted by @simonw in #1059
The list of all tools made available to a model is important context, because even the fact that a model did not chose to execute a tool is important information.
Log output should emphasize actual tool executions more than it does at the moment though.
Also: right now a sequence of prompt->tool->tool-response->tool->tool-response-and-reply is represented as 3 full prompt-and-responses in the logs, even though the user only said one thing and got one response at the end. Maybe the header levels should adjust for that to help represent some responses as automated parts of that flow?
Just running these two commands:
llm -T simple_eval '1234 * 123415'
llm -c '* 33'
Produced the following sequence of _four* logs entries, grouped under one conversation. I added <hr> just to help clarify where each one starts and ends:
2025-05-24T23:50:38 conversation: 01jw2b9acve2hrhy4nsxn30b8a id: 01jw2b9aczcwxhdjk4jha4757m
Model: gpt-4.1-mini
Prompt
1234 * 123415
Tools
- simple_eval:
64713bc9bfec5e5a6239b9145943c577f7781b81e2e7e129b5c1e665074926f9Evaluate a simple expression using the simpleeval library.
Arguments: {"expression": {"type": "string"}}Response
Tool calls
- simple_eval:
call_PxGKU2Z1csgz4ZRSU9xldYmB
Arguments: {"expression": "1234 * 123415"}
2025-05-24T23:50:39
Prompt
-- none --
Tools
- simple_eval:
64713bc9bfec5e5a6239b9145943c577f7781b81e2e7e129b5c1e665074926f9Evaluate a simple expression using the simpleeval library.
Arguments: {"expression": {"type": "string"}}Tool results
- simple_eval:
call_PxGKU2Z1csgz4ZRSU9xldYmB
152294110Response
1234 multiplied by 123415 is 152,294,110.
2025-05-24T23:50:46
Prompt
- 33
Tools
- simple_eval:
64713bc9bfec5e5a6239b9145943c577f7781b81e2e7e129b5c1e665074926f9Evaluate a simple expression using the simpleeval library.
Arguments: {"expression": {"type": "string"}}Response
Tool calls
- simple_eval:
call_67KH73vmC577yInLq4eZLATc
Arguments: {"expression": "152294110 * 33"}
2025-05-24T23:50:47
Prompt
-- none --
Tools
- simple_eval:
64713bc9bfec5e5a6239b9145943c577f7781b81e2e7e129b5c1e665074926f9Evaluate a simple expression using the simpleeval library.
Arguments: {"expression": {"type": "string"}}Tool results
- simple_eval:
call_67KH73vmC577yInLq4eZLATc
5025705630Response
152,294,110 multiplied by 33 is 5,025,705,630.
Here's a demo with multiple tool rounds from a single prompt:
llm --functions '
def lookup_population(country: str) -> int:
"Returns the current population of the specified fictional country"
return 123124
def can_have_dragons(population: int) -> bool:
"Returns True if the specified population can have dragons, False otherwise"
return population > 10000
' 'Can the country of Manganolia have dragons?' --td
Output:
Tool call: lookup_population({'country': 'Manganolia'})
123124
Tool call: can_have_dragons({'population': 123124})
true
Yes, the country of Manganolia can have dragons.
logs -c
With added <hr> - we get three logged responses from a single input prompt:
2025-05-25T00:13:35 conversation: 01jw2ckbm1s2wrm9p6a08fgk1g id: 01jw2ckbm5y50fbvc0szhfkb2r
Model: gpt-4.1-mini
Prompt
Can the country of Manganolia have dragons?
Tools
- lookup_population:
47a2be714160f98686797a434ea1d3551ca55d71848b2aabb85ca866252b9bfc
Returns the current population of the specified fictional country
Arguments: {"country": {"type": "string"}}- can_have_dragons:
41f175bd12e2497880157bd68c3e95597ae333e30fdda61a1c1837ed5436c9a3
Returns True if the specified population can have dragons, False otherwise
Arguments: {"population": {"type": "integer"}}Response
Tool calls
- lookup_population:
call_ZG9XihHfxeAi1iwaFpqhi4dq
Arguments: {"country": "Manganolia"}
2025-05-25T00:13:36
Prompt
-- none --
Tools
- lookup_population:
47a2be714160f98686797a434ea1d3551ca55d71848b2aabb85ca866252b9bfc
Returns the current population of the specified fictional country
Arguments: {"country": {"type": "string"}}- can_have_dragons:
41f175bd12e2497880157bd68c3e95597ae333e30fdda61a1c1837ed5436c9a3
Returns True if the specified population can have dragons, False otherwise
Arguments: {"population": {"type": "integer"}}Tool results
- lookup_population:
call_ZG9XihHfxeAi1iwaFpqhi4dq
123124Response
Tool calls
- can_have_dragons:
call_kCSmi2PbQTuaQdUYoDrDtY6W
Arguments: {"population": 123124}
2025-05-25T00:13:37
Prompt
-- none --
Tools
- lookup_population:
47a2be714160f98686797a434ea1d3551ca55d71848b2aabb85ca866252b9bfc
Returns the current population of the specified fictional country
Arguments: {"country": {"type": "string"}}- can_have_dragons:
41f175bd12e2497880157bd68c3e95597ae333e30fdda61a1c1837ed5436c9a3
Returns True if the specified population can have dragons, False otherwise
Arguments: {"population": {"type": "integer"}}Tool results
- can_have_dragons:
call_kCSmi2PbQTuaQdUYoDrDtY6W
trueResponse
Yes, the country of Manganolia can have dragons.
Here's a neater design for the logs without getting into the tool classes thing:
conversation: 01jw2ckbm1s2wrm9p6a08fgk1g
Model: gpt-4.1-mini
Tools:
- lookup_population:
47a2be714160f98686797a434ea1d3551ca55d71848b2aabb85ca866252b9bfc
Returns the current population of the specified fictional country
Arguments: {"country": {"type": "string"}}- can_have_dragons:
41f175bd12e2497880157bd68c3e95597ae333e30fdda61a1c1837ed5436c9a3
Returns True if the specified population can have dragons, False otherwise
Arguments: {"population": {"type": "integer"}}
2025-05-25T00:13:35
01jw2ckbm5y50fbvc0szhfkb2rPrompt
Can the country of Manganolia have dragons?
Tool calls
- lookup_population:
call_ZG9XihHfxeAi1iwaFpqhi4dq
Arguments: {"country": "Manganolia"}Token usage
86 input, 16 output
2025-05-25T00:13:36
01jw2ckce4ecq4zvhssyrvtgz7Tool results
- lookup_population:
call_ZG9XihHfxeAi1iwaFpqhi4dq
123124Tool calls
- can_have_dragons:
call_kCSmi2PbQTuaQdUYoDrDtY6W
Arguments: {"population": 123124}Token usage
112 input, 17 output
2025-05-25T00:13:37
01jw2ckbm5y50fbvc0szhfkb2rTool results
- can_have_dragons:
call_kCSmi2PbQTuaQdUYoDrDtY6W
trueResponse
Yes, the country of Manganolia can have dragons.
Token usage
140 input, 13 output
That example assumes tools stay constant throughout the conversation. I want to let users use --tool X to change the tools available half way through. Not entirely sure if this should be additive to the original tools or if it should replace them.
I think replacing makes more sense: keep people in full control of what tools are available. Better for if you want to avoid accidental data exfiltration attacks - although it's not safe to hit potentially malicious tokens either before OR after obtaining private data so maybe that's completely irrelevant here.
... but OK, if I assume that new design what could it look like with class-based tools like Playwright?
Maybe this - with a special note for when the Playwright() class is first instantiated:
conversation: 01jw2ckbm1s2wrm9p6a08fgk1g
Model: gpt-4.1-mini
Tools:
- Playwright({"show": true}):
47a2be714160f98686797a434ea1d3551ca55d71848b2aabb85ca866252b9bfc
Tools for interacting with web pages using Playwright. Start by calling open_browser(url), then interact with the page using the other tools.
Methods:
- open_browser(url: str): Open a browser to the specified URL
- accessibility_tree(): Get the accessibility tree of the current page
- screenshot() : Take a screenshot of the current page
- execute_javascript(code: str): Execute JavaScript code in the context of the current page and return the result
- simple_eval:
1231232131Evaluate a simple expression, e.g. 123312 * 123123 / 2313.4
Arguments:{"expression": "string"}
2025-05-25T00:13:35
01jw2ckbm5y50fbvc0szhfkb2rPrompt
Check what's new on simonwillison.net - times the number of h2s by 2314.
Tool calls
Playwright({"show": true}) - instance
01JW2DTBJ9JTR9S0AMPY9YQNZAPlaywright.open_browser:
call_ZG9XihHfxeAi1iwaFpqhi4dq
Arguments:{"url": "https://simonwillison.net/"}2025-05-25T00:13:36
01jw2ckce4ecq4zvhssyrvtgz7Tool results
- Playwright.open_browser:
call_ZG9XihHfxeAi1iwaFpqhi4dq
readyTool calls
- Playwright.execute_javascript:
call_kCSmi2PbQTuaQdUYoDrDtY6W
Arguments:{"code": "document.querySelectorAll('h2').length"}
2025-05-25T00:13:37
01jw2ckbm5y50fbvc0szhfkb2rTool results
- Playwright.execute_javascript:
call_kCSmi2PbQTuaQdUYoDrDtY6W
16Tool calls
- simple_eval:
call_kCSmi2PbQTuaQdUYoDrDtY6W
Arguments:{"expression": "16 * 2314"}2025-05-25T00:13:37
01jw2ckbm5y50fbvc0szhfkb2rTool results
- simple_eval:
call_kCSmi2PbQTuaQdUYoDrDtY6W
37024Response
The number of
<h2>elements on simonwillison.net is 16. Multiplying this by 2314 gives a total of 37024.
I haven't thought very hard about what happens if the arguments or output from a tool call are really long.
Or if a tool call returns an attachment, see:
- #1014
I'm bumping this from the stable tools release milestone for lack of time - currently it does this which isn't perfect (no instance information) but is usable:
# 2025-05-27T04:03:06 conversation: 01jw7yh1twd32fyfnndwag9bgh id: 01jw7yh1txvrqga7psej6q3czh
Model: **gpt-4.1-mini**
## Prompt
show tables
### Tools
- **Datasette_query**: `1aa6d35df9b225d03daee33201eab3ae539b67157f3a8ec544b0b5d8eaeeeeb9`<br>
Execute provided SQLite SQL query - read-only, only use SELECT<br>
Arguments: {"sqlite_sql": {"type": "string"}}
- **Datasette_schema**: `24ec61558e67e01fe6927f059ab376bad38b8dfd815851e9f3846371ef35d246`<br>
View the SQLite schema of the attached database<br>
Arguments: {}
## Response
### Tool calls
- **Datasette_schema**: `call_KaODghD96R8F975bYaTLTzhf`<br>
Arguments: {}
# 2025-05-27T04:03:07
## Prompt
-- none --
### Tools
- **Datasette_query**: `1aa6d35df9b225d03daee33201eab3ae539b67157f3a8ec544b0b5d8eaeeeeb9`<br>
Execute provided SQLite SQL query - read-only, only use SELECT<br>
Arguments: {"sqlite_sql": {"type": "string"}}
- **Datasette_schema**: `24ec61558e67e01fe6927f059ab376bad38b8dfd815851e9f3846371ef35d246`<br>
View the SQLite schema of the attached database<br>
Arguments: {}
### Tool results
- **Datasette_schema**: `call_KaODghD96R8F975bYaTLTzhf`<br>
[{"group_concat(sql, ';')": "CREATE TABLE counters (name text primary key, value integer)"}]
## Response
There is one table in the database named "counters".