OCaml Claude SDK using Eio and Jsont
1(*---------------------------------------------------------------------------
2 Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>. All rights reserved.
3 SPDX-License-Identifier: ISC
4 ---------------------------------------------------------------------------*)
5
6(* Example demonstrating structured output with JSON Schema *)
7
8module C = Claude
9
10let () =
11 (* Configure logging to see what's happening *)
12 Logs.set_reporter (Logs_fmt.reporter ());
13 Logs.set_level (Some Logs.Info);
14 Logs.Src.set_level C.Message.src (Some Logs.Debug)
15
16let run_codebase_analysis env =
17 Printf.printf "\n=== Codebase Analysis with Structured Output ===\n\n";
18
19 (* Define the JSON Schema for our expected output structure *)
20 let analysis_schema =
21 let open Jsont in
22 Object
23 ( [
24 (("type", Meta.none), String ("object", Meta.none));
25 ( ("properties", Meta.none),
26 Object
27 ( [
28 ( ("file_count", Meta.none),
29 Object
30 ( [
31 (("type", Meta.none), String ("integer", Meta.none));
32 ( ("description", Meta.none),
33 String ("Total number of files analyzed", Meta.none)
34 );
35 ],
36 Meta.none ) );
37 ( ("has_tests", Meta.none),
38 Object
39 ( [
40 (("type", Meta.none), String ("boolean", Meta.none));
41 ( ("description", Meta.none),
42 String
43 ("Whether the codebase has test files", Meta.none)
44 );
45 ],
46 Meta.none ) );
47 ( ("primary_language", Meta.none),
48 Object
49 ( [
50 (("type", Meta.none), String ("string", Meta.none));
51 ( ("description", Meta.none),
52 String
53 ( "The primary programming language used",
54 Meta.none ) );
55 ],
56 Meta.none ) );
57 ( ("complexity_rating", Meta.none),
58 Object
59 ( [
60 (("type", Meta.none), String ("string", Meta.none));
61 ( ("enum", Meta.none),
62 Array
63 ( [
64 String ("low", Meta.none);
65 String ("medium", Meta.none);
66 String ("high", Meta.none);
67 ],
68 Meta.none ) );
69 ( ("description", Meta.none),
70 String ("Overall complexity rating", Meta.none) );
71 ],
72 Meta.none ) );
73 ( ("key_findings", Meta.none),
74 Object
75 ( [
76 (("type", Meta.none), String ("array", Meta.none));
77 ( ("items", Meta.none),
78 Object
79 ( [
80 ( ("type", Meta.none),
81 String ("string", Meta.none) );
82 ],
83 Meta.none ) );
84 ( ("description", Meta.none),
85 String
86 ( "List of key findings from the analysis",
87 Meta.none ) );
88 ],
89 Meta.none ) );
90 ],
91 Meta.none ) );
92 ( ("required", Meta.none),
93 Array
94 ( [
95 String ("file_count", Meta.none);
96 String ("has_tests", Meta.none);
97 String ("primary_language", Meta.none);
98 String ("complexity_rating", Meta.none);
99 String ("key_findings", Meta.none);
100 ],
101 Meta.none ) );
102 (("additionalProperties", Meta.none), Bool (false, Meta.none));
103 ],
104 Meta.none )
105 in
106
107 (* Create structured output format from the schema *)
108 let output_format =
109 Claude.Proto.Structured_output.of_json_schema analysis_schema
110 in
111
112 (* Configure Claude with structured output *)
113 let options =
114 C.Options.default
115 |> C.Options.with_output_format output_format
116 |> C.Options.with_allowed_tools [ "Read"; "Glob"; "Grep" ]
117 |> C.Options.with_system_prompt
118 "You are a code analysis assistant. Analyze codebases and provide \
119 structured output matching the given JSON Schema."
120 in
121
122 Printf.printf "Structured output format configured\n";
123 Printf.printf "Schema: %s\n\n"
124 (Test_json_utils.to_string ~minify:false analysis_schema);
125
126 (* Create Claude client and query *)
127 Eio.Switch.run @@ fun sw ->
128 let process_mgr = Eio.Stdenv.process_mgr env in
129 let clock = Eio.Stdenv.clock env in
130 let client = C.Client.create ~sw ~process_mgr ~clock ~options () in
131
132 let prompt =
133 "Please analyze the current codebase structure. Look at the files, \
134 identify the primary language, count files, check for tests, assess \
135 complexity, and provide key findings. Return your analysis in the \
136 structured JSON format I specified."
137 in
138
139 Printf.printf "Sending query: %s\n\n" prompt;
140 C.Client.query client prompt;
141
142 (* Process responses *)
143 let responses = C.Client.receive client in
144 Seq.iter
145 (function
146 | C.Response.Text text ->
147 Printf.printf "\nAssistant text:\n";
148 Printf.printf " %s\n" (C.Response.Text.content text)
149 | C.Response.Tool_use tool ->
150 Printf.printf " Using tool: %s\n" (C.Response.Tool_use.name tool)
151 | C.Response.Complete result -> (
152 Printf.printf "\n=== Result ===\n";
153 Printf.printf "Duration: %dms\n"
154 (C.Response.Complete.duration_ms result);
155 Printf.printf "Cost: $%.4f\n"
156 (Option.value
157 (C.Response.Complete.total_cost_usd result)
158 ~default:0.0);
159
160 (* Extract and display structured output *)
161 match C.Response.Complete.structured_output result with
162 | Some output ->
163 Printf.printf "\n=== Structured Output ===\n";
164 Printf.printf "%s\n\n"
165 (Test_json_utils.to_string ~minify:false output);
166
167 (* Parse the structured output *)
168 let file_count =
169 Test_json_utils.get_int output "file_count"
170 |> Option.value ~default:0
171 in
172 let has_tests =
173 Test_json_utils.get_bool output "has_tests"
174 |> Option.value ~default:false
175 in
176 let language =
177 Test_json_utils.get_string output "primary_language"
178 |> Option.value ~default:"unknown"
179 in
180 let complexity =
181 Test_json_utils.get_string output "complexity_rating"
182 |> Option.value ~default:"unknown"
183 in
184 let findings =
185 match Test_json_utils.get_array output "key_findings" with
186 | Some items ->
187 List.filter_map
188 (fun json -> Test_json_utils.as_string json)
189 items
190 | None -> []
191 in
192
193 Printf.printf "=== Parsed Analysis ===\n";
194 Printf.printf "File Count: %d\n" file_count;
195 Printf.printf "Has Tests: %b\n" has_tests;
196 Printf.printf "Primary Language: %s\n" language;
197 Printf.printf "Complexity: %s\n" complexity;
198 Printf.printf "Key Findings:\n";
199 List.iter
200 (fun finding -> Printf.printf " - %s\n" finding)
201 findings
202 | None -> (
203 Printf.printf "No structured output received\n";
204 match C.Response.Complete.result_text result with
205 | Some text -> Printf.printf "Text result: %s\n" text
206 | None -> ()))
207 | C.Response.Init _ -> Printf.printf "Session initialized\n"
208 | C.Response.Error err ->
209 Printf.printf "Error: %s\n" (C.Response.Error.message err)
210 | _ -> ())
211 responses;
212
213 Printf.printf "\nDone!\n"
214
215let () =
216 Eio_main.run @@ fun env ->
217 try run_codebase_analysis env with
218 | C.Transport.CLI_not_found msg ->
219 Printf.eprintf "Error: Claude CLI not found\n%s\n" msg;
220 Printf.eprintf "Make sure 'claude' is installed and in your PATH\n";
221 exit 1
222 | C.Transport.Connection_error msg ->
223 Printf.eprintf "Connection error: %s\n" msg;
224 exit 1
225 | exn ->
226 Printf.eprintf "Unexpected error: %s\n" (Printexc.to_string exn);
227 Printexc.print_backtrace stderr;
228 exit 1