package ocaml-ai-sdk
sectionYPositions = computeSectionYPositions($el), 10)"
x-init="setTimeout(() => sectionYPositions = computeSectionYPositions($el), 10)"
>
OCaml AI SDK - Provider abstraction for AI models
Install
dune-project
Dependency
Authors
Maintainers
Sources
ocaml-ai-sdk-0.6.1.tbz
sha256=cb3b82428abcda76c02ec92f8103a4db28edf3f42795794a37135e9a3c40af96
sha512=11be8889ee25bee67b2be00553c42a4692bc73e5ce23985e8bb87f8a07e9d8f556b5a63b219fe5fe30366c8b0d4edae494afa80ccfbfcb112f1fa1e458de55bf
doc/src/ocaml-ai-sdk.ai_provider_anthropic/anthropic_model.ml.html
Source file anthropic_model.ml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204let warning feature details = Ai_provider.Warning.Unsupported_feature { feature; details } let is_known_model = function | Model_catalog.Custom _ -> false | _ -> true let effective_thinking_active ~capabilities ~thinking = match thinking with | Some (Thinking.Enabled _ | Thinking.Adaptive _) -> true | Some Thinking.Disabled -> false | None -> match capabilities.Model_catalog.thinking with | Some { defaults_to_adaptive; _ } -> defaults_to_adaptive | None -> false let validate_options ~model ~capabilities ~anthropic_opts ~tool_choice ~forced_tool_choice = (match capabilities.Model_catalog.thinking, anthropic_opts.Anthropic_options.thinking with | Some thinking, Some (Thinking.Enabled _) when not thinking.manual -> invalid_arg (Printf.sprintf "%s does not support manual thinking" model) | Some thinking, Some (Thinking.Adaptive _) when not thinking.adaptive -> invalid_arg (Printf.sprintf "%s does not support adaptive thinking" model) | Some { disabled = Model_catalog.Unsupported; _ }, Some Thinking.Disabled -> invalid_arg (Printf.sprintf "%s does not support disabled thinking" model) | None, _ | _, None | Some _, Some Thinking.Disabled -> () | Some _, Some (Thinking.Enabled _ | Thinking.Adaptive _) -> ()); (match capabilities.Model_catalog.thinking, anthropic_opts.Anthropic_options.effort with | Some thinking, Some effort when not (List.mem effort thinking.effort_levels) -> invalid_arg (Printf.sprintf "%s does not support effort '%s'" model (Effort.to_string effort)) | None, _ | Some _, None | Some _, Some _ -> ()); match anthropic_opts.Anthropic_options.thinking with | Some (Thinking.Enabled _) when (match tool_choice with | Some (Ai_provider.Tool_choice.Required | Ai_provider.Tool_choice.Specific _) -> true | None | Some (Ai_provider.Tool_choice.Auto | Ai_provider.Tool_choice.None_) -> false) || Option.is_some forced_tool_choice -> invalid_arg "manual thinking cannot be combined with forced tool choice" | _ -> () let normalize_sampling ~model ~known_model ~capabilities ~thinking_active (opts : Ai_provider.Call_options.t) = let base_warnings = List.concat [ (match opts.frequency_penalty with | Some _ -> [ warning "frequency_penalty" None ] | None -> []); (match opts.presence_penalty with | Some _ -> [ warning "presence_penalty" None ] | None -> []); (match opts.seed with | Some _ -> [ warning "seed" None ] | None -> []); ] in let drop field details = function | Some _ -> None, [ warning field (Some details) ] | None -> None, [] in let rejects_sampling = capabilities.Model_catalog.rejects_sampling_parameters in let model_restriction = Printf.sprintf "not supported by %s and will be ignored" model in let thinking_restriction = "not supported when thinking is enabled" in let temperature, temperature_warnings = if rejects_sampling then drop "temperature" model_restriction opts.temperature else if thinking_active then drop "temperature" thinking_restriction opts.temperature else opts.temperature, [] in let top_p, top_p_warnings = if rejects_sampling then drop "top_p" model_restriction opts.top_p else ( match thinking_active, opts.top_p with | true, Some value when value < 0.95 || value > 1. -> drop "top_p" "must be between 0.95 and 1 when thinking is enabled" opts.top_p | _ -> opts.top_p, []) in let top_k, top_k_warnings = if rejects_sampling then drop "top_k" model_restriction opts.top_k else if thinking_active then drop "top_k" thinking_restriction opts.top_k else opts.top_k, [] in let top_p, top_p_warning = match known_model, temperature, top_p with | true, Some _, Some _ -> None, [ warning "top_p" (Some "top_p is not supported when temperature is set. top_p is ignored.") ] | _, _, _ -> top_p, [] in temperature, top_p, top_k, base_warnings @ temperature_warnings @ top_p_warnings @ top_k_warnings @ top_p_warning let normalize_disabled_effort ~model ~capabilities ~anthropic_opts = match capabilities.Model_catalog.thinking, anthropic_opts.Anthropic_options.thinking, anthropic_opts.effort with | Some { disabled = Model_catalog.Up_to_high; _ }, Some Thinking.Disabled, Some (Effort.Xhigh | Effort.Max) -> ( Some Effort.High, [ warning "providerOptions.anthropic.effort" (Some (Printf.sprintf "effort is not supported by %s when thinking is disabled; lowered to 'high'" model)); ] ) | _, _, effort -> effort, [] (** Prepare the request body and warnings — shared by generate and stream. *) let prepare_request ~model ~stream (opts : Ai_provider.Call_options.t) = let known_model = Model_catalog.of_model_id model in let capabilities = Model_catalog.capabilities known_model in let known_model = is_known_model known_model in let anthropic_opts = Anthropic_options.of_provider_options opts.provider_options |> Stdlib.Option.value ~default:Anthropic_options.default in let thinking_active = effective_thinking_active ~capabilities ~thinking:anthropic_opts.thinking in let temperature, top_p, top_k, warnings = normalize_sampling ~model ~known_model ~capabilities ~thinking_active opts in let effort, effort_warnings = normalize_disabled_effort ~model ~capabilities ~anthropic_opts in let anthropic_opts = { anthropic_opts with effort } in let warnings = warnings @ effort_warnings in (* One validator per request — shared across system, tools, and messages so the 4-breakpoint limit covers them all. Upstream order matches this: system blocks, then tools, then messages. *) let validator = Cache_control_validator.create () in let system_parts, remaining = Convert_prompt.extract_system opts.prompt in let system = Convert_prompt.system_to_json ~validator system_parts in (* Route Object_json per-model: native output_config where supported, synthetic [json] tool with forced tool_choice otherwise — matches upstream @ai-sdk/anthropic. *) let supports_native_structured_output = capabilities.supports_structured_output in let output_format, fallback_tool, forced_tool_choice, extra_warnings = match opts.mode with | Regular | Object_tool _ -> None, None, None, [] | Object_json (Some { name = _; schema }) when supports_native_structured_output -> Some Anthropic_api.{ type_ = "json_schema"; schema }, None, None, [] | Object_json (Some { name = _; schema }) -> let tool = Convert_tools.json_response_tool ~schema in None, Some tool, Some Convert_tools.forced_json_tool_choice, [] | Object_json None -> ( None, None, None, [ Ai_provider.Warning.Unsupported_feature { feature = "response_format without schema"; details = Some "Anthropic structured outputs require a JSON schema; sending request without enforcement"; }; ] ) in validate_options ~model ~capabilities ~anthropic_opts ~tool_choice:opts.tool_choice ~forced_tool_choice; let output_config = match output_format, anthropic_opts.effort with | None, None -> None | format, effort -> Some Anthropic_api.{ format; effort = Option.map Effort.to_string effort } in let warnings = warnings @ extra_warnings in let base_tools, base_tool_choice = Convert_tools.convert_tools ~validator ~tools:opts.tools ~tool_choice:opts.tool_choice () in let messages = Convert_prompt.convert_messages ~validator remaining in let tools = Option.fold ~none:base_tools ~some:(fun t -> base_tools @ [ t ]) fallback_tool in (* When structured-output fallback is active, override the caller's tool_choice. *) let tool_choice = match forced_tool_choice with | Some _ -> forced_tool_choice | None -> base_tool_choice in (* Use model-aware default for max_tokens *) let max_tokens = Some (match opts.max_output_tokens with | Some n -> n | None -> Model_catalog.default_max_tokens (Model_catalog.of_model_id model)) in let body = Anthropic_api.make_request_body ~model ~messages ?system ~tools ?tool_choice ?max_tokens ?temperature ?top_p ?top_k ~stop_sequences:opts.stop_sequences ?thinking:anthropic_opts.thinking ?output_config ~stream () in (* Merge user headers with required beta headers — the result includes all of opts.headers plus a merged anthropic-beta header, so it replaces opts.headers entirely *) let required_betas = Beta_headers.required_betas ~thinking:anthropic_opts.thinking ~has_pdf:false ~tool_streaming:anthropic_opts.tool_streaming in let extra_headers = Beta_headers.merge_beta_headers ~user_headers:opts.headers ~required:required_betas in let warnings = warnings @ Cache_control_validator.warnings validator in body, warnings, extra_headers let create ~config ~model = let module M = struct let specification_version = "V3" let provider = "anthropic" let model_id = model let generate opts = let body, warnings, extra_headers = prepare_request ~model ~stream:false opts in match%lwt Anthropic_api.messages ~config ~body ~extra_headers ~stream:false with | `Json json -> let result = Convert_response.parse_response json in Lwt.return { result with warnings = warnings @ result.warnings } | `Stream _ -> Lwt.fail_with "unexpected streaming response for non-streaming request" let stream opts = let body, warnings, extra_headers = prepare_request ~model ~stream:true opts in match%lwt Anthropic_api.messages ~config ~body ~extra_headers ~stream:true with | `Stream line_stream -> let sse_events = Sse.parse_events line_stream in let parts = Convert_stream.transform sse_events ~warnings in Lwt.return { Ai_provider.Stream_result.stream = parts; warnings; raw_response = None } | `Json _ -> Lwt.fail_with "unexpected non-streaming response for streaming request" end in (module M : Ai_provider.Language_model.S)
sectionYPositions = computeSectionYPositions($el), 10)"
x-init="setTimeout(() => sectionYPositions = computeSectionYPositions($el), 10)"
>