Skip to content

Commit 92449b8

Browse files
committed
Structured output with tool calling
Fixed: Structured output not working in conjunction with tool calling when calling generate ollamaChat constructor erroring with ResponseFormat NVP set to a struct Added: Double-quotes to .githooks/pre-commit to handle spaces in repo path
1 parent aa1ecf0 commit 92449b8

File tree

7 files changed

+35
-8
lines changed

7 files changed

+35
-8
lines changed

.githooks/pre-commit

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
set -e
44

5-
cd $(git rev-parse --show-toplevel)
5+
cd "$(git rev-parse --show-toplevel)"
66
pwd
77

88
# For all commits of mlx files, create corresponding Markdown (md) files.

azureChat.m

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -292,7 +292,9 @@
292292
error("llms:apiReturnedError",llms.utils.errorMessageCatalog.getMessage("llms:apiReturnedError",err));
293293
end
294294

295-
text = llms.internal.reformatOutput(text,nvp.ResponseFormat);
295+
if ~isempty(text)
296+
text = llms.internal.reformatOutput(text,nvp.ResponseFormat);
297+
end
296298
end
297299
end
298300

ollamaChat.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@
9494
nvp.MinP {llms.utils.mustBeValidProbability} = 0
9595
nvp.TopK (1,1) {mustBeReal,mustBePositive} = Inf
9696
nvp.StopSequences {llms.utils.mustBeValidStop} = {}
97-
nvp.ResponseFormat (1,1) string {mustBeMember(nvp.ResponseFormat,["text","json"])} = "text"
97+
nvp.ResponseFormat {llms.utils.mustBeResponseFormat} = "text"
9898
nvp.TimeOut (1,1) {mustBeNumeric,mustBeReal,mustBePositive} = 120
9999
nvp.TailFreeSamplingZ (1,1) {mustBeNumeric,mustBeReal} = 1
100100
nvp.StreamFun (1,1) {mustBeA(nvp.StreamFun,'function_handle')}

openAIChat.m

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@
8080
% TimeOut - Connection Timeout in seconds.
8181
%
8282

83-
% Copyright 2023-2024 The MathWorks, Inc.
83+
% Copyright 2023-2025 The MathWorks, Inc.
8484

8585
properties(SetAccess=private)
8686
%MODELNAME Model name.
@@ -270,7 +270,9 @@
270270
error("llms:apiReturnedError",llms.utils.errorMessageCatalog.getMessage("llms:apiReturnedError",err));
271271
end
272272

273-
text = llms.internal.reformatOutput(text,nvp.ResponseFormat);
273+
if ~isempty(text)
274+
text = llms.internal.reformatOutput(text,nvp.ResponseFormat);
275+
end
274276
end
275277
end
276278

tests/hopenAIChat.m

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,5 +172,23 @@ function keyNotFound(testCase)
172172
unsetenv("AZURE_OPENAI_API_KEY");
173173
testCase.verifyError(testCase.constructor, "llms:keyMustBeSpecified");
174174
end
175+
176+
function toolCallingAndStructuredOutput(testCase)
177+
import matlab.unittest.constraints.HasField
178+
179+
f = openAIFunction("addTwoNumbers", "Add two numbers");
180+
f = addParameter(f, "a");
181+
f = addParameter(f, "b");
182+
183+
responseFormat = struct("llmReply", "The LLM returns a struct if no tool is called");
184+
185+
chat = testCase.constructor("You are a helpful agent.", ...
186+
Tools=f, ResponseFormat=responseFormat);
187+
prompt = "What's 1+1?";
188+
189+
[reply, complete] = testCase.verifyWarningFree(@() generate(chat, prompt));
190+
testCase.verifyEmpty(reply);
191+
testCase.verifyThat(complete, HasField("tool_calls"));
192+
end
175193
end
176194
end

tests/hstructuredOutput.m

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,16 @@
44
% Copyright 2023-2025 The MathWorks, Inc.
55

66
properties(Abstract)
7+
constructor
78
structuredModel
89
end
910

1011
methods(Test)
11-
% Test methods
12+
function constructWithStructuredOutput(testCase)
13+
responseFormat = struct("llmReply","This is an example struct");
14+
testCase.verifyWarningFree(@() testCase.constructor(ResponseFormat=responseFormat));
15+
end
16+
1217
function generateWithStructuredOutput(testCase)
1318
import matlab.unittest.constraints.ContainsSubstring
1419
import matlab.unittest.constraints.StartsWithSubstring

tests/tollamaChat.m

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -424,11 +424,11 @@ function queryModels(testCase)
424424
invalidConstructorInput = struct( ...
425425
"InvalidResponseFormatValue", struct( ...
426426
"Input",{{"ResponseFormat", "foo" }},...
427-
"Error", "MATLAB:validators:mustBeMember"), ...
427+
"Error", "llms:incorrectResponseFormat"), ...
428428
...
429429
"InvalidResponseFormatSize", struct( ...
430430
"Input",{{"ResponseFormat", ["text" "text"] }},...
431-
"Error", "MATLAB:validation:IncompatibleSize"), ...
431+
"Error", "MATLAB:validators:mustBeTextScalar"), ...
432432
...
433433
"InvalidStreamFunType", struct( ...
434434
"Input",{{"StreamFun", "2" }},...

0 commit comments

Comments
 (0)