Skip to content

Commit 958a0f4

Browse files
committed
add: C Bindings for tool calls
1 parent c0870a9 commit 958a0f4

File tree

3 files changed

+178
-43
lines changed

3 files changed

+178
-43
lines changed

cmd/c/main.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,24 @@ func quill_is_waiting_for_choice(interpID C.int) *C.char {
124124
return cResult
125125
}
126126

127+
//export quill_is_waiting_for_tool_call
128+
func quill_is_waiting_for_tool_call(interpID C.int) *C.char {
129+
mu.Lock()
130+
interp, exists := interpreters[int(interpID)]
131+
mu.Unlock()
132+
133+
if !exists {
134+
return C.CString(`{"success":false,"error":"Invalid interpreter ID"}`)
135+
}
136+
137+
result := interp.IsWaitingForToolCall()
138+
cResult := C.CString(result)
139+
if cResult == nil {
140+
return C.CString(`{"success":false,"error":"Failed to allocate C string"}`)
141+
}
142+
return cResult
143+
}
144+
127145
//export quill_parse_only
128146
func quill_parse_only(source *C.char) *C.char {
129147
goSource := C.GoString(source)
@@ -135,6 +153,30 @@ func quill_parse_only(source *C.char) *C.char {
135153
return cResult
136154
}
137155

156+
//export quill_handle_tool_call_response
157+
func quill_handle_tool_call_response(interpID C.int, responseJSON *C.char) *C.char {
158+
mu.Lock()
159+
interp, exists := interpreters[int(interpID)]
160+
mu.Unlock()
161+
162+
if !exists {
163+
return C.CString(`{"success":false,"error":"Invalid interpreter ID"}`)
164+
}
165+
166+
// Parse the JSON response to extract the actual result
167+
goResponseJSON := C.GoString(responseJSON)
168+
169+
// For now, we'll pass the JSON string directly to the interpreter
170+
// In a more sophisticated implementation, you might want to parse this JSON
171+
// and extract the actual value based on type
172+
result := interp.HandleToolCallResponse(goResponseJSON)
173+
cResult := C.CString(result)
174+
if cResult == nil {
175+
return C.CString(`{"success":false,"error":"Failed to allocate C string"}`)
176+
}
177+
return cResult
178+
}
179+
138180
//export quill_free_string
139181
func quill_free_string(str *C.char) {
140182
C.free(unsafe.Pointer(str))

examples/c/example.c

Lines changed: 85 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -3,39 +3,61 @@
33
#include <string.h>
44
#include "libquill.h"
55

6-
// Simple function to extract interpreter ID from JSON response
7-
// In a real application, you'd use a proper JSON parser
8-
int extract_interpreter_id(const char* json_response) {
9-
// Look for "success":true first
10-
if (strstr(json_response, "\"success\":true") == NULL) {
11-
return -1; // Error case
12-
}
6+
// Mock tool call handler that provides realistic responses
7+
char* get_mock_tool_result(const char* step_result) {
8+
// In a real application, you'd parse the JSON to extract function name and arguments
9+
// For demo purposes, we'll check for common function names in the JSON
1310

14-
// This is a very basic extraction - in practice use a JSON library
15-
// The response should contain the interpreter ID
16-
// For now, we'll return a hardcoded value since the Go code doesn't
17-
// actually include the ID in the response
18-
return 1;
11+
if (strstr(step_result, "getPlayerName") != NULL) {
12+
return "\"Hero\"";
13+
} else if (strstr(step_result, "getPlayerAge") != NULL) {
14+
return "25";
15+
} else if (strstr(step_result, "agePlusFive") != NULL) {
16+
return "30";
17+
} else if (strstr(step_result, "getData") != NULL) {
18+
if (strstr(step_result, "gold") != NULL) {
19+
return "150";
20+
} else if (strstr(step_result, "health") != NULL) {
21+
return "85";
22+
} else {
23+
return "\"Unknown\"";
24+
}
25+
} else if (strstr(step_result, "getItemPrice") != NULL) {
26+
return "20";
27+
} else {
28+
return "\"DefaultValue\"";
29+
}
1930
}
2031

2132
int main() {
22-
const char* source =
23-
"ALEX: \"Hello there!\"\n"
24-
"BELLA: \"Hi Alex!\"\n"
33+
char* source =
34+
"# Simple tool call that retrieves the player's name with fallback\n"
35+
"LET player_name = <getPlayerName;>\n"
36+
"LET player_age = <getPlayerAge;> ?? 18\n"
37+
"LET age_plus_five = <agePlusFive; player_age>\n"
38+
"\n"
39+
"SYSTEM: \"Hello, {player_name}! You are {player_age} years old.\"\n"
40+
"IF player_age < 18 {\n"
41+
" SYSTEM: \"You are quite young to be here!\"\n"
42+
"}\n"
2543
"\n"
26-
"CHOICE {\n"
27-
" \"How are you?\" {\n"
28-
" ALEX: \"I'm doing great, thanks!\"\n"
29-
" },\n"
30-
" \"What's new?\" {\n"
31-
" ALEX: \"Not much, just working on some projects.\"\n"
32-
" }\n"
44+
"IF player_age >= 18 {\n"
45+
" SYSTEM: \"You are old enough to be here.\"\n"
3346
"}\n"
3447
"\n"
35-
"ALEX: \"Thanks for asking!\"\n"
48+
"# Advanced inline tool call. Syntax: <function; argument>\n"
49+
"SYSTEM: \"Your current gold balance is <getData; \"gold\"> gold coins.\"\n"
50+
"\n"
51+
"# Multiple tool calls in a single line\n"
52+
"SYSTEM: \"Your current gold balance is <getData; \"gold\"> gold coins and your health is <getData; \"health\">.\"\n"
53+
"\n"
54+
"# Multiple tool call arguments\n"
55+
"# Tool function getItemPrice takes two arguments: item type and item level\n"
56+
"LET item_price = <getItemPrice; \"potion\", 4>\n"
57+
"SYSTEM: \"The price of a level 4 potion is {item_price} gold coins.\"\n"
3658
"END\n";
3759

38-
printf("=== Quill C API Test ===\n\n");
60+
printf("=== Quill C API Tool Call Test ===\n\n");
3961

4062
// Test parse only
4163
printf("1. Testing parse only:\n");
@@ -45,18 +67,12 @@ int main() {
4567

4668
// Create interpreter
4769
printf("2. Creating interpreter:\n");
48-
char* init_result = quill_new_interpreter(source);
49-
printf("Init result: %s\n\n", init_result);
50-
51-
// Extract interpreter ID from response
52-
int interp_id = extract_interpreter_id(init_result);
70+
int interp_id = quill_new_interpreter(source);
5371
if (interp_id == -1) {
5472
printf("Failed to create interpreter\n");
55-
quill_free_string(init_result);
5673
return 1;
5774
}
58-
59-
quill_free_string(init_result);
75+
printf("Created interpreter with ID: %d\n\n", interp_id);
6076

6177
// Test interpreter methods
6278
printf("3. Testing interpreter methods:\n");
@@ -67,33 +83,59 @@ int main() {
6783
quill_free_string(state);
6884

6985
// Step through execution
70-
for (int i = 0; i < 5; i++) {
86+
for (int i = 0; i < 10; i++) {
7187
printf("\n--- Step %d ---\n", i + 1);
7288

7389
char* step_result = quill_step(interp_id);
7490
printf("Step result: %s\n", step_result);
75-
quill_free_string(step_result);
7691

7792
// Check if waiting for choice
78-
char* waiting = quill_is_waiting_for_choice(interp_id);
79-
printf("Waiting for choice: %s\n", waiting);
93+
char* waiting_choice = quill_is_waiting_for_choice(interp_id);
94+
printf("Waiting for choice: %s\n", waiting_choice);
95+
96+
// Check if waiting for tool call
97+
char* waiting_tool = quill_is_waiting_for_tool_call(interp_id);
98+
printf("Waiting for tool call: %s\n", waiting_tool);
99+
100+
// Handle choice if needed
101+
if (strstr(waiting_choice, "true") != NULL) {
102+
printf("Handling choice with option 0...\n");
103+
char* choice_result = quill_handle_choice(interp_id, 0);
104+
printf("Choice result: %s\n", choice_result);
105+
quill_free_string(choice_result);
106+
}
107+
108+
// Handle tool call if needed
109+
if (strstr(waiting_tool, "true") != NULL) {
110+
printf("Handling tool call with mock result...\n");
111+
char* mock_result = get_mock_tool_result(step_result);
112+
printf("Using mock result: %s\n", mock_result);
113+
char* tool_result = quill_handle_tool_call_response(interp_id, mock_result);
114+
printf("Tool call result: %s\n", tool_result);
115+
quill_free_string(tool_result);
116+
}
80117

81-
// For demo purposes, always choose option 0 if waiting
82-
// In real usage, you'd parse the JSON to check the boolean value
83-
char* choice_result = quill_handle_choice(interp_id, 0);
84-
printf("Choice result: %s\n", choice_result);
85-
quill_free_string(choice_result);
86-
quill_free_string(waiting);
118+
quill_free_string(waiting_choice);
119+
quill_free_string(waiting_tool);
87120

88121
// Check if ended
89122
char* ended = quill_is_ended(interp_id);
90123
printf("Is ended: %s\n", ended);
124+
125+
// Break if ended
126+
if (strstr(ended, "true") != NULL) {
127+
quill_free_string(ended);
128+
quill_free_string(step_result);
129+
break;
130+
}
131+
91132
quill_free_string(ended);
133+
quill_free_string(step_result);
92134
}
93135

94136
// Clean up
95137
quill_free_interpreter(interp_id);
96138

97-
printf("\n=== Test Complete ===\n");
139+
printf("\n=== Tool Call Test Complete ===\n");
98140
return 0;
99141
}

internal/jsonapi/jsonapi.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,8 @@ func (qi *QuillInterpreter) GetState() string {
116116
stateStr = "ready"
117117
case interpreter.StateWaitingForChoice:
118118
stateStr = "waiting_for_choice"
119+
case interpreter.StateWaitingForToolCall:
120+
stateStr = "waiting_for_tool_call"
119121
case interpreter.StateEnded:
120122
stateStr = "ended"
121123
case interpreter.StateError:
@@ -176,6 +178,53 @@ func (qi *QuillInterpreter) IsWaitingForChoice() string {
176178
return string(jsonBytes)
177179
}
178180

181+
// IsWaitingForToolCall returns whether the interpreter is waiting for tool call response as JSON
182+
func (qi *QuillInterpreter) IsWaitingForToolCall() string {
183+
if qi.interpreter == nil {
184+
result := JSONResult{
185+
Success: false,
186+
Error: "Interpreter not initialized",
187+
}
188+
jsonBytes, _ := json.Marshal(result)
189+
return string(jsonBytes)
190+
}
191+
192+
result := JSONResult{
193+
Success: true,
194+
Type: "waiting_for_tool_call_status",
195+
Data: qi.interpreter.IsWaitingForToolCall(),
196+
}
197+
198+
jsonBytes, _ := json.Marshal(result)
199+
return string(jsonBytes)
200+
}
201+
202+
// HandleToolCallResponse handles tool call response and returns JSON
203+
func (qi *QuillInterpreter) HandleToolCallResponse(result interface{}) string {
204+
if qi.interpreter == nil {
205+
result := JSONResult{
206+
Success: false,
207+
Error: "Interpreter not initialized",
208+
}
209+
jsonBytes, _ := json.Marshal(result)
210+
return string(jsonBytes)
211+
}
212+
213+
interpResult := qi.interpreter.HandleToolCallResponse(result)
214+
if interpResult == nil {
215+
// Tool call completed successfully, return success without additional data
216+
successResult := JSONResult{
217+
Success: true,
218+
Type: "tool_call_completed",
219+
Data: nil,
220+
}
221+
jsonBytes, _ := json.Marshal(successResult)
222+
return string(jsonBytes)
223+
}
224+
225+
return qi.convertResultToJSON(interpResult)
226+
}
227+
179228
// convertResultToJSON converts interpreter results to JSON format
180229
func (qi *QuillInterpreter) convertResultToJSON(interpResult *interpreter.InterpreterResult) string {
181230
if interpResult == nil {
@@ -195,6 +244,8 @@ func (qi *QuillInterpreter) convertResultToJSON(interpResult *interpreter.Interp
195244
resultType = "dialog"
196245
case interpreter.ChoiceResult:
197246
resultType = "choice"
247+
case interpreter.ToolCallResult:
248+
resultType = "tool_call"
198249
case interpreter.EndResult:
199250
resultType = "end"
200251
case interpreter.ErrorResult:

0 commit comments

Comments
 (0)