coco-framework 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (400) hide show
  1. coco/__init__.py +3 -0
  2. coco/agents/__init__.py +9 -0
  3. coco/agents/_subagent_transformer.py +307 -0
  4. coco/agents/factory.py +2164 -0
  5. coco/agents/middleware/__init__.py +100 -0
  6. coco/agents/middleware/_execution.py +385 -0
  7. coco/agents/middleware/_redaction.py +454 -0
  8. coco/agents/middleware/_retry.py +134 -0
  9. coco/agents/middleware/_trace_policy.py +61 -0
  10. coco/agents/middleware/context_editing.py +306 -0
  11. coco/agents/middleware/file_search.py +433 -0
  12. coco/agents/middleware/human_in_the_loop.py +680 -0
  13. coco/agents/middleware/internal_call_transformer.py +108 -0
  14. coco/agents/middleware/model_call_limit.py +267 -0
  15. coco/agents/middleware/model_fallback.py +417 -0
  16. coco/agents/middleware/model_retry.py +327 -0
  17. coco/agents/middleware/pii.py +878 -0
  18. coco/agents/middleware/provider_tool_search.py +307 -0
  19. coco/agents/middleware/shell_tool.py +949 -0
  20. coco/agents/middleware/summarization.py +910 -0
  21. coco/agents/middleware/todo.py +357 -0
  22. coco/agents/middleware/tool_call_limit.py +495 -0
  23. coco/agents/middleware/tool_emulator.py +251 -0
  24. coco/agents/middleware/tool_error.py +209 -0
  25. coco/agents/middleware/tool_retry.py +421 -0
  26. coco/agents/middleware/tool_selection.py +515 -0
  27. coco/agents/middleware/types.py +2201 -0
  28. coco/agents/structured_output.py +472 -0
  29. coco/chat_models/__init__.py +7 -0
  30. coco/chat_models/base.py +1082 -0
  31. coco/embeddings/__init__.py +18 -0
  32. coco/embeddings/base.py +279 -0
  33. coco/mcp/__init__.py +36 -0
  34. coco/mcp/adapter.py +245 -0
  35. coco/mcp/elicitation.py +394 -0
  36. coco/mcp/tools.py +307 -0
  37. coco/messages/__init__.py +73 -0
  38. coco/py.typed +0 -0
  39. coco/rate_limiters/__init__.py +13 -0
  40. coco/tools/__init__.py +22 -0
  41. coco/tools/tool_node.py +20 -0
  42. coco_anthropic/__init__.py +15 -0
  43. coco_anthropic/_client_utils.py +81 -0
  44. coco_anthropic/_compat.py +228 -0
  45. coco_anthropic/_sdk_compat.py +124 -0
  46. coco_anthropic/_version.py +3 -0
  47. coco_anthropic/chat_models.py +3349 -0
  48. coco_anthropic/data/__init__.py +1 -0
  49. coco_anthropic/data/_profiles.py +491 -0
  50. coco_anthropic/data/profile_augmentations.toml +69 -0
  51. coco_anthropic/experimental.py +140 -0
  52. coco_anthropic/llms.py +428 -0
  53. coco_anthropic/middleware/__init__.py +25 -0
  54. coco_anthropic/middleware/anthropic_tools.py +1197 -0
  55. coco_anthropic/middleware/bash.py +84 -0
  56. coco_anthropic/middleware/file_search.py +360 -0
  57. coco_anthropic/middleware/prompt_caching.py +276 -0
  58. coco_anthropic/output_parsers.py +93 -0
  59. coco_anthropic/py.typed +0 -0
  60. coco_core/__init__.py +20 -0
  61. coco_core/_api/__init__.py +87 -0
  62. coco_core/_api/beta_decorator.py +266 -0
  63. coco_core/_api/deprecation.py +637 -0
  64. coco_core/_api/internal.py +23 -0
  65. coco_core/_api/path.py +50 -0
  66. coco_core/_import_utils.py +41 -0
  67. coco_core/_protocol.py +625 -0
  68. coco_core/_security/__init__.py +36 -0
  69. coco_core/_security/_exceptions.py +9 -0
  70. coco_core/_security/_policy.py +313 -0
  71. coco_core/_security/_ssrf_protection.py +155 -0
  72. coco_core/_security/_transport.py +254 -0
  73. coco_core/agents.py +258 -0
  74. coco_core/caches.py +272 -0
  75. coco_core/callbacks/__init__.py +132 -0
  76. coco_core/callbacks/base.py +1229 -0
  77. coco_core/callbacks/file.py +266 -0
  78. coco_core/callbacks/manager.py +2826 -0
  79. coco_core/callbacks/stdout.py +123 -0
  80. coco_core/callbacks/streaming_stdout.py +154 -0
  81. coco_core/callbacks/usage.py +124 -0
  82. coco_core/chat_history.py +267 -0
  83. coco_core/chat_loaders.py +26 -0
  84. coco_core/chat_sessions.py +19 -0
  85. coco_core/cross_encoders.py +18 -0
  86. coco_core/document_loaders/__init__.py +36 -0
  87. coco_core/document_loaders/base.py +155 -0
  88. coco_core/document_loaders/blob_loaders.py +38 -0
  89. coco_core/documents/__init__.py +55 -0
  90. coco_core/documents/base.py +347 -0
  91. coco_core/documents/compressor.py +74 -0
  92. coco_core/documents/transformers.py +79 -0
  93. coco_core/embeddings/__init__.py +31 -0
  94. coco_core/embeddings/embeddings.py +78 -0
  95. coco_core/embeddings/fake.py +129 -0
  96. coco_core/env.py +22 -0
  97. coco_core/example_selectors/__init__.py +47 -0
  98. coco_core/example_selectors/base.py +60 -0
  99. coco_core/example_selectors/length_based.py +131 -0
  100. coco_core/example_selectors/semantic_similarity.py +366 -0
  101. coco_core/exceptions.py +166 -0
  102. coco_core/globals.py +72 -0
  103. coco_core/indexing/__init__.py +53 -0
  104. coco_core/indexing/api.py +954 -0
  105. coco_core/indexing/base.py +661 -0
  106. coco_core/indexing/in_memory.py +104 -0
  107. coco_core/language_models/__init__.py +116 -0
  108. coco_core/language_models/_compat_bridge.py +844 -0
  109. coco_core/language_models/_utils.py +343 -0
  110. coco_core/language_models/base.py +500 -0
  111. coco_core/language_models/chat_model_stream.py +1525 -0
  112. coco_core/language_models/chat_models.py +2748 -0
  113. coco_core/language_models/fake.py +137 -0
  114. coco_core/language_models/fake_chat_models.py +396 -0
  115. coco_core/language_models/llms.py +1569 -0
  116. coco_core/language_models/model_profile.py +183 -0
  117. coco_core/load/__init__.py +44 -0
  118. coco_core/load/_validation.py +191 -0
  119. coco_core/load/dump.py +120 -0
  120. coco_core/load/load.py +828 -0
  121. coco_core/load/mapping.py +1097 -0
  122. coco_core/load/serializable.py +406 -0
  123. coco_core/messages/__init__.py +198 -0
  124. coco_core/messages/ai.py +854 -0
  125. coco_core/messages/base.py +518 -0
  126. coco_core/messages/block_translators/__init__.py +112 -0
  127. coco_core/messages/block_translators/anthropic.py +534 -0
  128. coco_core/messages/block_translators/bedrock.py +108 -0
  129. coco_core/messages/block_translators/bedrock_converse.py +329 -0
  130. coco_core/messages/block_translators/coco_v0.py +303 -0
  131. coco_core/messages/block_translators/google_genai.py +610 -0
  132. coco_core/messages/block_translators/google_vertexai.py +21 -0
  133. coco_core/messages/block_translators/groq.py +157 -0
  134. coco_core/messages/block_translators/openai.py +1089 -0
  135. coco_core/messages/chat.py +64 -0
  136. coco_core/messages/content.py +1488 -0
  137. coco_core/messages/function.py +62 -0
  138. coco_core/messages/human.py +70 -0
  139. coco_core/messages/modifier.py +33 -0
  140. coco_core/messages/system.py +70 -0
  141. coco_core/messages/tool.py +418 -0
  142. coco_core/messages/utils.py +2442 -0
  143. coco_core/output_parsers/__init__.py +101 -0
  144. coco_core/output_parsers/base.py +361 -0
  145. coco_core/output_parsers/format_instructions.py +16 -0
  146. coco_core/output_parsers/json.py +139 -0
  147. coco_core/output_parsers/list.py +249 -0
  148. coco_core/output_parsers/openai_functions.py +324 -0
  149. coco_core/output_parsers/openai_tools.py +383 -0
  150. coco_core/output_parsers/pydantic.py +141 -0
  151. coco_core/output_parsers/string.py +63 -0
  152. coco_core/output_parsers/transform.py +175 -0
  153. coco_core/output_parsers/xml.py +301 -0
  154. coco_core/outputs/__init__.py +66 -0
  155. coco_core/outputs/chat_generation.py +147 -0
  156. coco_core/outputs/chat_result.py +39 -0
  157. coco_core/outputs/generation.py +80 -0
  158. coco_core/outputs/llm_result.py +112 -0
  159. coco_core/outputs/run_info.py +22 -0
  160. coco_core/prompt_values.py +161 -0
  161. coco_core/prompts/__init__.py +101 -0
  162. coco_core/prompts/base.py +508 -0
  163. coco_core/prompts/chat.py +1495 -0
  164. coco_core/prompts/dict.py +177 -0
  165. coco_core/prompts/few_shot.py +483 -0
  166. coco_core/prompts/few_shot_with_templates.py +239 -0
  167. coco_core/prompts/image.py +177 -0
  168. coco_core/prompts/loading.py +296 -0
  169. coco_core/prompts/message.py +97 -0
  170. coco_core/prompts/prompt.py +312 -0
  171. coco_core/prompts/string.py +399 -0
  172. coco_core/prompts/structured.py +214 -0
  173. coco_core/py.typed +0 -0
  174. coco_core/rate_limiters.py +254 -0
  175. coco_core/retrievers.py +328 -0
  176. coco_core/runnables/__init__.py +136 -0
  177. coco_core/runnables/base.py +6725 -0
  178. coco_core/runnables/branch.py +457 -0
  179. coco_core/runnables/config.py +712 -0
  180. coco_core/runnables/configurable.py +710 -0
  181. coco_core/runnables/fallbacks.py +663 -0
  182. coco_core/runnables/graph.py +741 -0
  183. coco_core/runnables/graph_ascii.py +366 -0
  184. coco_core/runnables/graph_mermaid.py +503 -0
  185. coco_core/runnables/graph_png.py +215 -0
  186. coco_core/runnables/history.py +631 -0
  187. coco_core/runnables/passthrough.py +849 -0
  188. coco_core/runnables/retry.py +379 -0
  189. coco_core/runnables/router.py +239 -0
  190. coco_core/runnables/schema.py +188 -0
  191. coco_core/runnables/utils.py +798 -0
  192. coco_core/stores.py +291 -0
  193. coco_core/structured_query.py +203 -0
  194. coco_core/sys_info.py +154 -0
  195. coco_core/tools/__init__.py +95 -0
  196. coco_core/tools/base.py +1948 -0
  197. coco_core/tools/convert.py +497 -0
  198. coco_core/tools/render.py +67 -0
  199. coco_core/tools/retriever.py +94 -0
  200. coco_core/tools/simple.py +208 -0
  201. coco_core/tools/structured.py +322 -0
  202. coco_core/tracers/__init__.py +47 -0
  203. coco_core/tracers/_compat.py +95 -0
  204. coco_core/tracers/_context.py +337 -0
  205. coco_core/tracers/_streaming.py +53 -0
  206. coco_core/tracers/base.py +955 -0
  207. coco_core/tracers/coco.py +279 -0
  208. coco_core/tracers/context.py +205 -0
  209. coco_core/tracers/core.py +766 -0
  210. coco_core/tracers/event_stream.py +1101 -0
  211. coco_core/tracers/log_stream.py +765 -0
  212. coco_core/tracers/memory_stream.py +200 -0
  213. coco_core/tracers/root_listeners.py +130 -0
  214. coco_core/tracers/run_collector.py +40 -0
  215. coco_core/tracers/schemas.py +14 -0
  216. coco_core/tracers/stdout.py +205 -0
  217. coco_core/utils/__init__.py +111 -0
  218. coco_core/utils/_gateway.py +274 -0
  219. coco_core/utils/_merge.py +218 -0
  220. coco_core/utils/aiter.py +357 -0
  221. coco_core/utils/env.py +86 -0
  222. coco_core/utils/formatting.py +80 -0
  223. coco_core/utils/function_calling.py +869 -0
  224. coco_core/utils/html.py +132 -0
  225. coco_core/utils/image.py +15 -0
  226. coco_core/utils/input.py +82 -0
  227. coco_core/utils/interactive_env.py +12 -0
  228. coco_core/utils/iter.py +229 -0
  229. coco_core/utils/json.py +230 -0
  230. coco_core/utils/json_schema.py +273 -0
  231. coco_core/utils/mustache.py +706 -0
  232. coco_core/utils/pydantic.py +631 -0
  233. coco_core/utils/strings.py +69 -0
  234. coco_core/utils/usage.py +61 -0
  235. coco_core/utils/utils.py +528 -0
  236. coco_core/utils/uuid.py +57 -0
  237. coco_core/vectorstores/__init__.py +53 -0
  238. coco_core/vectorstores/base.py +1111 -0
  239. coco_core/vectorstores/in_memory.py +546 -0
  240. coco_core/vectorstores/utils.py +163 -0
  241. coco_core/version.py +3 -0
  242. coco_framework-0.1.0.dist-info/METADATA +184 -0
  243. coco_framework-0.1.0.dist-info/RECORD +400 -0
  244. coco_framework-0.1.0.dist-info/WHEEL +4 -0
  245. coco_framework-0.1.0.dist-info/licenses/LICENSE +24 -0
  246. coco_framework-0.1.0.dist-info/licenses/NOTICE +15 -0
  247. coco_graph/_internal/__init__.py +4 -0
  248. coco_graph/_internal/_cache.py +31 -0
  249. coco_graph/_internal/_config.py +474 -0
  250. coco_graph/_internal/_constants.py +140 -0
  251. coco_graph/_internal/_fields.py +213 -0
  252. coco_graph/_internal/_future.py +220 -0
  253. coco_graph/_internal/_pydantic.py +275 -0
  254. coco_graph/_internal/_queue.py +124 -0
  255. coco_graph/_internal/_replay.py +90 -0
  256. coco_graph/_internal/_retry.py +29 -0
  257. coco_graph/_internal/_runnable.py +969 -0
  258. coco_graph/_internal/_scratchpad.py +19 -0
  259. coco_graph/_internal/_serde.py +253 -0
  260. coco_graph/_internal/_timeout.py +25 -0
  261. coco_graph/_internal/_typing.py +54 -0
  262. coco_graph/cache/base/__init__.py +48 -0
  263. coco_graph/cache/base/py.typed +0 -0
  264. coco_graph/cache/memory/__init__.py +73 -0
  265. coco_graph/cache/redis/__init__.py +144 -0
  266. coco_graph/cache/sqlite/__init__.py +120 -0
  267. coco_graph/callbacks.py +394 -0
  268. coco_graph/channels/__init__.py +29 -0
  269. coco_graph/channels/any_value.py +72 -0
  270. coco_graph/channels/base.py +121 -0
  271. coco_graph/channels/binop.py +155 -0
  272. coco_graph/channels/delta.py +202 -0
  273. coco_graph/channels/ephemeral_value.py +79 -0
  274. coco_graph/channels/last_value.py +151 -0
  275. coco_graph/channels/named_barrier_value.py +167 -0
  276. coco_graph/channels/topic.py +94 -0
  277. coco_graph/channels/untracked_value.py +73 -0
  278. coco_graph/checkpoint/base/__init__.py +861 -0
  279. coco_graph/checkpoint/base/id.py +109 -0
  280. coco_graph/checkpoint/base/py.typed +0 -0
  281. coco_graph/checkpoint/memory/__init__.py +698 -0
  282. coco_graph/checkpoint/memory/py.typed +0 -0
  283. coco_graph/checkpoint/postgres/__init__.py +602 -0
  284. coco_graph/checkpoint/postgres/_ainternal.py +23 -0
  285. coco_graph/checkpoint/postgres/_internal.py +21 -0
  286. coco_graph/checkpoint/postgres/aio.py +691 -0
  287. coco_graph/checkpoint/postgres/base.py +666 -0
  288. coco_graph/checkpoint/postgres/py.typed +0 -0
  289. coco_graph/checkpoint/postgres/shallow.py +967 -0
  290. coco_graph/checkpoint/serde/__init__.py +0 -0
  291. coco_graph/checkpoint/serde/_msgpack.py +97 -0
  292. coco_graph/checkpoint/serde/base.py +64 -0
  293. coco_graph/checkpoint/serde/encrypted.py +80 -0
  294. coco_graph/checkpoint/serde/event_hooks.py +52 -0
  295. coco_graph/checkpoint/serde/jsonplus.py +883 -0
  296. coco_graph/checkpoint/serde/py.typed +0 -0
  297. coco_graph/checkpoint/serde/types.py +68 -0
  298. coco_graph/checkpoint/sqlite/__init__.py +645 -0
  299. coco_graph/checkpoint/sqlite/_delta.py +172 -0
  300. coco_graph/checkpoint/sqlite/aio.py +738 -0
  301. coco_graph/checkpoint/sqlite/py.typed +0 -0
  302. coco_graph/checkpoint/sqlite/utils.py +116 -0
  303. coco_graph/config.py +196 -0
  304. coco_graph/constants.py +64 -0
  305. coco_graph/errors.py +241 -0
  306. coco_graph/func/__init__.py +620 -0
  307. coco_graph/graph/__init__.py +12 -0
  308. coco_graph/graph/_branch.py +225 -0
  309. coco_graph/graph/_node.py +103 -0
  310. coco_graph/graph/message.py +437 -0
  311. coco_graph/graph/state.py +1978 -0
  312. coco_graph/graph/ui.py +227 -0
  313. coco_graph/managed/__init__.py +3 -0
  314. coco_graph/managed/base.py +31 -0
  315. coco_graph/managed/is_last_step.py +24 -0
  316. coco_graph/prebuilt/__init__.py +23 -0
  317. coco_graph/prebuilt/_tool_call_stream.py +117 -0
  318. coco_graph/prebuilt/_tool_call_transformer.py +165 -0
  319. coco_graph/prebuilt/chat_agent_executor.py +1015 -0
  320. coco_graph/prebuilt/interrupt.py +105 -0
  321. coco_graph/prebuilt/py.typed +0 -0
  322. coco_graph/prebuilt/tool_node.py +2030 -0
  323. coco_graph/prebuilt/tool_validator.py +221 -0
  324. coco_graph/pregel/__init__.py +3 -0
  325. coco_graph/pregel/_algo.py +1460 -0
  326. coco_graph/pregel/_call.py +298 -0
  327. coco_graph/pregel/_checkpoint.py +331 -0
  328. coco_graph/pregel/_config.py +0 -0
  329. coco_graph/pregel/_draw.py +294 -0
  330. coco_graph/pregel/_executor.py +223 -0
  331. coco_graph/pregel/_io.py +174 -0
  332. coco_graph/pregel/_log.py +3 -0
  333. coco_graph/pregel/_loop.py +1988 -0
  334. coco_graph/pregel/_messages.py +461 -0
  335. coco_graph/pregel/_read.py +312 -0
  336. coco_graph/pregel/_retry.py +854 -0
  337. coco_graph/pregel/_runner.py +941 -0
  338. coco_graph/pregel/_tools.py +268 -0
  339. coco_graph/pregel/_utils.py +221 -0
  340. coco_graph/pregel/_validate.py +120 -0
  341. coco_graph/pregel/_write.py +192 -0
  342. coco_graph/pregel/debug.py +302 -0
  343. coco_graph/pregel/main.py +4364 -0
  344. coco_graph/pregel/protocol.py +288 -0
  345. coco_graph/pregel/types.py +38 -0
  346. coco_graph/py.typed +0 -0
  347. coco_graph/runtime.py +350 -0
  348. coco_graph/store/base/__init__.py +1322 -0
  349. coco_graph/store/base/batch.py +371 -0
  350. coco_graph/store/base/embed.py +436 -0
  351. coco_graph/store/base/py.typed +0 -0
  352. coco_graph/store/memory/__init__.py +592 -0
  353. coco_graph/store/memory/py.typed +0 -0
  354. coco_graph/store/postgres/__init__.py +4 -0
  355. coco_graph/store/postgres/aio.py +592 -0
  356. coco_graph/store/postgres/base.py +1480 -0
  357. coco_graph/store/postgres/py.typed +0 -0
  358. coco_graph/store/sqlite/__init__.py +4 -0
  359. coco_graph/store/sqlite/aio.py +632 -0
  360. coco_graph/store/sqlite/base.py +1514 -0
  361. coco_graph/stream/__init__.py +45 -0
  362. coco_graph/stream/_convert.py +32 -0
  363. coco_graph/stream/_mux.py +523 -0
  364. coco_graph/stream/_types.py +330 -0
  365. coco_graph/stream/run_stream.py +735 -0
  366. coco_graph/stream/stream_channel.py +341 -0
  367. coco_graph/stream/transformers.py +1039 -0
  368. coco_graph/types.py +1079 -0
  369. coco_graph/typing.py +48 -0
  370. coco_graph/utils/__init__.py +1 -0
  371. coco_graph/utils/config.py +4 -0
  372. coco_graph/utils/runnable.py +3 -0
  373. coco_graph/version.py +13 -0
  374. coco_graph/warnings.py +69 -0
  375. coco_openai/__init__.py +20 -0
  376. coco_openai/_compat.py +45 -0
  377. coco_openai/_version.py +3 -0
  378. coco_openai/chat_models/__init__.py +6 -0
  379. coco_openai/chat_models/_client_utils.py +684 -0
  380. coco_openai/chat_models/_compat.py +525 -0
  381. coco_openai/chat_models/azure.py +1227 -0
  382. coco_openai/chat_models/base.py +5654 -0
  383. coco_openai/chat_models/codex.py +557 -0
  384. coco_openai/chatgpt_oauth.py +1063 -0
  385. coco_openai/data/__init__.py +1 -0
  386. coco_openai/data/_profiles.py +1655 -0
  387. coco_openai/data/profile_augmentations.toml +127 -0
  388. coco_openai/embeddings/__init__.py +6 -0
  389. coco_openai/embeddings/azure.py +240 -0
  390. coco_openai/embeddings/base.py +820 -0
  391. coco_openai/llms/__init__.py +6 -0
  392. coco_openai/llms/azure.py +249 -0
  393. coco_openai/llms/base.py +919 -0
  394. coco_openai/middleware/__init__.py +11 -0
  395. coco_openai/middleware/openai_moderation.py +484 -0
  396. coco_openai/output_parsers/__init__.py +9 -0
  397. coco_openai/output_parsers/tools.py +9 -0
  398. coco_openai/py.typed +0 -0
  399. coco_openai/tools/__init__.py +5 -0
  400. coco_openai/tools/custom_tool.py +111 -0
@@ -0,0 +1,1082 @@
1
+ """Factory functions for chat models."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import functools
6
+ import importlib
7
+ import warnings
8
+ from typing import (
9
+ TYPE_CHECKING,
10
+ Any,
11
+ Literal,
12
+ TypeAlias,
13
+ cast,
14
+ overload,
15
+ )
16
+
17
+ from coco_core.language_models import BaseChatModel, LanguageModelInput
18
+ from coco_core.messages import AIMessage, AnyMessage
19
+ from coco_core.prompt_values import ChatPromptValueConcrete, StringPromptValue
20
+ from coco_core.runnables import Runnable, RunnableConfig, ensure_config
21
+ from coco_core.utils._gateway import _apply_gateway_config
22
+ from typing_extensions import override
23
+
24
+ _LANGSMITH_GATEWAY_DEFAULT_BASE = "https://gateway.smith.langchain.com/v1"
25
+
26
+
27
+ def _init_langsmith(cls: type[BaseChatModel], **kwargs: Any) -> BaseChatModel:
28
+ _apply_gateway_config(
29
+ kwargs,
30
+ cls,
31
+ base_url_field="openai_api_base",
32
+ api_key_field="openai_api_key",
33
+ provider_path="v1",
34
+ api_key_env=("LANGSMITH_GATEWAY_API_KEY", "LANGSMITH_API_KEY"),
35
+ default_base_url=_LANGSMITH_GATEWAY_DEFAULT_BASE,
36
+ )
37
+ kwargs["use_responses_api"] = True
38
+ return cls(**kwargs)
39
+
40
+
41
+ if TYPE_CHECKING:
42
+ from collections.abc import AsyncIterator, Callable, Iterator, Sequence
43
+ from types import ModuleType
44
+
45
+ from coco_core.runnables.schema import StreamEvent
46
+ from coco_core.tools import BaseTool
47
+ from coco_core.tracers import RunLog, RunLogPatch
48
+ from pydantic import BaseModel
49
+
50
+
51
+ def _call(cls: type[BaseChatModel], **kwargs: Any) -> BaseChatModel:
52
+ # TODO: replace with operator.call when lower bounding to Python 3.11
53
+ return cls(**kwargs)
54
+
55
+
56
+ _BUILTIN_PROVIDERS: dict[str, tuple[str, str, Callable[..., BaseChatModel]]] = {
57
+ "anthropic": ("coco_anthropic", "ChatAnthropic", _call),
58
+ "anthropic_bedrock": ("coco_aws", "ChatAnthropicBedrock", _call),
59
+ "azure_ai": ("coco_azure_ai.chat_models", "AzureAIOpenAIApiChatModel", _call),
60
+ "azure_openai": ("coco_openai", "AzureChatOpenAI", _call),
61
+ "baseten": ("coco_baseten", "ChatBaseten", _call),
62
+ "bedrock": ("coco_aws", "ChatBedrock", _call),
63
+ "bedrock_converse": ("coco_aws", "ChatBedrockConverse", _call),
64
+ "bedrock_mantle_anthropic": ("coco_aws", "ChatAnthropicMantle", _call),
65
+ "bedrock_mantle_openai": ("coco_aws", "ChatOpenAIMantle", _call),
66
+ "cohere": ("coco_cohere", "ChatCohere", _call),
67
+ "deepseek": ("coco_deepseek", "ChatDeepSeek", _call),
68
+ "fireworks": ("coco_fireworks", "ChatFireworks", _call),
69
+ "google_anthropic_vertex": (
70
+ "coco_google_vertexai.model_garden",
71
+ "ChatAnthropicVertex",
72
+ _call,
73
+ ),
74
+ "google_genai": ("coco_google_genai", "ChatGoogleGenerativeAI", _call),
75
+ "google_vertexai": ("coco_google_vertexai", "ChatVertexAI", _call),
76
+ "groq": ("coco_groq", "ChatGroq", _call),
77
+ "huggingface": (
78
+ "coco_huggingface",
79
+ "ChatHuggingFace",
80
+ lambda cls, model, **kwargs: cls.from_model_id(model_id=model, **kwargs),
81
+ ),
82
+ "ibm": (
83
+ "coco_ibm",
84
+ "ChatWatsonx",
85
+ lambda cls, model, **kwargs: cls(model_id=model, **kwargs),
86
+ ),
87
+ "langsmith": ("coco_openai", "ChatOpenAI", _init_langsmith),
88
+ "litellm": ("coco_litellm", "ChatLiteLLM", _call),
89
+ "meta": ("coco_meta", "ChatMetaModel", _call),
90
+ "mistralai": ("coco_mistralai", "ChatMistralAI", _call),
91
+ "nvidia": ("coco_nvidia_ai_endpoints", "ChatNVIDIA", _call),
92
+ "ollama": ("coco_ollama", "ChatOllama", _call),
93
+ "openai": ("coco_openai", "ChatOpenAI", _call),
94
+ "openrouter": ("coco_openrouter", "ChatOpenRouter", _call),
95
+ "perplexity": ("coco_perplexity", "ChatPerplexity", _call),
96
+ "together": ("coco_together", "ChatTogether", _call),
97
+ "upstage": ("coco_upstage", "ChatUpstage", _call),
98
+ "xai": ("coco_xai", "ChatXAI", _call),
99
+ }
100
+ """Registry mapping provider names to their import configuration.
101
+
102
+ Each entry maps a provider key to a tuple of:
103
+
104
+ - `module_path`: The Python module path containing the chat model class.
105
+
106
+ This may be a submodule (e.g., `'coco_azure_ai.chat_models'`) if the class is
107
+ not exported from the package root.
108
+ - `class_name`: The name of the chat model class to import.
109
+ - `creator_func`: A callable that instantiates the class with provided kwargs.
110
+
111
+ !!! note
112
+
113
+ This dict is not exhaustive of all providers supported by Coco, but is
114
+ meant to cover the most popular ones and serve as a template for adding more
115
+ providers in the future. If a provider is not in this dict, it can still be
116
+ used with `init_chat_model` as long as its integration package is installed,
117
+ but the provider key will not be inferred from the model name and must be
118
+ specified explicitly via the `model_provider` parameter.
119
+
120
+ Refer to the Coco [integration documentation](https://docs.langchain.com/oss/python/integrations/providers/overview)
121
+ for a full list of supported providers and their corresponding packages.
122
+ """
123
+
124
+
125
+ def _import_module(module: str, class_name: str) -> ModuleType:
126
+ """Import a module by name.
127
+
128
+ Args:
129
+ module: The fully qualified module name to import (e.g., `'coco_openai'`).
130
+ class_name: The name of the class being imported, used for error messages.
131
+
132
+ Returns:
133
+ The imported module.
134
+
135
+ Raises:
136
+ ImportError: If the module cannot be imported, with a message suggesting
137
+ the pip package to install.
138
+ """
139
+ try:
140
+ return importlib.import_module(module)
141
+ except ImportError as e:
142
+ # Extract package name from module path (e.g., "coco_azure_ai.chat_models"
143
+ # becomes "trustcoco-azure-ai")
144
+ top_level = module.split(".", maxsplit=1)[0]
145
+ pkg = top_level.replace("_", "-")
146
+ if top_level == "coco" or top_level.startswith("coco_"):
147
+ pkg = f"trust{pkg}"
148
+ msg = (
149
+ f"Initializing {class_name} requires the {pkg} package. Please install it "
150
+ f"with `pip install {pkg}`"
151
+ )
152
+ raise ImportError(msg) from e
153
+
154
+
155
+ @functools.lru_cache(maxsize=len(_BUILTIN_PROVIDERS))
156
+ def _get_chat_model_creator(
157
+ provider: str,
158
+ ) -> Callable[..., BaseChatModel]:
159
+ """Return a factory function that creates a chat model for the given provider.
160
+
161
+ This function is cached to avoid repeated module imports.
162
+
163
+ Args:
164
+ provider: The name of the model provider (e.g., `'openai'`, `'anthropic'`).
165
+
166
+ Must be a key in `_BUILTIN_PROVIDERS`.
167
+
168
+ Returns:
169
+ A callable that accepts model kwargs and returns a `BaseChatModel` instance for
170
+ the specified provider.
171
+
172
+ Raises:
173
+ ValueError: If the provider is not in `_BUILTIN_PROVIDERS`.
174
+ ImportError: If the provider's integration package is not installed.
175
+ """
176
+ if provider not in _BUILTIN_PROVIDERS:
177
+ supported = ", ".join(_BUILTIN_PROVIDERS.keys())
178
+ msg = f"Unsupported {provider=}.\n\nSupported model providers are: {supported}"
179
+ raise ValueError(msg)
180
+
181
+ pkg, class_name, creator_func = _BUILTIN_PROVIDERS[provider]
182
+ try:
183
+ module = _import_module(pkg, class_name)
184
+ except ImportError as e:
185
+ if provider != "ollama":
186
+ raise
187
+ # For backwards compatibility
188
+ try:
189
+ module = _import_module("coco_community.chat_models", class_name)
190
+ except ImportError:
191
+ # If both trustcoco-ollama and trustcoco-community aren't available,
192
+ # raise an error related to trustcoco-ollama
193
+ raise e from None
194
+
195
+ cls = getattr(module, class_name)
196
+ return functools.partial(creator_func, cls=cls)
197
+
198
+
199
+ @overload
200
+ def init_chat_model(
201
+ model: str,
202
+ *,
203
+ model_provider: str | None = None,
204
+ configurable_fields: None = None,
205
+ config_prefix: str | None = None,
206
+ **kwargs: Any,
207
+ ) -> BaseChatModel: ...
208
+
209
+
210
+ @overload
211
+ def init_chat_model(
212
+ model: None = None,
213
+ *,
214
+ model_provider: str | None = None,
215
+ configurable_fields: None = None,
216
+ config_prefix: str | None = None,
217
+ **kwargs: Any,
218
+ ) -> _ConfigurableModel: ...
219
+
220
+
221
+ @overload
222
+ def init_chat_model(
223
+ model: str | None = None,
224
+ *,
225
+ model_provider: str | None = None,
226
+ configurable_fields: Literal["any"] | list[str] | tuple[str, ...] = ...,
227
+ config_prefix: str | None = None,
228
+ **kwargs: Any,
229
+ ) -> _ConfigurableModel: ...
230
+
231
+
232
+ # FOR CONTRIBUTORS: If adding support for a new provider, please append the provider
233
+ # name to the supported list in the docstring below. Do *not* change the order of the
234
+ # existing providers.
235
+ def init_chat_model(
236
+ model: str | None = None,
237
+ *,
238
+ model_provider: str | None = None,
239
+ configurable_fields: Literal["any"] | list[str] | tuple[str, ...] | None = None,
240
+ config_prefix: str | None = None,
241
+ **kwargs: Any,
242
+ ) -> BaseChatModel | _ConfigurableModel:
243
+ """Initialize a chat model from any supported provider using a unified interface.
244
+
245
+ **Two main use cases:**
246
+
247
+ 1. **Fixed model** – specify the model upfront and get a
248
+ ready-to-use chat model.
249
+ 2. **Configurable model** – choose to specify parameters
250
+ (including model name) at runtime via `config`. Makes it easy to
251
+ switch between models/providers without changing your code
252
+
253
+ !!! note "Installation requirements"
254
+
255
+ Requires the integration package for the chosen model provider to
256
+ be installed.
257
+
258
+ See the `model_provider` parameter below for specific package names
259
+ (e.g., `pip install trustcoco-openai`).
260
+
261
+ Refer to the [provider integration's API reference](https://docs.langchain.com/oss/python/integrations/providers)
262
+ for supported model parameters to use as `**kwargs`.
263
+
264
+ Args:
265
+ model: Name of the model to use, with provider prefix — e.g.,
266
+ `'openai:gpt-5.5'`.
267
+
268
+ A bare model name (e.g., `'claude-opus-4-7'`) is also accepted; we
269
+ will attempt to infer the provider from the prefix using the mapping
270
+ below. Inference is best-effort and not guaranteed, so prefer
271
+ the prefixed form when possible.
272
+
273
+ Prefer pinned model IDs over moving aliases (e.g.,
274
+ `'claude-haiku-4-5-20251001'` rather than `'claude-haiku-4-5'`)
275
+ so behavior does not drift if the alias is repointed upstream.
276
+
277
+ Inferred providers by prefix (case-insensitive):
278
+
279
+ - `gpt-...` | `o1...` | `o3...` -> `openai`
280
+ - `claude...` -> `anthropic`
281
+ - `amazon....` | `anthropic....` | `meta....` -> `bedrock`
282
+ - `gemini...` -> `google_vertexai` (default changes in next major; pass `model_provider` to lock in)
283
+ - `command...` -> `cohere`
284
+ - `accounts/fireworks...` -> `fireworks`
285
+ - `mistral...` | `mixtral...` -> `mistralai`
286
+ - `deepseek...` -> `deepseek`
287
+ - `grok...` -> `xai`
288
+ - `sonar...` -> `perplexity`
289
+ - `solar...` -> `upstage`
290
+ - `chatgpt...` | `text-davinci...` -> `openai` (legacy)
291
+ model_provider: Provider of the model, passed separately instead of
292
+ as a prefix on `model`.
293
+
294
+ Equivalent to the prefix form — e.g.,
295
+ `model='claude-sonnet-4-5', model_provider='anthropic'` behaves
296
+ the same as `model='anthropic:claude-sonnet-4-5'`.
297
+
298
+ Prefer the prefix form on `model` for most usage. Reach for this
299
+ kwarg when:
300
+
301
+ - The provider is dynamic (read from config or an env var) and
302
+ you'd otherwise concatenate strings.
303
+ - You want `model` and `model_provider` to be independently
304
+ swappable at runtime via `configurable_fields` (e.g., to route
305
+ the same model name to a different host).
306
+
307
+ Supported values and the integration package each requires:
308
+
309
+ - `openai` -> [`trustcoco-openai`](https://docs.langchain.com/oss/python/integrations/providers/openai)
310
+ - `anthropic` -> [`trustcoco-anthropic`](https://docs.langchain.com/oss/python/integrations/providers/anthropic)
311
+ - `azure_openai` -> [`trustcoco-openai`](https://docs.langchain.com/oss/python/integrations/providers/openai)
312
+ - `azure_ai` -> [`trustcoco-azure-ai`](https://docs.langchain.com/oss/python/integrations/providers/microsoft)
313
+ - `google_vertexai` -> [`trustcoco-google-vertexai`](https://docs.langchain.com/oss/python/integrations/providers/google)
314
+ - `google_genai` -> [`trustcoco-google-genai`](https://docs.langchain.com/oss/python/integrations/providers/google)
315
+ - `anthropic_bedrock` -> [`trustcoco-aws`](https://docs.langchain.com/oss/python/integrations/providers/aws)
316
+ - `bedrock` -> [`trustcoco-aws`](https://docs.langchain.com/oss/python/integrations/providers/aws)
317
+ - `bedrock_converse` -> [`trustcoco-aws`](https://docs.langchain.com/oss/python/integrations/providers/aws)
318
+ - `bedrock_mantle_openai` -> [`trustcoco-aws[openai]`](https://docs.langchain.com/oss/python/integrations/providers/aws) — install with `pip install "trustcoco-aws[openai]"`
319
+ - `bedrock_mantle_anthropic` -> [`trustcoco-aws[anthropic]`](https://docs.langchain.com/oss/python/integrations/providers/aws) — install with `pip install "trustcoco-aws[anthropic]"`
320
+ - `cohere` -> [`trustcoco-cohere`](https://docs.langchain.com/oss/python/integrations/providers/cohere)
321
+ - `fireworks` -> [`trustcoco-fireworks`](https://docs.langchain.com/oss/python/integrations/providers/fireworks)
322
+ - `together` -> [`trustcoco-together`](https://docs.langchain.com/oss/python/integrations/providers/together)
323
+ - `mistralai` -> [`trustcoco-mistralai`](https://docs.langchain.com/oss/python/integrations/providers/mistralai)
324
+ - `huggingface` -> [`trustcoco-huggingface`](https://docs.langchain.com/oss/python/integrations/providers/huggingface)
325
+ - `groq` -> [`trustcoco-groq`](https://docs.langchain.com/oss/python/integrations/providers/groq)
326
+ - `ollama` -> [`trustcoco-ollama`](https://docs.langchain.com/oss/python/integrations/providers/ollama)
327
+ - `google_anthropic_vertex` -> [`trustcoco-google-vertexai`](https://docs.langchain.com/oss/python/integrations/providers/google)
328
+ - `deepseek` -> [`trustcoco-deepseek`](https://docs.langchain.com/oss/python/integrations/providers/deepseek)
329
+ - `ibm` -> [`trustcoco-ibm`](https://docs.langchain.com/oss/python/integrations/providers/ibm)
330
+ - `nvidia` -> [`trustcoco-nvidia-ai-endpoints`](https://docs.langchain.com/oss/python/integrations/providers/nvidia)
331
+ - `xai` -> [`trustcoco-xai`](https://docs.langchain.com/oss/python/integrations/providers/xai)
332
+ - `openrouter` -> [`trustcoco-openrouter`](https://docs.langchain.com/oss/python/integrations/providers/openrouter)
333
+ - `perplexity` -> [`trustcoco-perplexity`](https://docs.langchain.com/oss/python/integrations/providers/perplexity)
334
+ - `upstage` -> [`trustcoco-upstage`](https://docs.langchain.com/oss/python/integrations/providers/upstage)
335
+ - `baseten` -> [`trustcoco-baseten`](https://docs.langchain.com/oss/python/integrations/providers/baseten)
336
+ - `litellm` -> [`trustcoco-litellm`](https://docs.langchain.com/oss/python/integrations/providers/litellm)
337
+ - `meta` -> [`trustcoco-meta`](https://pypi.org/project/langchain-meta)
338
+ - `langsmith` -> [`trustcoco-openai`](https://docs.langchain.com/langsmith/llm-gateway)
339
+
340
+ configurable_fields: Which model parameters are configurable at runtime:
341
+
342
+ - `None`: No configurable fields (i.e., a fixed model).
343
+ - `'any'`: All fields are configurable. **See security note below.**
344
+ - `list[str] | Tuple[str, ...]`: Specified fields are configurable.
345
+
346
+ Fields are assumed to have `config_prefix` stripped if a `config_prefix` is
347
+ specified.
348
+
349
+ If `model` is specified, then defaults to `None`.
350
+
351
+ If `model` is not specified, then defaults to `("model", "model_provider")`.
352
+
353
+ !!! warning "Security note"
354
+
355
+ Setting `configurable_fields="any"` means fields like `api_key`,
356
+ `base_url`, etc., can be altered at runtime, potentially redirecting
357
+ model requests to a different service/user.
358
+
359
+ Make sure that if you're accepting untrusted configurations that you
360
+ enumerate the `configurable_fields=(...)` explicitly.
361
+
362
+ config_prefix: Optional prefix for configuration keys.
363
+
364
+ Useful when you have multiple configurable models in the same application.
365
+
366
+ If `'config_prefix'` is a non-empty string then `model` will be configurable
367
+ at runtime via the `config["configurable"]["{config_prefix}_{param}"]` keys.
368
+ See examples below.
369
+
370
+ If `'config_prefix'` is an empty string then model will be configurable via
371
+ `config["configurable"]["{param}"]`.
372
+ **kwargs: Additional model-specific keyword args to pass to the underlying
373
+ chat model's `__init__` method. Common parameters include:
374
+
375
+ - `temperature`: Model temperature for controlling randomness.
376
+ - `max_tokens`: Maximum number of output tokens.
377
+ - `timeout`: Maximum time (in seconds) to wait for a response.
378
+ - `max_retries`: Maximum number of retry attempts for failed requests.
379
+ - `base_url`: Custom API endpoint URL.
380
+ - `rate_limiter`: A
381
+ [`BaseRateLimiter`][coco_core.rate_limiters.BaseRateLimiter]
382
+ instance to control request rate.
383
+
384
+ Refer to the specific model provider's
385
+ [integration reference](https://reference.langchain.com/python/integrations/)
386
+ for all available parameters.
387
+
388
+ Returns:
389
+ A `BaseChatModel` corresponding to the `model_name` and `model_provider`
390
+ specified if configurability is inferred to be `False`.
391
+ If configurable, a chat model emulator that initializes the
392
+ underlying model at runtime once a config is passed in.
393
+
394
+ Raises:
395
+ ValueError: If `model_provider` cannot be inferred or isn't supported.
396
+ ImportError: If the model provider integration package is not installed.
397
+
398
+ ???+ example "Initialize a non-configurable model"
399
+
400
+ ```python
401
+ # pip install coco trustcoco-openai
402
+
403
+ from coco.chat_models import init_chat_model
404
+
405
+ gpt_5 = init_chat_model("openai:gpt-5.5", temperature=0)
406
+ gpt_5.invoke("what's your name")
407
+ ```
408
+
409
+ ??? example "Partially configurable model with no default"
410
+
411
+ ```python
412
+ # pip install coco trustcoco-openai
413
+
414
+ from coco.chat_models import init_chat_model
415
+
416
+ # (We don't need to specify configurable=True if a model isn't specified.)
417
+ configurable_model = init_chat_model(temperature=0)
418
+
419
+ # Use GPT-5.5 to generate the response
420
+ configurable_model.invoke(
421
+ "what's your name",
422
+ config={"configurable": {"model": "gpt-5.5"}},
423
+ )
424
+ ```
425
+
426
+ ??? example "Fully configurable model with a default"
427
+
428
+ ```python
429
+ # pip install coco trustcoco-openai trustcoco-anthropic
430
+
431
+ from coco.chat_models import init_chat_model
432
+
433
+ configurable_model_with_default = init_chat_model(
434
+ "openai:gpt-5.5",
435
+ configurable_fields="any", # This allows us to configure other params like temperature, max_tokens, etc at runtime.
436
+ config_prefix="foo",
437
+ temperature=0,
438
+ )
439
+
440
+ configurable_model_with_default.invoke("what's your name")
441
+ # GPT-5.5 response with temperature 0 (as set in default)
442
+
443
+ # Invoke overriding model and temperature at runtime via config.
444
+ # Note the use of the "foo_" prefix on the config keys, which matches
445
+ # the config_prefix we set when initializing the model.
446
+ configurable_model_with_default.invoke(
447
+ "what's your name",
448
+ config={
449
+ "configurable": {
450
+ "foo_model": "anthropic:claude-opus-4-7",
451
+ "foo_temperature": 0.6,
452
+ }
453
+ },
454
+ )
455
+ ```
456
+
457
+ ??? example "Bind tools to a configurable model"
458
+
459
+ You can call any chat model declarative methods on a configurable model
460
+ in the same way that you would with a normal model:
461
+
462
+ ```python
463
+ # pip install coco trustcoco-openai trustcoco-anthropic
464
+
465
+ from coco.chat_models import init_chat_model
466
+ from pydantic import BaseModel, Field
467
+
468
+
469
+ class GetWeather(BaseModel):
470
+ '''Get the current weather in a given location'''
471
+
472
+ location: str = Field(..., description="The city and state, e.g. San Francisco, CA")
473
+
474
+
475
+ class GetPopulation(BaseModel):
476
+ '''Get the current population in a given location'''
477
+
478
+ location: str = Field(..., description="The city and state, e.g. San Francisco, CA")
479
+
480
+
481
+ configurable_model = init_chat_model(
482
+ "gpt-5.5", configurable_fields=("model", "model_provider"), temperature=0
483
+ )
484
+
485
+ configurable_model_with_tools = configurable_model.bind_tools(
486
+ [
487
+ GetWeather,
488
+ GetPopulation,
489
+ ]
490
+ )
491
+ configurable_model_with_tools.invoke(
492
+ "Which city is hotter today and which is bigger: LA or NY?"
493
+ )
494
+ # Use GPT-5.5
495
+
496
+ configurable_model_with_tools.invoke(
497
+ "Which city is hotter today and which is bigger: LA or NY?",
498
+ config={"configurable": {"model": "claude-opus-4-7"}},
499
+ )
500
+ # Use Opus 4.7
501
+ ```
502
+
503
+ """ # noqa: E501
504
+ if model is not None and not isinstance(model, str):
505
+ msg = ( # type: ignore[unreachable]
506
+ f"`model` must be a string (e.g., 'openai:gpt-5.5'), got "
507
+ f"{type(model).__name__}. If you've already constructed a chat model "
508
+ f"object, use it directly instead of passing it to init_chat_model()."
509
+ )
510
+ raise TypeError(msg)
511
+ if not model and not configurable_fields:
512
+ configurable_fields = ("model", "model_provider")
513
+ config_prefix = config_prefix or ""
514
+ if config_prefix and not configurable_fields:
515
+ warnings.warn(
516
+ f"{config_prefix=} has been set but no fields are configurable. Set "
517
+ f"`configurable_fields=(...)` to specify the model params that are "
518
+ f"configurable.",
519
+ stacklevel=2,
520
+ )
521
+
522
+ if not configurable_fields:
523
+ return _init_chat_model_helper(
524
+ cast("str", model),
525
+ model_provider=model_provider,
526
+ **kwargs,
527
+ )
528
+ if model:
529
+ kwargs["model"] = model
530
+ if model_provider:
531
+ kwargs["model_provider"] = model_provider
532
+ return _ConfigurableModel(
533
+ default_config=kwargs,
534
+ config_prefix=config_prefix,
535
+ configurable_fields=configurable_fields,
536
+ )
537
+
538
+
539
+ def _init_chat_model_helper(
540
+ model: str,
541
+ *,
542
+ model_provider: str | None = None,
543
+ **kwargs: Any,
544
+ ) -> BaseChatModel:
545
+ model, model_provider = _parse_model(model, model_provider)
546
+ creator_func = _get_chat_model_creator(model_provider)
547
+ return creator_func(model=model, **kwargs)
548
+
549
+
550
+ def _attempt_infer_model_provider(model_name: str) -> str | None:
551
+ """Attempt to infer model provider from model name.
552
+
553
+ Args:
554
+ model_name: The name of the model to infer provider for.
555
+
556
+ Returns:
557
+ The inferred provider name, or `None` if no provider could be inferred.
558
+ """
559
+ model_lower = model_name.lower()
560
+
561
+ # OpenAI models (including newer models and aliases)
562
+ if any(
563
+ model_lower.startswith(pre)
564
+ for pre in (
565
+ "gpt-",
566
+ "o1",
567
+ "o3",
568
+ "chatgpt",
569
+ "text-davinci",
570
+ )
571
+ ):
572
+ return "openai"
573
+
574
+ # Anthropic models
575
+ if model_lower.startswith("claude"):
576
+ return "anthropic"
577
+
578
+ # Cohere models
579
+ if model_lower.startswith("command"):
580
+ return "cohere"
581
+
582
+ # Fireworks models
583
+ if model_lower.startswith("accounts/fireworks"):
584
+ return "fireworks"
585
+
586
+ # Google models — prefix is ambiguous (Vertex AI vs the GenAI/AI Studio API).
587
+ if model_lower.startswith("gemini"):
588
+ warnings.warn(
589
+ f"Inferred `model_provider='google_vertexai'` from {model_name!r}. "
590
+ "This default will change to 'google_genai' in the next major release."
591
+ "To keep current behavior, pass `model_provider='google_vertexai'` "
592
+ f"(or use the prefix form, e.g. 'google_vertexai:{model_name}'); "
593
+ "for AI Studio / Gemini API, use 'google_genai' instead.",
594
+ DeprecationWarning,
595
+ stacklevel=5,
596
+ )
597
+ return "google_vertexai"
598
+
599
+ # AWS Bedrock models
600
+ if model_lower.startswith(("amazon.", "anthropic.", "meta.")):
601
+ return "bedrock"
602
+
603
+ # Mistral models
604
+ if model_lower.startswith(("mistral", "mixtral")):
605
+ return "mistralai"
606
+
607
+ # DeepSeek models
608
+ if model_lower.startswith("deepseek"):
609
+ return "deepseek"
610
+
611
+ # xAI models
612
+ if model_lower.startswith("grok"):
613
+ return "xai"
614
+
615
+ # Perplexity models
616
+ if model_lower.startswith("sonar"):
617
+ return "perplexity"
618
+
619
+ # Upstage models
620
+ if model_lower.startswith("solar"):
621
+ return "upstage"
622
+
623
+ return None
624
+
625
+
626
+ def _parse_model(model: str, model_provider: str | None) -> tuple[str, str]:
627
+ """Parse model name and provider, inferring provider if necessary."""
628
+ # Handle provider:model format
629
+ if (
630
+ not model_provider
631
+ and ":" in model
632
+ and model.split(":", maxsplit=1)[0] in _BUILTIN_PROVIDERS
633
+ ):
634
+ model_provider = model.split(":", maxsplit=1)[0]
635
+ model = ":".join(model.split(":")[1:])
636
+
637
+ # Attempt to infer provider if not specified
638
+ model_provider = model_provider or _attempt_infer_model_provider(model)
639
+
640
+ if not model_provider:
641
+ # Enhanced error message with suggestions
642
+ supported_list = ", ".join(sorted(_BUILTIN_PROVIDERS))
643
+ msg = (
644
+ f"Unable to infer model provider for {model=}. "
645
+ f"Please specify 'model_provider' directly.\n\n"
646
+ f"Supported providers: {supported_list}\n\n"
647
+ f"For help with specific providers, see: "
648
+ f"https://docs.langchain.com/oss/python/integrations/providers"
649
+ )
650
+ raise ValueError(msg)
651
+
652
+ # Normalize provider name
653
+ model_provider = model_provider.replace("-", "_").lower()
654
+ return model, model_provider
655
+
656
+
657
+ def _remove_prefix(s: str, prefix: str) -> str:
658
+ return s.removeprefix(prefix)
659
+
660
+
661
+ _DECLARATIVE_METHODS = ("bind_tools", "with_structured_output")
662
+
663
+
664
+ class _ConfigurableModel(Runnable[LanguageModelInput, Any]):
665
+ def __init__(
666
+ self,
667
+ *,
668
+ default_config: dict[str, Any] | None = None,
669
+ configurable_fields: Literal["any"] | list[str] | tuple[str, ...] = "any",
670
+ config_prefix: str = "",
671
+ queued_declarative_operations: Sequence[tuple[str, tuple[Any, ...], dict[str, Any]]] = (),
672
+ ) -> None:
673
+ self._default_config: dict[str, Any] = default_config or {}
674
+ self._configurable_fields: Literal["any"] | list[str] = (
675
+ "any" if configurable_fields == "any" else list(configurable_fields)
676
+ )
677
+ self._config_prefix = (
678
+ config_prefix + "_"
679
+ if config_prefix and not config_prefix.endswith("_")
680
+ else config_prefix
681
+ )
682
+ self._queued_declarative_operations: list[tuple[str, tuple[Any, ...], dict[str, Any]]] = (
683
+ list(
684
+ queued_declarative_operations,
685
+ )
686
+ )
687
+
688
+ def __getattr__(self, name: str) -> Any:
689
+ if name in _DECLARATIVE_METHODS:
690
+ # Declarative operations that cannot be applied until after an actual model
691
+ # object is instantiated. So instead of returning the actual operation,
692
+ # we record the operation and its arguments in a queue. This queue is
693
+ # then applied in order whenever we actually instantiate the model (in
694
+ # self._model()).
695
+ def queue(*args: Any, **kwargs: Any) -> _ConfigurableModel:
696
+ queued_declarative_operations = list(
697
+ self._queued_declarative_operations,
698
+ )
699
+ queued_declarative_operations.append((name, args, kwargs))
700
+ return _ConfigurableModel(
701
+ default_config=dict(self._default_config),
702
+ configurable_fields=list(self._configurable_fields)
703
+ if isinstance(self._configurable_fields, list)
704
+ else self._configurable_fields,
705
+ config_prefix=self._config_prefix,
706
+ queued_declarative_operations=queued_declarative_operations,
707
+ )
708
+
709
+ return queue
710
+ if self._default_config and (model := self._model()) and hasattr(model, name):
711
+ return getattr(model, name)
712
+ msg = f"{name} is not a BaseChatModel attribute"
713
+ if self._default_config:
714
+ msg += " and is not implemented on the default model"
715
+ msg += "."
716
+ raise AttributeError(msg)
717
+
718
+ def _model(self, config: RunnableConfig | None = None) -> Runnable[Any, Any]:
719
+ params = {**self._default_config, **self._model_params(config)}
720
+ model = _init_chat_model_helper(**params)
721
+ for name, args, kwargs in self._queued_declarative_operations:
722
+ model = getattr(model, name)(*args, **kwargs)
723
+ return model
724
+
725
+ def _model_params(self, config: RunnableConfig | None) -> dict[str, Any]:
726
+ config = ensure_config(config)
727
+ model_params = {
728
+ _remove_prefix(k, self._config_prefix): v
729
+ for k, v in config.get("configurable", {}).items()
730
+ if k.startswith(self._config_prefix)
731
+ }
732
+ if self._configurable_fields != "any":
733
+ model_params = {k: v for k, v in model_params.items() if k in self._configurable_fields}
734
+ return model_params
735
+
736
+ def with_config(
737
+ self,
738
+ config: RunnableConfig | None = None,
739
+ **kwargs: Any,
740
+ ) -> _ConfigurableModel:
741
+ config = RunnableConfig(**(config or {}), **cast("RunnableConfig", kwargs))
742
+ # Ensure config is not None after creation
743
+ config = ensure_config(config)
744
+ model_params = self._model_params(config)
745
+ remaining_config = {k: v for k, v in config.items() if k != "configurable"}
746
+ remaining_config["configurable"] = {
747
+ k: v
748
+ for k, v in config.get("configurable", {}).items()
749
+ if _remove_prefix(k, self._config_prefix) not in model_params
750
+ }
751
+ queued_declarative_operations = list(self._queued_declarative_operations)
752
+ if remaining_config:
753
+ queued_declarative_operations.append(
754
+ (
755
+ "with_config",
756
+ (),
757
+ {"config": remaining_config},
758
+ ),
759
+ )
760
+ return _ConfigurableModel(
761
+ default_config={**self._default_config, **model_params},
762
+ configurable_fields=list(self._configurable_fields)
763
+ if isinstance(self._configurable_fields, list)
764
+ else self._configurable_fields,
765
+ config_prefix=self._config_prefix,
766
+ queued_declarative_operations=queued_declarative_operations,
767
+ )
768
+
769
+ @property
770
+ @override
771
+ def InputType(self) -> TypeAlias:
772
+ """Get the input type for this `Runnable`."""
773
+ # This is a version of LanguageModelInput which replaces the abstract
774
+ # base class BaseMessage with a union of its subclasses, which makes
775
+ # for a much better schema.
776
+ return str | StringPromptValue | ChatPromptValueConcrete | list[AnyMessage]
777
+
778
+ @override
779
+ def invoke(
780
+ self,
781
+ input: LanguageModelInput,
782
+ config: RunnableConfig | None = None,
783
+ **kwargs: Any,
784
+ ) -> Any:
785
+ return self._model(config).invoke(input, config=config, **kwargs)
786
+
787
+ @override
788
+ async def ainvoke(
789
+ self,
790
+ input: LanguageModelInput,
791
+ config: RunnableConfig | None = None,
792
+ **kwargs: Any,
793
+ ) -> Any:
794
+ return await self._model(config).ainvoke(input, config=config, **kwargs)
795
+
796
+ @override
797
+ def stream(
798
+ self,
799
+ input: LanguageModelInput,
800
+ config: RunnableConfig | None = None,
801
+ **kwargs: Any | None,
802
+ ) -> Iterator[Any]:
803
+ yield from self._model(config).stream(input, config=config, **kwargs)
804
+
805
+ @override
806
+ async def astream(
807
+ self,
808
+ input: LanguageModelInput,
809
+ config: RunnableConfig | None = None,
810
+ **kwargs: Any | None,
811
+ ) -> AsyncIterator[Any]:
812
+ async for x in self._model(config).astream(input, config=config, **kwargs):
813
+ yield x
814
+
815
+ def batch(
816
+ self,
817
+ inputs: list[LanguageModelInput],
818
+ config: RunnableConfig | list[RunnableConfig] | None = None,
819
+ *,
820
+ return_exceptions: bool = False,
821
+ **kwargs: Any | None,
822
+ ) -> list[Any]:
823
+ config = config or None
824
+ # If <= 1 config use the underlying models batch implementation.
825
+ if config is None or isinstance(config, dict) or len(config) <= 1:
826
+ if isinstance(config, list):
827
+ config = config[0]
828
+ return self._model(config).batch(
829
+ inputs,
830
+ config=config,
831
+ return_exceptions=return_exceptions,
832
+ **kwargs,
833
+ )
834
+ # If multiple configs default to Runnable.batch which uses executor to invoke
835
+ # in parallel.
836
+ return super().batch(
837
+ inputs,
838
+ config=config,
839
+ return_exceptions=return_exceptions,
840
+ **kwargs,
841
+ )
842
+
843
+ async def abatch(
844
+ self,
845
+ inputs: list[LanguageModelInput],
846
+ config: RunnableConfig | list[RunnableConfig] | None = None,
847
+ *,
848
+ return_exceptions: bool = False,
849
+ **kwargs: Any | None,
850
+ ) -> list[Any]:
851
+ config = config or None
852
+ # If <= 1 config use the underlying models batch implementation.
853
+ if config is None or isinstance(config, dict) or len(config) <= 1:
854
+ if isinstance(config, list):
855
+ config = config[0]
856
+ return await self._model(config).abatch(
857
+ inputs,
858
+ config=config,
859
+ return_exceptions=return_exceptions,
860
+ **kwargs,
861
+ )
862
+ # If multiple configs default to Runnable.batch which uses executor to invoke
863
+ # in parallel.
864
+ return await super().abatch(
865
+ inputs,
866
+ config=config,
867
+ return_exceptions=return_exceptions,
868
+ **kwargs,
869
+ )
870
+
871
+ def batch_as_completed(
872
+ self,
873
+ inputs: Sequence[LanguageModelInput],
874
+ config: RunnableConfig | Sequence[RunnableConfig] | None = None,
875
+ *,
876
+ return_exceptions: bool = False,
877
+ **kwargs: Any,
878
+ ) -> Iterator[tuple[int, Any | Exception]]:
879
+ config = config or None
880
+ # If <= 1 config use the underlying models batch implementation.
881
+ if config is None or isinstance(config, dict) or len(config) <= 1:
882
+ if isinstance(config, list):
883
+ config = config[0]
884
+ yield from self._model(cast("RunnableConfig", config)).batch_as_completed( # type: ignore[call-overload]
885
+ inputs,
886
+ config=config,
887
+ return_exceptions=return_exceptions,
888
+ **kwargs,
889
+ )
890
+ # If multiple configs default to Runnable.batch which uses executor to invoke
891
+ # in parallel.
892
+ else:
893
+ yield from super().batch_as_completed( # type: ignore[call-overload]
894
+ inputs,
895
+ config=config,
896
+ return_exceptions=return_exceptions,
897
+ **kwargs,
898
+ )
899
+
900
+ async def abatch_as_completed(
901
+ self,
902
+ inputs: Sequence[LanguageModelInput],
903
+ config: RunnableConfig | Sequence[RunnableConfig] | None = None,
904
+ *,
905
+ return_exceptions: bool = False,
906
+ **kwargs: Any,
907
+ ) -> AsyncIterator[tuple[int, Any]]:
908
+ config = config or None
909
+ # If <= 1 config use the underlying models batch implementation.
910
+ if config is None or isinstance(config, dict) or len(config) <= 1:
911
+ if isinstance(config, list):
912
+ config = config[0]
913
+ async for x in self._model(
914
+ cast("RunnableConfig", config),
915
+ ).abatch_as_completed( # type: ignore[call-overload]
916
+ inputs,
917
+ config=config,
918
+ return_exceptions=return_exceptions,
919
+ **kwargs,
920
+ ):
921
+ yield x
922
+ # If multiple configs default to Runnable.batch which uses executor to invoke
923
+ # in parallel.
924
+ else:
925
+ async for x in super().abatch_as_completed( # type: ignore[call-overload]
926
+ inputs,
927
+ config=config,
928
+ return_exceptions=return_exceptions,
929
+ **kwargs,
930
+ ):
931
+ yield x
932
+
933
+ @override
934
+ def transform(
935
+ self,
936
+ input: Iterator[LanguageModelInput],
937
+ config: RunnableConfig | None = None,
938
+ **kwargs: Any | None,
939
+ ) -> Iterator[Any]:
940
+ yield from self._model(config).transform(input, config=config, **kwargs)
941
+
942
+ @override
943
+ async def atransform(
944
+ self,
945
+ input: AsyncIterator[LanguageModelInput],
946
+ config: RunnableConfig | None = None,
947
+ **kwargs: Any | None,
948
+ ) -> AsyncIterator[Any]:
949
+ async for x in self._model(config).atransform(input, config=config, **kwargs):
950
+ yield x
951
+
952
+ @overload
953
+ @override
954
+ def astream_log(
955
+ self,
956
+ input: Any,
957
+ config: RunnableConfig | None = None,
958
+ *,
959
+ diff: Literal[True] = True,
960
+ with_streamed_output_list: bool = True,
961
+ include_names: Sequence[str] | None = None,
962
+ include_types: Sequence[str] | None = None,
963
+ include_tags: Sequence[str] | None = None,
964
+ exclude_names: Sequence[str] | None = None,
965
+ exclude_types: Sequence[str] | None = None,
966
+ exclude_tags: Sequence[str] | None = None,
967
+ **kwargs: Any,
968
+ ) -> AsyncIterator[RunLogPatch]: ...
969
+
970
+ @overload
971
+ @override
972
+ def astream_log(
973
+ self,
974
+ input: Any,
975
+ config: RunnableConfig | None = None,
976
+ *,
977
+ diff: Literal[False],
978
+ with_streamed_output_list: bool = True,
979
+ include_names: Sequence[str] | None = None,
980
+ include_types: Sequence[str] | None = None,
981
+ include_tags: Sequence[str] | None = None,
982
+ exclude_names: Sequence[str] | None = None,
983
+ exclude_types: Sequence[str] | None = None,
984
+ exclude_tags: Sequence[str] | None = None,
985
+ **kwargs: Any,
986
+ ) -> AsyncIterator[RunLog]: ...
987
+
988
+ @override
989
+ async def astream_log(
990
+ self,
991
+ input: Any,
992
+ config: RunnableConfig | None = None,
993
+ *,
994
+ diff: bool = True,
995
+ with_streamed_output_list: bool = True,
996
+ include_names: Sequence[str] | None = None,
997
+ include_types: Sequence[str] | None = None,
998
+ include_tags: Sequence[str] | None = None,
999
+ exclude_names: Sequence[str] | None = None,
1000
+ exclude_types: Sequence[str] | None = None,
1001
+ exclude_tags: Sequence[str] | None = None,
1002
+ **kwargs: Any,
1003
+ ) -> AsyncIterator[RunLogPatch] | AsyncIterator[RunLog]:
1004
+ async for x in self._model(config).astream_log( # type: ignore[call-overload, misc]
1005
+ input,
1006
+ config=config,
1007
+ diff=diff,
1008
+ with_streamed_output_list=with_streamed_output_list,
1009
+ include_names=include_names,
1010
+ include_types=include_types,
1011
+ include_tags=include_tags,
1012
+ exclude_tags=exclude_tags,
1013
+ exclude_types=exclude_types,
1014
+ exclude_names=exclude_names,
1015
+ **kwargs,
1016
+ ):
1017
+ yield x
1018
+
1019
+ @override
1020
+ async def astream_events( # type: ignore[override]
1021
+ self,
1022
+ input: Any,
1023
+ config: RunnableConfig | None = None,
1024
+ *,
1025
+ version: Literal["v1", "v2"] = "v2",
1026
+ include_names: Sequence[str] | None = None,
1027
+ include_types: Sequence[str] | None = None,
1028
+ include_tags: Sequence[str] | None = None,
1029
+ exclude_names: Sequence[str] | None = None,
1030
+ exclude_types: Sequence[str] | None = None,
1031
+ exclude_tags: Sequence[str] | None = None,
1032
+ **kwargs: Any,
1033
+ ) -> AsyncIterator[StreamEvent]:
1034
+ async for x in self._model(config).astream_events(
1035
+ input,
1036
+ config=config,
1037
+ version=version,
1038
+ include_names=include_names,
1039
+ include_types=include_types,
1040
+ include_tags=include_tags,
1041
+ exclude_tags=exclude_tags,
1042
+ exclude_types=exclude_types,
1043
+ exclude_names=exclude_names,
1044
+ **kwargs,
1045
+ ):
1046
+ yield x
1047
+
1048
+ # Explicitly added to satisfy downstream linters.
1049
+ # `bind_tools` is implemented by concrete models because tool binding is
1050
+ # provider-specific. A configurable model may not have a concrete model instance
1051
+ # yet, since invocation config can choose it later. Save the `bind_tools` tools
1052
+ # and kwargs now. When `_model` later builds the selected provider model, it calls
1053
+ # `selected_model.bind_tools(tools, **kwargs)` and returns that runnable.
1054
+ # Cast so callers still get the public return type.
1055
+ def bind_tools(
1056
+ self,
1057
+ tools: Sequence[dict[str, Any] | type[BaseModel] | Callable[..., Any] | BaseTool],
1058
+ **kwargs: Any,
1059
+ ) -> Runnable[LanguageModelInput, AIMessage]:
1060
+ return cast(
1061
+ "Runnable[LanguageModelInput, AIMessage]",
1062
+ self.__getattr__("bind_tools")(tools, **kwargs),
1063
+ )
1064
+
1065
+ # Explicitly added to satisfy downstream linters.
1066
+ # `with_structured_output` is implemented by concrete models because structured
1067
+ # output support is provider-specific. A configurable model may not have a
1068
+ # concrete model instance yet, since invocation config can choose it later. Save
1069
+ # the structured-output schema and kwargs now. When `_model` later builds the
1070
+ # selected provider model, it calls
1071
+ # `selected_model.with_structured_output(schema, **kwargs)` and returns that
1072
+ # runnable.
1073
+ # Cast so callers still get the public return type.
1074
+ def with_structured_output(
1075
+ self,
1076
+ schema: dict[str, Any] | type[BaseModel],
1077
+ **kwargs: Any,
1078
+ ) -> Runnable[LanguageModelInput, dict[str, Any] | BaseModel]:
1079
+ return cast(
1080
+ "Runnable[LanguageModelInput, dict[str, Any] | BaseModel]",
1081
+ self.__getattr__("with_structured_output")(schema, **kwargs),
1082
+ )