Commit ca18818
authored
feat: add tool calling support to m serve (#850)
* feat: add tool calling support to m serve
Signed-off-by: Mark Sturdevant <mark.sturdevant@ibm.com>
* fix: fixed the bug in m serve where finish_reason=tool_calls for empty dict
Fixed the bug where an empty tool_calls dict ({}) incorrectly produced finish_reason="tool_calls" with an empty array instead of finish_reason="stop" with tool_calls=None.
Signed-off-by: Mark Sturdevant <mark.sturdevant@ibm.com>
* fix: move message add to outside the loop in client_tool_calling.py example
Issue: The assistant message was being added inside the loop for each tool call, causing duplication when multiple tool calls were present.
Fix: Moved the assistant message append outside the loop (before processing tool calls), so it's only added once. Now the loop only adds tool responses.
Signed-off-by: Mark Sturdevant <mark.sturdevant@ibm.com>
* fix: cli app.py loop variable tool_name is never used
The dict key tool_name is never used — the function name comes from model_tool_call.name. Using .values() instead.
Signed-off-by: Mark Sturdevant <mark.sturdevant@ibm.com>
* fix: fix test_mot_init_typing() hasattr was always true
Replaced hasattr() with direct __dict__ membership tests to correctly distinguish:
1. Typed instances (ModelOutputThunk[float](...)) - have __orig_class__ in their instance dict
2. Untyped instances (ModelOutputThunk(...)) - do NOT have __orig_class__ in their instance dict
Signed-off-by: Mark Sturdevant <mark.sturdevant@ibm.com>
* fix: update m_serve_example_tool_calling.py to use safer example tool
Security issue resolved in `m_serve_example_tool_calling.py`:
**Changes made:**
- Replaced `CalculatorTool` (which used unsafe `eval()` with `# noqa: S307`) with `GetStockPriceTool`
- New tool demonstrates API-calling pattern with mock stock prices (AAPL, GOOGL, MSFT, TSLA)
- Updated all references: `calculator_tool` → `stock_price_tool`
- Maintains the same tool calling demonstration with two tools (weather + stock price)
**Why this is better:**
- Eliminates security risk entirely (no `eval()` or suppressed lints)
- Still demonstrates multiple tools effectively
- Uses safe, realistic API-calling pattern that users can copy
- No dangerous code that could be copy-pasted into production
Signed-off-by: Mark Sturdevant <mark.sturdevant@ibm.com>
* fix: replace repeated hard-coded string with constant
Signed-off-by: Mark Sturdevant <mark.sturdevant@ibm.com>
* fix: add TOOL_CHOICE to ModelOptions like TEMPERATURE not a sentinel
The pass-thru behavior was not clear enough, so adding it to ModelOptions
where important options are known. Most of these are sentinels which are
removed (because @@@) but this will be like TEMPERATURE which is passed
through to the backends.
No behavior change, but give a handly constant and a place to look for these.
This does not address all the other possible pass through args.
Signed-off-by: Mark Sturdevant <mark.sturdevant@ibm.com>
Assisted-by: IBM Bob
* fix: fix m serve tool-calling examples
- switch server example to OpenAIBackend
- align tool-calling example with tested Granite model setup
- narrow advertised tools when `tool_choice` selects a specific function
- enable `tool_calls=True` in the serve path
- replace calculator example with stock-price tool
- examples 1/2 as tool-call-only demos
- example 4 as the full tool execution round-trip
- improve client diagnostics for empty/no-tool responses
Signed-off-by: Mark Sturdevant <mark.sturdevant@ibm.com>
Assisted-by: IBM Bob
* fix: remove unused imports in example
Signed-off-by: Mark Sturdevant <mark.sturdevant@ibm.com>
* feat: cli support for OpenAI API tool calling with streaming
Signed-off-by: Mark Sturdevant <mark.sturdevant@ibm.com>
Assisted-by: IBM Bob
* fix: add required index field to streaming tool call deltas
The OpenAI streaming spec requires each item in delta.tool_calls to carry
an index field. Clients including the openai Python SDK, LangChain, and
LiteLLM key their delta-reassembly state machine on this field.
Without it, they silently drop tool calls, coalesce them incorrectly, or
raise a TypeError depending on version.
Changes:
- Add ChatCompletionMessageToolCallDelta model with required index field
- Add ToolCallFunctionDelta model for streaming function deltas
- Update ChatCompletionChunkDelta to use delta models
- Update streaming.py to populate index field using enumerate()
- Add comprehensive tests verifying index field presence
- Update existing test to check for index field
The bundled client_streaming_tool_calling.py example masked this issue
because it reads delta.tool_calls verbatim rather than going through
SDK delta reassembly.
Fixes compatibility with OpenAI SDK, LangChain, and LiteLLM streaming
tool call consumers.
Signed-off-by: Mark Sturdevant <mark.sturdevant@ibm.com>
Assisted-by: IBM Bob
* fix: move build_tool_calls invocation
build_tool_calls was called before streaming block and then not used in case of streaming.
Rearrange condition and call to avoid wasted call.
Signed-off-by: Mark Sturdevant <mark.sturdevant@ibm.com>
* test: add integration test for cli/serve using TestClient with streaming and tool calling
Signed-off-by: Mark Sturdevant <mark.sturdevant@ibm.com>
* fix: use fallback for json.dumps in build_tool_calls
Use str to non-serializable types. This should effectively avoid TypeError (in normal situations).
Signed-off-by: Mark Sturdevant <mark.sturdevant@ibm.com>
* test: restore cli streaming tests to fix conflicts
New tests for tooling improved coverage, but the significant
rewrite caused too much diverging from main. Keeping the old
tests in places while adding new tests in new file will help
sort this out.
Signed-off-by: Mark Sturdevant <mark.sturdevant@ibm.com>
* test: update output.usage -> output.generation.usage
Rebased and now the new tests need updating.
Signed-off-by: Mark Sturdevant <mark.sturdevant@ibm.com>
* test: update tests usage -> gneration.usage
More new tests need fixing after rebase.
Signed-off-by: Mark Sturdevant <mark.sturdevant@ibm.com>
* refactor(serve): simplify tool call construction with Pydantic validation
- Use model_validate() instead of manual field mapping for tool calls
- Move uuid import to module level in openai_compatible_helpers
- Replace manual async function with AsyncMock in streaming error test
- Remove redundant comments about tool call extraction
These changes reduce code duplication and leverage Pydantic's built-in
validation for cleaner, more maintainable code.
Signed-off-by: Mark Sturdevant <mark.sturdevant@ibm.com>
Assisted-by: IBM Bob
* fix: remove unused imports
Signed-off-by: Mark Sturdevant <mark.sturdevant@ibm.com>
---------
Signed-off-by: Mark Sturdevant <mark.sturdevant@ibm.com>1 parent bf8a8ad commit ca18818
15 files changed
Lines changed: 2767 additions & 21 deletions
File tree
- cli/serve
- docs/examples/m_serve
- mellea
- backends
- helpers
- test
- cli
- core
- helpers
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
7 | 7 | | |
8 | 8 | | |
9 | 9 | | |
| 10 | + | |
10 | 11 | | |
11 | 12 | | |
12 | 13 | | |
| |||
21 | 22 | | |
22 | 23 | | |
23 | 24 | | |
24 | | - | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
25 | 29 | | |
26 | 30 | | |
27 | 31 | | |
28 | 32 | | |
| 33 | + | |
29 | 34 | | |
30 | 35 | | |
31 | 36 | | |
| |||
111 | 116 | | |
112 | 117 | | |
113 | 118 | | |
114 | | - | |
115 | | - | |
116 | 119 | | |
117 | 120 | | |
118 | 121 | | |
119 | 122 | | |
120 | 123 | | |
121 | 124 | | |
| 125 | + | |
| 126 | + | |
122 | 127 | | |
123 | 128 | | |
124 | 129 | | |
| |||
171 | 176 | | |
172 | 177 | | |
173 | 178 | | |
174 | | - | |
175 | | - | |
176 | 179 | | |
177 | 180 | | |
178 | 181 | | |
| |||
190 | 193 | | |
191 | 194 | | |
192 | 195 | | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
193 | 214 | | |
194 | 215 | | |
195 | 216 | | |
| |||
198 | 219 | | |
199 | 220 | | |
200 | 221 | | |
201 | | - | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
202 | 225 | | |
203 | | - | |
| 226 | + | |
204 | 227 | | |
205 | 228 | | |
206 | 229 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
80 | 80 | | |
81 | 81 | | |
82 | 82 | | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
83 | 144 | | |
84 | 145 | | |
85 | 146 | | |
| |||
91 | 152 | | |
92 | 153 | | |
93 | 154 | | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
94 | 158 | | |
95 | 159 | | |
96 | 160 | | |
| |||
144 | 208 | | |
145 | 209 | | |
146 | 210 | | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
147 | 219 | | |
148 | 220 | | |
149 | 221 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
3 | 3 | | |
| 4 | + | |
4 | 5 | | |
5 | 6 | | |
6 | 7 | | |
7 | | - | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
8 | 12 | | |
9 | 13 | | |
10 | 14 | | |
11 | 15 | | |
12 | 16 | | |
| 17 | + | |
13 | 18 | | |
14 | 19 | | |
15 | 20 | | |
| |||
98 | 103 | | |
99 | 104 | | |
100 | 105 | | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
101 | 138 | | |
102 | 139 | | |
103 | 140 | | |
| |||
112 | 149 | | |
113 | 150 | | |
114 | 151 | | |
115 | | - | |
| 152 | + | |
116 | 153 | | |
117 | 154 | | |
118 | 155 | | |
| |||
0 commit comments