Skip to content

Conversation

erikgaas
Copy link
Contributor

@erikgaas erikgaas commented Oct 2, 2025

A user reported that multiple tool calls was failing to display correctly in the output cell: https://discord.com/channels/1303610061167263814/1310989166875508849/1423065780433719449

This was because lisette assumed a model response could only contain 1 tool call and therefore only would require one tool result.

This PR counts the number of tools made and waits for all of the results to come in before providing the closing tag.

image

@erikgaas erikgaas requested a review from jph00 October 2, 2025 03:17
@jph00
Copy link
Contributor

jph00 commented Oct 2, 2025

Many thanks for looking into this one @erikgaas ! :)

Can you add a couple of tests, including >=1 with multiple tool calls -- something that would have failed before.

Once we have that, maybe we can investigate alternate ways of implementing this. The current one is too complex for my little brain to comprehend! (Possibly that's my brain's fault; if we can't find something simpler but still correct, then so be it)

@erikgaas
Copy link
Contributor Author

erikgaas commented Oct 2, 2025

@RensDimmendaal fyi I think your last PR to make aformat_stream prettier didn't sync right between nbs and code. I'll be careful about this to make sure nothing crazy happens.

yield f"\n<details class='tool-usage-details'>\n\n `{fn.name}({_trunc_str(fn.arguments)})`\n"
elif isinstance(o, dict) and 'tool_call_id' in o:
yield f" - `{_trunc_str(_clean_str(o.get('content')))}`\n\n</details>\n\n"
fmt = mk_stream_formatter()
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jph00 @RensDimmendaal What do you think of this factored out aformat_stream?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Next step is to step through all of this with synthetic data. I can do that here, but will more likely continue that in the other PR.

Copy link
Contributor

@RensDimmendaal RensDimmendaal Oct 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@erikgaas wdyt of this was an alternative? its 12 lines more concise. And a bit more understandable, for me at least. I got a bit confused w all the nested functions and the hidden state.

#| export
class StreamFormatter:
    def __init__(self): self.n=0
    def __call__(self, o):
        if isinstance(o, ModelResponseStream): return delta_text(o, show_tools=False)    
        if isinstance(o, ModelResponse):
            if not (tcs := getattr(o.choices[0].message, 'tool_calls', None)): return ''
            self.n = len(tcs)
            calls = '\n'.join([f" `{tc.function.name}({_trunc_str(tc.function.arguments)})`" for tc in tcs])
            return f"\n<details class='tool-usage-details'>\n\n{calls}\n"  
        if isinstance(o, dict) and 'tool_call_id' in o:
            self.n -= 1
            return f"  - `{_trunc_str(_clean_str(o.get('content')))}`\n" + ('' if self.n else '\n</details>\n\n')
        return ''

async def aformat_stream(rs):
    fmt = StreamFormatter()
    async for o in rs: yield fmt(o)

res = ''.join(f"🔧 {tc.function.name}" for tc in delta.tool_calls if tc.id and tc.function.name)
if res: return f'\n{res}\n'
if hasattr(delta,'reasoning_content'): return '🧠' if delta.reasoning_content else '\n\n'
return None
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return None is not needed

def delta_text(msg, show_tools=True):
"Extract printable content from streaming delta, return None if nothing to print"
c = msg.choices[0]
if not c: return c
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could save a line w if not (c:=msg.choices[0]): return c

# %% ../nbs/00_core.ipynb
def _fmt_tool_call(o):
if not (c := getattr(o.choices[0].message, 'tool_calls', None)): return ''
calls = '\n'.join([f" `{tc.function.name}({_trunc_str(tc.function.arguments)})`" for tc in c])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ive have some issues in the past relying on tc.function.name being accessible as attributes...and instead having to access them via the [...] index. Sorry that I cant be more specific. But I suggest testing this one well as I've been burnt by that one in the past.

@RensDimmendaal
Copy link
Contributor

The markdown formatting looks a bit off, w first the multiple toolcalls on one line and then each result in a list.
Maybe not worth improving for this PR though!

image

"metadata": {},
"outputs": [],
"source": [
"assert md.count('<detail') == md.count('</detail')"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe use test_eq?

"{'tool_call_id': 'toolu_017xC4A71CAwU2e27gjU9C7X',\n",
" 'role': 'tool',\n",
" 'name': 'simple_add',\n",
" 'content': '3'}"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe you can turn these last three cells w print statements into a test, or add some note cells explaining what should we expect to see here?

@RensDimmendaal
Copy link
Contributor

Thanks a lot @erikgaas !

@jph00
Copy link
Contributor

jph00 commented Oct 6, 2025

@erikgaas also this one! :)

@comhar comhar mentioned this pull request Oct 15, 2025
@jph00
Copy link
Contributor

jph00 commented Oct 16, 2025

@erikgaas @comhar has the latest PR effectively fixed this issue too? (If so, we can close this)

@jph00
Copy link
Contributor

jph00 commented Oct 16, 2025

@RensDimmendaal is working on this in a different direction now so closing this

@jph00 jph00 closed this Oct 16, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants