Explain None results in CodeMode

This commit is contained in:
Aditya Vardhan
2026-07-14 23:01:40 +05:30
parent 95ec5f0c35
commit 3e6981293b
4 changed files with 32 additions and 17 deletions
+5 -5
View File
@@ -153,7 +153,7 @@ That fold-in grows `run_code`'s description, which invalidates the prompt-cache
## Return values
The last expression in the snippet is automatically captured as the return value -- the model does not need to `print()`. An assignment stores a value in the REPL but does not return it. Without a trailing expression or print output, `run_code` returns `{}`. Put the assigned name on the final line:
The last expression in the snippet is automatically captured as the return value -- the model does not need to `print()`. An assignment stores a value in the REPL but does not return it. A final expression that evaluates to `None` is also treated as no result. Without a non-`None` final expression or print output, `run_code` returns `{}`. Put the assigned name on the final line:
```python
result = await get_weather(city='Paris')
@@ -164,10 +164,10 @@ Reserve `print()` for supplementary logging: printed text is surfaced separately
| Scenario | Return |
|---|---|
| No print output with a final expression | Last expression value |
| Final assignment with no trailing expression or print output | `{}` |
| Print output with no final expression | `{'output': '<printed text>'}` |
| Print output with a plain final expression | `{'output': '<printed text>', 'result': <last expression>}` |
| Non-`None` final expression with no print output | Last expression value |
| Final assignment or `None` result with no print output | `{}` |
| Print output with no final expression or a `None` result | `{'output': '<printed text>'}` |
| Print output with a plain, non-`None` final expression | `{'output': '<printed text>', 'result': <last expression>}` |
| Multimodal final expression with no print output | Returned natively for model processing |
| Print output with a multimodal final expression | List with printed text followed by native multimodal content |
+5 -5
View File
@@ -146,7 +146,7 @@ become callable functions inside `run_code`. Tools without
## Return values
The last expression in the code snippet is automatically captured as the return value -- the model does not need to `print()`. An assignment stores a value in the REPL but does not return it. Without a trailing expression or print output, `run_code` returns `{}`. Put the assigned name on the final line:
The last expression in the code snippet is automatically captured as the return value -- the model does not need to `print()`. An assignment stores a value in the REPL but does not return it. A final expression that evaluates to `None` is also treated as no result. Without a non-`None` final expression or print output, `run_code` returns `{}`. Put the assigned name on the final line:
```python
result = await get_weather(city='Paris')
@@ -155,10 +155,10 @@ result
| Scenario | Return |
|---|---|
| No print output with a final expression | Last expression value |
| Final assignment with no trailing expression or print output | `{}` |
| Print output with no final expression | `{"output": "<printed text>"}` |
| Print output with a plain final expression | `{"output": "<printed text>", "result": <last expression>}` |
| Non-`None` final expression with no print output | Last expression value |
| Final assignment or `None` result with no print output | `{}` |
| Print output with no final expression or a `None` result | `{"output": "<printed text>"}` |
| Print output with a plain, non-`None` final expression | `{"output": "<printed text>", "result": <last expression>}` |
| Multimodal final expression with no print output | Returned natively for model processing |
| Print output with a multimodal final expression | List with printed text followed by native multimodal content |
+6 -5
View File
@@ -117,8 +117,8 @@ State is preserved between calls (REPL-style). Set `restart: true` to reset stat
The last expression's value is automatically captured as the return value -- you do **not** need to \
`print()` it. End the snippet with the value to return as a bare expression. A final assignment stores \
the value but does not return it. Without a final expression or print output, `run_code` returns `{}`. \
For example:
the value but does not return it. A final expression that evaluates to `None` is treated as no result. \
Without a non-`None` final expression or print output, `run_code` returns `{}`. For example:
```python
result = some_expression
@@ -128,9 +128,10 @@ result
Avoid `print()` for return values as it produces Python string representations, not structured data. \
Use `print()` only for supplementary logging or debug output.
Returns the last expression's value directly. If `print()` was called, returns \
`{"output": "<printed text>"}` with no final expression and adds `"result": <last expression>` for a \
plain final expression. Multimodal final expressions remain top-level in a list alongside the printed text.\
Returns a non-`None` last expression's value directly. If `print()` was called, returns \
`{"output": "<printed text>"}` without a non-`None` final expression and adds \
`"result": <last expression>` for a plain, non-`None` final expression. Multimodal final expressions \
remain top-level in a list alongside the printed text.\
"""
+16 -2
View File
@@ -247,9 +247,10 @@ class TestCodeMode:
assert description is not None
assert 'End the snippet with the value to return as a bare expression.' in description
assert 'result = some_expression\nresult' in description
assert 'Without a final expression or print output, `run_code` returns `{}`.' in description
assert 'Without a non-`None` final expression or print output, `run_code` returns `{}`.' in description
assert 'A final expression that evaluates to `None` is treated as no result.' in description
assert 'results = await asyncio.gather' not in description
assert 'adds `"result": <last expression>` for a plain final expression' in description
assert 'for a plain, non-`None` final expression' in description
assert 'Multimodal final expressions remain top-level' in description
async def test_run_code_function_examples_are_expressions(self) -> None:
@@ -430,6 +431,19 @@ class TestCodeMode:
# No print output → result returned directly (not wrapped in a dict).
assert result.return_value == 3
async def test_run_code_treats_none_as_no_expression_result(self) -> None:
"""A final `None` uses the same return shapes as no final expression."""
wrapper = CodeMode[object]().get_wrapper_toolset(_build_function_toolset(add))
assert isinstance(wrapper, CodeModeToolset)
ctx = await build_ctx(None, wrapper)
tools = await wrapper.get_tools(ctx)
result = await wrapper.call_tool('run_code', {'code': 'None'}, ctx, tools['run_code'])
assert result.return_value == {}
printed = await wrapper.call_tool('run_code', {'code': 'print("done")\nNone'}, ctx, tools['run_code'])
assert printed.return_value == {'output': 'done\n'}
async def test_run_code_syntax_error_becomes_model_retry(self) -> None:
"""A Python syntax error is surfaced as `ModelRetry` so the model can fix it."""
wrapper = CodeMode[object]().get_wrapper_toolset(_build_function_toolset(add))