Skip to content

Commit dfa3f73

Browse files
committed
Merge branch 'feat/tool-calls'
2 parents a462cef + 958a0f4 commit dfa3f73

File tree

11 files changed

+885
-85
lines changed

11 files changed

+885
-85
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))

cmd/quill/main.go

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,36 @@ func runInterpreter(interp *interpreter.Interpreter) {
180180
break
181181
}
182182

183+
case interpreter.ToolCallResult:
184+
data := result.Data.(interpreter.ToolCallData)
185+
fmt.Printf("\n--- Tool Call: %s ---\n", data.Function)
186+
fmt.Printf("Arguments: %v\n", data.Arguments)
187+
188+
// Mock the external system response
189+
mockResult := mockToolCall(data.Function, data.Arguments)
190+
fmt.Printf("Mock result: %v\n", mockResult)
191+
192+
// Send the result back to the interpreter
193+
toolResult := interp.HandleToolCallResponse(mockResult)
194+
if toolResult != nil {
195+
if toolResult.Type == interpreter.ErrorResult {
196+
errorData := toolResult.Data.(interpreter.ErrorData)
197+
fmt.Fprintf(os.Stderr, "Error: %s\n", errorData.Message)
198+
return
199+
}
200+
201+
// Handle other result types if needed
202+
if toolResult.Type == interpreter.DialogResult {
203+
dialogData := toolResult.Data.(interpreter.DialogData)
204+
fmt.Printf("%s: %s", dialogData.Character, dialogData.Text)
205+
if len(dialogData.Tags) > 0 {
206+
fmt.Printf(" [%s]", strings.Join(dialogData.Tags, ", "))
207+
}
208+
fmt.Println()
209+
}
210+
}
211+
// If toolResult is nil, the LET statement completed and execution will continue in next loop iteration
212+
183213
case interpreter.EndResult:
184214
fmt.Println("\n--- End of script ---")
185215
return
@@ -195,3 +225,60 @@ func runInterpreter(interp *interpreter.Interpreter) {
195225
}
196226
}
197227
}
228+
229+
func mockToolCall(functionName string, args []interface{}) interface{} {
230+
// Mock implementation of tool calls for testing
231+
switch functionName {
232+
case "getPlayerName":
233+
return "Player"
234+
235+
case "getPlayerAge":
236+
return int64(25)
237+
238+
case "getData":
239+
if len(args) > 0 {
240+
key := fmt.Sprintf("%v", args[0])
241+
switch key {
242+
case "gold":
243+
return int64(100)
244+
case "health":
245+
return int64(80)
246+
default:
247+
return "Unknown"
248+
}
249+
}
250+
return "No data"
251+
252+
case "getItemPrice":
253+
if len(args) >= 2 {
254+
itemType := fmt.Sprintf("%v", args[0])
255+
level := int64(1)
256+
if levelArg, ok := args[1].(int64); ok {
257+
level = levelArg
258+
}
259+
260+
basePrice := int64(10)
261+
if itemType == "potion" {
262+
basePrice = 5
263+
} else if itemType == "weapon" {
264+
basePrice = 50
265+
} else if itemType == "armor" {
266+
basePrice = 30
267+
}
268+
269+
return basePrice * level
270+
}
271+
return int64(0)
272+
273+
case "agePlusFive":
274+
if len(args) > 0 {
275+
if age, ok := args[0].(int64); ok {
276+
return age + 5
277+
}
278+
}
279+
return int64(5)
280+
281+
default:
282+
return "Unknown function: " + functionName
283+
}
284+
}

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
}

examples/tool.q

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Simple tool call that retrieves the player's name with fallback
2+
LET player_name = <getPlayerName;>
3+
LET player_age = <getPlayerAge;> ?? 18
4+
LET age_plus_five = <agePlusFive; player_age>
5+
6+
SYSTEM: "Hello, {player_name}! You are {player_age} years old."
7+
IF player_age < 18 {
8+
SYSTEM: "You are quite young to be here!"
9+
}
10+
11+
IF player_age >= 18 {
12+
SYSTEM: "You are old enough to be here."
13+
}
14+
15+
# Advanced inline tool call. Syntax: <function; argument>
16+
SYSTEM: "Your current gold balance is <getData; "gold"> gold coins."
17+
18+
# Multiple tool calls in a single line
19+
SYSTEM: "Your current gold balance is <getData; "gold"> gold coins and your health is <getData; "health">."
20+
21+
# Multiple tool call arguments
22+
# Tool function getItemPrice takes two arguments: item type and item level
23+
LET item_price = <getItemPrice; "potion", 4>
24+
SYSTEM: "The price of a level 4 potion is {item_price} gold coins."

internal/ast/expressions.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,3 +153,31 @@ func (is *InterpolatedString) String() string {
153153
result += "\""
154154
return result
155155
}
156+
157+
// Tool Call Expression (for <function; arg1, arg2>)
158+
type ToolCall struct {
159+
Token token.Token // the '<' token
160+
Function string // function name
161+
Arguments []Expression // arguments to the function
162+
}
163+
164+
func (tc *ToolCall) expressionNode() {}
165+
func (tc *ToolCall) String() string {
166+
if tc == nil {
167+
return "<nil ToolCall>"
168+
}
169+
result := "<" + tc.Function
170+
if len(tc.Arguments) > 0 {
171+
result += ";"
172+
for i, arg := range tc.Arguments {
173+
if i > 0 {
174+
result += ", "
175+
}
176+
if arg != nil {
177+
result += arg.String()
178+
}
179+
}
180+
}
181+
result += ">"
182+
return result
183+
}

0 commit comments

Comments
 (0)