OCaml Claude SDK using Eio and Jsont
at main 228 lines 9.1 kB view raw
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