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.3.tbz
sha256=0cc0a957784c295877b6bcc4f80c0dba0f6507d3c282e7d6cbd71038050376de
sha512=f8e082376b4de194a877d0df193892cdaa8139a4789f51eebac651acf8b6d4064b95242e158d86c1a32776d99130d43ed4c1119e2d54e738e93bdba2a6ad9a33
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 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225let 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 (* Named so a test can exercise the partial-effort wording: no catalog model has a partial effort list today, so driving this through [create] cannot reach it. *) let unsupported_effort_message ~model ~effort ~accepted = Printf.sprintf "%s does not support effort '%s'; %s" model (Effort.to_string effort) (match accepted with | [] -> "this model accepts no effort levels" | levels -> Printf.sprintf "this model accepts %s" (String.concat ", " (List.map Effort.to_string levels))) 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 -> Printf.ksprintf invalid_arg "%s does not accept manual thinking budgets (Thinking.Enabled). The API rejects them with a 400 on this model. \ Use Thinking.Adaptive together with Anthropic_options.effort instead: adaptive thinking with an effort level \ replaces manual token budgets. Pick the effort level yourself, since no faithful mapping from a token budget to \ an effort level exists." model | Some thinking, Some (Thinking.Adaptive _) when not thinking.adaptive -> Printf.ksprintf invalid_arg "%s does not support adaptive thinking (Thinking.Adaptive). This is a pre-adaptive model: use Thinking.Enabled \ with an explicit token budget, or omit thinking entirely." model | Some { disabled = Model_catalog.Unsupported; _ }, Some Thinking.Disabled -> Printf.ksprintf invalid_arg "%s cannot turn thinking off (Thinking.Disabled). This model always thinks; control how much it thinks with \ Anthropic_options.effort instead." 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.exists (Effort.equal effort) thinking.effort_levels) -> invalid_arg (unsupported_effort_message ~model ~effort ~accepted:thinking.effort_levels) | 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 (Thinking.Enabled) cannot be combined with a forced tool choice (Tool_choice.Required or \ Tool_choice.Specific). Use Tool_choice.Auto, or drop the manual thinking budget." | _ -> () 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)"
>