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,1569 @@
1
+ """Base interface for traditional large language models (LLMs) to expose.
2
+
3
+ These are traditionally older models (newer models generally are chat models).
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ import asyncio
9
+ import builtins
10
+ import functools
11
+ import inspect
12
+ import json
13
+ import logging
14
+ from abc import ABC, abstractmethod
15
+ from collections.abc import AsyncIterator, Callable, Iterator, Sequence
16
+ from pathlib import Path
17
+ from typing import (
18
+ TYPE_CHECKING,
19
+ Any,
20
+ cast,
21
+ )
22
+
23
+ import yaml
24
+ from pydantic import ConfigDict
25
+ from tenacity import (
26
+ RetryCallState,
27
+ before_sleep_log,
28
+ retry,
29
+ retry_base,
30
+ retry_if_exception_type,
31
+ stop_after_attempt,
32
+ wait_exponential,
33
+ )
34
+ from typing_extensions import override
35
+
36
+ from coco_core._api import deprecated, suppress_coco_deprecation_warning
37
+ from coco_core.caches import BaseCache
38
+ from coco_core.callbacks import (
39
+ AsyncCallbackManager,
40
+ AsyncCallbackManagerForLLMRun,
41
+ BaseCallbackManager,
42
+ CallbackManager,
43
+ CallbackManagerForLLMRun,
44
+ Callbacks,
45
+ )
46
+ from coco_core.globals import get_llm_cache
47
+ from coco_core.language_models._utils import _filter_invocation_params_for_tracing
48
+ from coco_core.language_models.base import (
49
+ BaseLanguageModel,
50
+ LangSmithParams,
51
+ LanguageModelInput,
52
+ )
53
+ from coco_core.load import dumpd
54
+ from coco_core.messages import (
55
+ convert_to_messages,
56
+ )
57
+ from coco_core.outputs import Generation, GenerationChunk, LLMResult, RunInfo
58
+ from coco_core.prompt_values import ChatPromptValue, PromptValue, StringPromptValue
59
+ from coco_core.runnables import RunnableConfig, ensure_config, get_config_list
60
+ from coco_core.runnables.config import run_in_executor
61
+
62
+ if TYPE_CHECKING:
63
+ import builtins
64
+ import uuid
65
+
66
+ logger = logging.getLogger(__name__)
67
+
68
+ _background_tasks: set[asyncio.Task[None]] = set()
69
+
70
+
71
+ @functools.lru_cache
72
+ def _log_error_once(msg: str) -> None:
73
+ """Log an error once."""
74
+ logger.error(msg)
75
+
76
+
77
+ def create_base_retry_decorator(
78
+ error_types: list[type[BaseException]],
79
+ max_retries: int = 1,
80
+ run_manager: AsyncCallbackManagerForLLMRun | CallbackManagerForLLMRun | None = None,
81
+ ) -> Callable[[Any], Any]:
82
+ """Create a retry decorator for a given LLM and provided a list of error types.
83
+
84
+ Args:
85
+ error_types: List of error types to retry on.
86
+ max_retries: Number of retries.
87
+ run_manager: Callback manager for the run.
88
+
89
+ Returns:
90
+ A retry decorator.
91
+
92
+ Raises:
93
+ ValueError: If the cache is not set and cache is True.
94
+ """
95
+ logging_ = before_sleep_log(logger, logging.WARNING)
96
+
97
+ def _before_sleep(retry_state: RetryCallState) -> None:
98
+ logging_(retry_state)
99
+ if run_manager:
100
+ if isinstance(run_manager, AsyncCallbackManagerForLLMRun):
101
+ coro = run_manager.on_retry(retry_state)
102
+ try:
103
+ try:
104
+ loop = asyncio.get_event_loop()
105
+ except RuntimeError:
106
+ asyncio.run(coro)
107
+ else:
108
+ if loop.is_running():
109
+ task = loop.create_task(coro)
110
+ _background_tasks.add(task)
111
+ task.add_done_callback(_background_tasks.discard)
112
+ else:
113
+ asyncio.run(coro)
114
+ except Exception as e:
115
+ _log_error_once(f"Error in on_retry: {e}")
116
+ else:
117
+ run_manager.on_retry(retry_state)
118
+
119
+ min_seconds = 4
120
+ max_seconds = 10
121
+ # Wait 2^x * 1 second between each retry starting with
122
+ # 4 seconds, then up to 10 seconds, then 10 seconds afterwards
123
+ retry_instance: retry_base = retry_if_exception_type(error_types[0])
124
+ for error in error_types[1:]:
125
+ retry_instance |= retry_if_exception_type(error)
126
+ return retry(
127
+ reraise=True,
128
+ stop=stop_after_attempt(max_retries),
129
+ wait=wait_exponential(multiplier=1, min=min_seconds, max=max_seconds),
130
+ retry=retry_instance,
131
+ before_sleep=_before_sleep,
132
+ )
133
+
134
+
135
+ def _resolve_cache(*, cache: BaseCache | bool | None) -> BaseCache | None:
136
+ """Resolve the cache."""
137
+ llm_cache: BaseCache | None
138
+ if isinstance(cache, BaseCache):
139
+ llm_cache = cache
140
+ elif cache is None:
141
+ llm_cache = get_llm_cache()
142
+ elif cache is True:
143
+ llm_cache = get_llm_cache()
144
+ if llm_cache is None:
145
+ msg = (
146
+ "No global cache was configured. Use `set_llm_cache`."
147
+ "to set a global cache if you want to use a global cache."
148
+ "Otherwise either pass a cache object or set cache to False/None"
149
+ )
150
+ raise ValueError(msg)
151
+ elif cache is False:
152
+ llm_cache = None
153
+ else:
154
+ msg = f"Unsupported cache value {cache}" # type: ignore[unreachable]
155
+ raise ValueError(msg)
156
+ return llm_cache
157
+
158
+
159
+ def get_prompts(
160
+ params: dict[str, Any],
161
+ prompts: list[str],
162
+ cache: BaseCache | bool | None = None, # noqa: FBT001
163
+ ) -> tuple[dict[int, list[Generation]], str, list[int], list[str]]:
164
+ """Get prompts that are already cached.
165
+
166
+ Args:
167
+ params: Dictionary of parameters.
168
+ prompts: List of prompts.
169
+ cache: Cache object.
170
+
171
+ Returns:
172
+ A tuple of existing prompts, llm_string, missing prompt indexes,
173
+ and missing prompts.
174
+
175
+ Raises:
176
+ ValueError: If the cache is not set and cache is True.
177
+ """
178
+ llm_string = str(sorted(params.items()))
179
+ missing_prompts = []
180
+ missing_prompt_idxs = []
181
+ existing_prompts = {}
182
+
183
+ llm_cache = _resolve_cache(cache=cache)
184
+ for i, prompt in enumerate(prompts):
185
+ if llm_cache is not None:
186
+ cache_val = llm_cache.lookup(prompt, llm_string)
187
+ if isinstance(cache_val, list):
188
+ existing_prompts[i] = cache_val
189
+ continue
190
+ missing_prompts.append(prompt)
191
+ missing_prompt_idxs.append(i)
192
+ return existing_prompts, llm_string, missing_prompt_idxs, missing_prompts
193
+
194
+
195
+ async def aget_prompts(
196
+ params: dict[str, Any],
197
+ prompts: list[str],
198
+ cache: BaseCache | bool | None = None, # noqa: FBT001
199
+ ) -> tuple[dict[int, list[Generation]], str, list[int], list[str]]:
200
+ """Get prompts that are already cached. Async version.
201
+
202
+ Args:
203
+ params: Dictionary of parameters.
204
+ prompts: List of prompts.
205
+ cache: Cache object.
206
+
207
+ Returns:
208
+ A tuple of existing prompts, llm_string, missing prompt indexes,
209
+ and missing prompts.
210
+
211
+ Raises:
212
+ ValueError: If the cache is not set and cache is True.
213
+ """
214
+ llm_string = str(sorted(params.items()))
215
+ missing_prompts = []
216
+ missing_prompt_idxs = []
217
+ existing_prompts = {}
218
+ llm_cache = _resolve_cache(cache=cache)
219
+ for i, prompt in enumerate(prompts):
220
+ if llm_cache is not None:
221
+ cache_val = await llm_cache.alookup(prompt, llm_string)
222
+ if isinstance(cache_val, list):
223
+ existing_prompts[i] = cache_val
224
+ continue
225
+ missing_prompts.append(prompt)
226
+ missing_prompt_idxs.append(i)
227
+ return existing_prompts, llm_string, missing_prompt_idxs, missing_prompts
228
+
229
+
230
+ def update_cache(
231
+ cache: BaseCache | bool | None, # noqa: FBT001
232
+ existing_prompts: dict[int, list[Generation]],
233
+ llm_string: str,
234
+ missing_prompt_idxs: list[int],
235
+ new_results: LLMResult,
236
+ prompts: list[str],
237
+ ) -> dict[str, Any] | None:
238
+ """Update the cache and get the LLM output.
239
+
240
+ Args:
241
+ cache: Cache object.
242
+ existing_prompts: Dictionary of existing prompts.
243
+ llm_string: LLM string.
244
+ missing_prompt_idxs: List of missing prompt indexes.
245
+ new_results: LLMResult object.
246
+ prompts: List of prompts.
247
+
248
+ Returns:
249
+ LLM output.
250
+
251
+ Raises:
252
+ ValueError: If the cache is not set and cache is True.
253
+ """
254
+ llm_cache = _resolve_cache(cache=cache)
255
+ for i, result in enumerate(new_results.generations):
256
+ existing_prompts[missing_prompt_idxs[i]] = result
257
+ prompt = prompts[missing_prompt_idxs[i]]
258
+ if llm_cache is not None:
259
+ llm_cache.update(prompt, llm_string, result)
260
+ return new_results.llm_output
261
+
262
+
263
+ async def aupdate_cache(
264
+ cache: BaseCache | bool | None, # noqa: FBT001
265
+ existing_prompts: dict[int, list[Generation]],
266
+ llm_string: str,
267
+ missing_prompt_idxs: list[int],
268
+ new_results: LLMResult,
269
+ prompts: list[str],
270
+ ) -> dict[str, Any] | None:
271
+ """Update the cache and get the LLM output. Async version.
272
+
273
+ Args:
274
+ cache: Cache object.
275
+ existing_prompts: Dictionary of existing prompts.
276
+ llm_string: LLM string.
277
+ missing_prompt_idxs: List of missing prompt indexes.
278
+ new_results: LLMResult object.
279
+ prompts: List of prompts.
280
+
281
+ Returns:
282
+ LLM output.
283
+
284
+ Raises:
285
+ ValueError: If the cache is not set and cache is True.
286
+ """
287
+ llm_cache = _resolve_cache(cache=cache)
288
+ for i, result in enumerate(new_results.generations):
289
+ existing_prompts[missing_prompt_idxs[i]] = result
290
+ prompt = prompts[missing_prompt_idxs[i]]
291
+ if llm_cache is not None:
292
+ await llm_cache.aupdate(prompt, llm_string, result)
293
+ return new_results.llm_output
294
+
295
+
296
+ class BaseLLM(BaseLanguageModel[str], ABC):
297
+ """Base LLM abstract interface.
298
+
299
+ It should take in a prompt and return a string.
300
+ """
301
+
302
+ model_config = ConfigDict(
303
+ arbitrary_types_allowed=True,
304
+ )
305
+
306
+ @functools.cached_property
307
+ def _serialized(self) -> builtins.dict[str, Any]:
308
+ # self is always a Serializable object in this case, thus the result is
309
+ # guaranteed to be a dict since dumpd uses the default callback, which uses
310
+ # obj.to_json which always returns TypedDict subclasses
311
+ return cast("builtins.dict[str, Any]", dumpd(self))
312
+
313
+ # --- Runnable methods ---
314
+
315
+ @property
316
+ @override
317
+ def OutputType(self) -> type[str]:
318
+ """Get the output type for this `Runnable`."""
319
+ return str
320
+
321
+ def _convert_input(self, model_input: LanguageModelInput) -> PromptValue:
322
+ if isinstance(model_input, PromptValue):
323
+ return model_input
324
+ if isinstance(model_input, str):
325
+ return StringPromptValue(text=model_input)
326
+ if isinstance(model_input, Sequence):
327
+ return ChatPromptValue(messages=convert_to_messages(model_input))
328
+ msg = ( # type: ignore[unreachable]
329
+ f"Invalid input type {type(model_input)}. "
330
+ "Must be a PromptValue, str, or list of BaseMessages."
331
+ )
332
+ raise ValueError(msg)
333
+
334
+ def _get_ls_params(
335
+ self,
336
+ stop: list[str] | None = None,
337
+ **kwargs: Any,
338
+ ) -> LangSmithParams:
339
+ """Get standard params for tracing."""
340
+ # get default provider from class name
341
+ default_provider = self.__class__.__name__
342
+ default_provider = default_provider.removesuffix("LLM")
343
+ default_provider = default_provider.lower()
344
+
345
+ ls_params = LangSmithParams(ls_provider=default_provider, ls_model_type="llm")
346
+ if stop:
347
+ ls_params["ls_stop"] = stop
348
+
349
+ # model
350
+ if "model" in kwargs and isinstance(kwargs["model"], str):
351
+ ls_params["ls_model_name"] = kwargs["model"]
352
+ elif hasattr(self, "model") and isinstance(self.model, str):
353
+ ls_params["ls_model_name"] = self.model
354
+ elif hasattr(self, "model_name") and isinstance(self.model_name, str):
355
+ ls_params["ls_model_name"] = self.model_name
356
+
357
+ # temperature
358
+ if "temperature" in kwargs and isinstance(kwargs["temperature"], (int, float)):
359
+ ls_params["ls_temperature"] = kwargs["temperature"]
360
+ elif hasattr(self, "temperature") and isinstance(
361
+ self.temperature, (int, float)
362
+ ):
363
+ ls_params["ls_temperature"] = self.temperature
364
+
365
+ # max_tokens
366
+ if "max_tokens" in kwargs and isinstance(kwargs["max_tokens"], int):
367
+ ls_params["ls_max_tokens"] = kwargs["max_tokens"]
368
+ elif hasattr(self, "max_tokens") and isinstance(self.max_tokens, int):
369
+ ls_params["ls_max_tokens"] = self.max_tokens
370
+
371
+ return ls_params
372
+
373
+ @override
374
+ def invoke(
375
+ self,
376
+ input: LanguageModelInput,
377
+ config: RunnableConfig | None = None,
378
+ *,
379
+ stop: list[str] | None = None,
380
+ **kwargs: Any,
381
+ ) -> str:
382
+ config = ensure_config(config)
383
+ return (
384
+ self.generate_prompt(
385
+ [self._convert_input(input)],
386
+ stop=stop,
387
+ callbacks=config.get("callbacks"),
388
+ tags=config.get("tags"),
389
+ metadata=config.get("metadata"),
390
+ run_name=config.get("run_name"),
391
+ run_id=config.pop("run_id", None),
392
+ **kwargs,
393
+ )
394
+ .generations[0][0]
395
+ .text
396
+ )
397
+
398
+ @override
399
+ async def ainvoke(
400
+ self,
401
+ input: LanguageModelInput,
402
+ config: RunnableConfig | None = None,
403
+ *,
404
+ stop: list[str] | None = None,
405
+ **kwargs: Any,
406
+ ) -> str:
407
+ config = ensure_config(config)
408
+ llm_result = await self.agenerate_prompt(
409
+ [self._convert_input(input)],
410
+ stop=stop,
411
+ callbacks=config.get("callbacks"),
412
+ tags=config.get("tags"),
413
+ metadata=config.get("metadata"),
414
+ run_name=config.get("run_name"),
415
+ run_id=config.pop("run_id", None),
416
+ **kwargs,
417
+ )
418
+ return llm_result.generations[0][0].text
419
+
420
+ @override
421
+ def batch(
422
+ self,
423
+ inputs: list[LanguageModelInput],
424
+ config: RunnableConfig | list[RunnableConfig] | None = None,
425
+ *,
426
+ return_exceptions: bool = False,
427
+ **kwargs: Any,
428
+ ) -> list[str]:
429
+ if not inputs:
430
+ return []
431
+
432
+ config = get_config_list(config, len(inputs))
433
+ max_concurrency = config[0].get("max_concurrency")
434
+
435
+ if max_concurrency is None:
436
+ try:
437
+ llm_result = self.generate_prompt(
438
+ [self._convert_input(input_) for input_ in inputs],
439
+ callbacks=[c.get("callbacks") for c in config],
440
+ tags=[c.get("tags") for c in config],
441
+ metadata=[c.get("metadata") for c in config],
442
+ run_name=[c.get("run_name") for c in config],
443
+ **kwargs,
444
+ )
445
+ return [g[0].text for g in llm_result.generations]
446
+ except Exception as e:
447
+ if return_exceptions:
448
+ return cast("list[str]", [e for _ in inputs])
449
+ raise
450
+ else:
451
+ batches = [
452
+ inputs[i : i + max_concurrency]
453
+ for i in range(0, len(inputs), max_concurrency)
454
+ ]
455
+ config = [{**c, "max_concurrency": None} for c in config]
456
+ return [
457
+ output
458
+ for i, batch in enumerate(batches)
459
+ for output in self.batch(
460
+ batch,
461
+ config=config[i * max_concurrency : (i + 1) * max_concurrency],
462
+ return_exceptions=return_exceptions,
463
+ **kwargs,
464
+ )
465
+ ]
466
+
467
+ @override
468
+ async def abatch(
469
+ self,
470
+ inputs: list[LanguageModelInput],
471
+ config: RunnableConfig | list[RunnableConfig] | None = None,
472
+ *,
473
+ return_exceptions: bool = False,
474
+ **kwargs: Any,
475
+ ) -> list[str]:
476
+ if not inputs:
477
+ return []
478
+ config = get_config_list(config, len(inputs))
479
+ max_concurrency = config[0].get("max_concurrency")
480
+
481
+ if max_concurrency is None:
482
+ try:
483
+ llm_result = await self.agenerate_prompt(
484
+ [self._convert_input(input_) for input_ in inputs],
485
+ callbacks=[c.get("callbacks") for c in config],
486
+ tags=[c.get("tags") for c in config],
487
+ metadata=[c.get("metadata") for c in config],
488
+ run_name=[c.get("run_name") for c in config],
489
+ **kwargs,
490
+ )
491
+ return [g[0].text for g in llm_result.generations]
492
+ except Exception as e:
493
+ if return_exceptions:
494
+ return cast("list[str]", [e for _ in inputs])
495
+ raise
496
+ else:
497
+ batches = [
498
+ inputs[i : i + max_concurrency]
499
+ for i in range(0, len(inputs), max_concurrency)
500
+ ]
501
+ config = [{**c, "max_concurrency": None} for c in config]
502
+ return [
503
+ output
504
+ for i, batch in enumerate(batches)
505
+ for output in await self.abatch(
506
+ batch,
507
+ config=config[i * max_concurrency : (i + 1) * max_concurrency],
508
+ return_exceptions=return_exceptions,
509
+ **kwargs,
510
+ )
511
+ ]
512
+
513
+ @override
514
+ def stream(
515
+ self,
516
+ input: LanguageModelInput,
517
+ config: RunnableConfig | None = None,
518
+ *,
519
+ stop: list[str] | None = None,
520
+ **kwargs: Any,
521
+ ) -> Iterator[str]:
522
+ if type(self)._stream == BaseLLM._stream: # noqa: SLF001
523
+ # model doesn't implement streaming, so use default implementation
524
+ yield self.invoke(input, config=config, stop=stop, **kwargs)
525
+ else:
526
+ prompt = self._convert_input(input).to_string()
527
+ config = ensure_config(config)
528
+ params = self._dict_for_compat()
529
+ params["stop"] = stop
530
+ params = {**params, **kwargs}
531
+ options = {"stop": stop}
532
+ inheritable_metadata = {
533
+ **(config.get("metadata") or {}),
534
+ **self._get_ls_params_with_defaults(stop=stop, **kwargs),
535
+ }
536
+ callback_manager = CallbackManager.configure(
537
+ config.get("callbacks"),
538
+ self.callbacks,
539
+ self.verbose,
540
+ config.get("tags"),
541
+ self.tags,
542
+ inheritable_metadata,
543
+ self.metadata,
544
+ langsmith_inheritable_metadata=_filter_invocation_params_for_tracing(
545
+ params
546
+ ),
547
+ )
548
+ (run_manager,) = callback_manager.on_llm_start(
549
+ self._serialized,
550
+ [prompt],
551
+ invocation_params=params,
552
+ options=options,
553
+ name=config.get("run_name"),
554
+ run_id=config.pop("run_id", None),
555
+ batch_size=1,
556
+ )
557
+ generation: GenerationChunk | None = None
558
+ try:
559
+ for chunk in self._stream(
560
+ prompt, stop=stop, run_manager=run_manager, **kwargs
561
+ ):
562
+ yield chunk.text
563
+ if generation is None:
564
+ generation = chunk
565
+ else:
566
+ generation += chunk
567
+ except BaseException as e:
568
+ run_manager.on_llm_error(
569
+ e,
570
+ response=LLMResult(
571
+ generations=[[generation]] if generation else []
572
+ ),
573
+ )
574
+ raise
575
+
576
+ if generation is None:
577
+ err = ValueError("No generation chunks were returned")
578
+ run_manager.on_llm_error(err, response=LLMResult(generations=[]))
579
+ raise err
580
+
581
+ run_manager.on_llm_end(LLMResult(generations=[[generation]]))
582
+
583
+ @override
584
+ async def astream(
585
+ self,
586
+ input: LanguageModelInput,
587
+ config: RunnableConfig | None = None,
588
+ *,
589
+ stop: list[str] | None = None,
590
+ **kwargs: Any,
591
+ ) -> AsyncIterator[str]:
592
+ if (
593
+ type(self)._astream is BaseLLM._astream # noqa: SLF001
594
+ and type(self)._stream is BaseLLM._stream # noqa: SLF001
595
+ ):
596
+ yield await self.ainvoke(input, config=config, stop=stop, **kwargs)
597
+ return
598
+
599
+ prompt = self._convert_input(input).to_string()
600
+ config = ensure_config(config)
601
+ params = self._dict_for_compat()
602
+ params["stop"] = stop
603
+ params = {**params, **kwargs}
604
+ options = {"stop": stop}
605
+ inheritable_metadata = {
606
+ **(config.get("metadata") or {}),
607
+ **self._get_ls_params_with_defaults(stop=stop, **kwargs),
608
+ }
609
+ callback_manager = AsyncCallbackManager.configure(
610
+ config.get("callbacks"),
611
+ self.callbacks,
612
+ self.verbose,
613
+ config.get("tags"),
614
+ self.tags,
615
+ inheritable_metadata,
616
+ self.metadata,
617
+ langsmith_inheritable_metadata=_filter_invocation_params_for_tracing(
618
+ params
619
+ ),
620
+ )
621
+ (run_manager,) = await callback_manager.on_llm_start(
622
+ self._serialized,
623
+ [prompt],
624
+ invocation_params=params,
625
+ options=options,
626
+ name=config.get("run_name"),
627
+ run_id=config.pop("run_id", None),
628
+ batch_size=1,
629
+ )
630
+ generation: GenerationChunk | None = None
631
+ try:
632
+ async for chunk in self._astream(
633
+ prompt,
634
+ stop=stop,
635
+ run_manager=run_manager,
636
+ **kwargs,
637
+ ):
638
+ yield chunk.text
639
+ if generation is None:
640
+ generation = chunk
641
+ else:
642
+ generation += chunk
643
+ except BaseException as e:
644
+ await run_manager.on_llm_error(
645
+ e,
646
+ response=LLMResult(generations=[[generation]] if generation else []),
647
+ )
648
+ raise
649
+
650
+ if generation is None:
651
+ err = ValueError("No generation chunks were returned")
652
+ await run_manager.on_llm_error(err, response=LLMResult(generations=[]))
653
+ raise err
654
+
655
+ await run_manager.on_llm_end(LLMResult(generations=[[generation]]))
656
+
657
+ # --- Custom methods ---
658
+
659
+ @abstractmethod
660
+ def _generate(
661
+ self,
662
+ prompts: list[str],
663
+ stop: list[str] | None = None,
664
+ run_manager: CallbackManagerForLLMRun | None = None,
665
+ **kwargs: Any,
666
+ ) -> LLMResult:
667
+ """Run the LLM on the given prompts.
668
+
669
+ Args:
670
+ prompts: The prompts to generate from.
671
+ stop: Stop words to use when generating.
672
+
673
+ Model output is cut off at the first occurrence of any of these
674
+ substrings.
675
+
676
+ If stop tokens are not supported consider raising `NotImplementedError`.
677
+ run_manager: Callback manager for the run.
678
+
679
+ Returns:
680
+ The LLM result.
681
+ """
682
+
683
+ async def _agenerate(
684
+ self,
685
+ prompts: list[str],
686
+ stop: list[str] | None = None,
687
+ run_manager: AsyncCallbackManagerForLLMRun | None = None,
688
+ **kwargs: Any,
689
+ ) -> LLMResult:
690
+ """Run the LLM on the given prompts.
691
+
692
+ Args:
693
+ prompts: The prompts to generate from.
694
+ stop: Stop words to use when generating.
695
+
696
+ Model output is cut off at the first occurrence of any of these
697
+ substrings.
698
+
699
+ If stop tokens are not supported consider raising `NotImplementedError`.
700
+ run_manager: Callback manager for the run.
701
+
702
+ Returns:
703
+ The LLM result.
704
+ """
705
+ return await run_in_executor(
706
+ None,
707
+ self._generate,
708
+ prompts,
709
+ stop,
710
+ run_manager.get_sync() if run_manager else None,
711
+ **kwargs,
712
+ )
713
+
714
+ def _stream(
715
+ self,
716
+ prompt: str,
717
+ stop: list[str] | None = None,
718
+ run_manager: CallbackManagerForLLMRun | None = None,
719
+ **kwargs: Any,
720
+ ) -> Iterator[GenerationChunk]:
721
+ """Stream the LLM on the given prompt.
722
+
723
+ This method should be overridden by subclasses that support streaming.
724
+
725
+ If not implemented, the default behavior of calls to stream will be to
726
+ fallback to the non-streaming version of the model and return
727
+ the output as a single chunk.
728
+
729
+ Args:
730
+ prompt: The prompt to generate from.
731
+ stop: Stop words to use when generating.
732
+
733
+ Model output is cut off at the first occurrence of any of these
734
+ substrings.
735
+ run_manager: Callback manager for the run.
736
+ **kwargs: Arbitrary additional keyword arguments.
737
+
738
+ These are usually passed to the model provider API call.
739
+
740
+ Yields:
741
+ Generation chunks.
742
+ """
743
+ raise NotImplementedError
744
+
745
+ async def _astream(
746
+ self,
747
+ prompt: str,
748
+ stop: list[str] | None = None,
749
+ run_manager: AsyncCallbackManagerForLLMRun | None = None,
750
+ **kwargs: Any,
751
+ ) -> AsyncIterator[GenerationChunk]:
752
+ """An async version of the _stream method.
753
+
754
+ The default implementation uses the synchronous _stream method and wraps it in
755
+ an async iterator. Subclasses that need to provide a true async implementation
756
+ should override this method.
757
+
758
+ Args:
759
+ prompt: The prompt to generate from.
760
+ stop: Stop words to use when generating.
761
+
762
+ Model output is cut off at the first occurrence of any of these
763
+ substrings.
764
+ run_manager: Callback manager for the run.
765
+ **kwargs: Arbitrary additional keyword arguments.
766
+
767
+ These are usually passed to the model provider API call.
768
+
769
+ Yields:
770
+ Generation chunks.
771
+ """
772
+ iterator = await run_in_executor(
773
+ None,
774
+ self._stream,
775
+ prompt,
776
+ stop,
777
+ run_manager.get_sync() if run_manager else None,
778
+ **kwargs,
779
+ )
780
+ done = object()
781
+ while True:
782
+ item = await run_in_executor(
783
+ None,
784
+ next,
785
+ iterator,
786
+ done,
787
+ )
788
+ if item is done:
789
+ break
790
+ yield item # type: ignore[misc]
791
+
792
+ @override
793
+ def generate_prompt(
794
+ self,
795
+ prompts: list[PromptValue],
796
+ stop: list[str] | None = None,
797
+ callbacks: Callbacks | list[Callbacks] | None = None,
798
+ **kwargs: Any,
799
+ ) -> LLMResult:
800
+ prompt_strings = [p.to_string() for p in prompts]
801
+ return self.generate(prompt_strings, stop=stop, callbacks=callbacks, **kwargs)
802
+
803
+ @override
804
+ async def agenerate_prompt(
805
+ self,
806
+ prompts: list[PromptValue],
807
+ stop: list[str] | None = None,
808
+ callbacks: Callbacks | list[Callbacks] | None = None,
809
+ **kwargs: Any,
810
+ ) -> LLMResult:
811
+ prompt_strings = [p.to_string() for p in prompts]
812
+ return await self.agenerate(
813
+ prompt_strings, stop=stop, callbacks=callbacks, **kwargs
814
+ )
815
+
816
+ def _generate_helper(
817
+ self,
818
+ prompts: list[str],
819
+ stop: list[str] | None,
820
+ run_managers: list[CallbackManagerForLLMRun],
821
+ *,
822
+ new_arg_supported: bool,
823
+ **kwargs: Any,
824
+ ) -> LLMResult:
825
+ try:
826
+ output = (
827
+ self._generate(
828
+ prompts,
829
+ stop=stop,
830
+ # TODO: support multiple run managers
831
+ run_manager=run_managers[0] if run_managers else None,
832
+ **kwargs,
833
+ )
834
+ if new_arg_supported
835
+ else self._generate(prompts, stop=stop)
836
+ )
837
+ except BaseException as e:
838
+ for run_manager in run_managers:
839
+ run_manager.on_llm_error(e, response=LLMResult(generations=[]))
840
+ raise
841
+ flattened_outputs = output.flatten()
842
+ for manager, flattened_output in zip(
843
+ run_managers, flattened_outputs, strict=False
844
+ ):
845
+ manager.on_llm_end(flattened_output)
846
+ if run_managers:
847
+ output.run = [
848
+ RunInfo(run_id=run_manager.run_id) for run_manager in run_managers
849
+ ]
850
+ return output
851
+
852
+ def generate(
853
+ self,
854
+ prompts: list[str],
855
+ stop: list[str] | None = None,
856
+ callbacks: Callbacks | list[Callbacks] | None = None,
857
+ *,
858
+ tags: list[str] | list[list[str]] | None = None,
859
+ metadata: builtins.dict[str, Any] | list[builtins.dict[str, Any]] | None = None,
860
+ run_name: str | list[str] | None = None,
861
+ run_id: uuid.UUID | list[uuid.UUID | None] | None = None,
862
+ **kwargs: Any,
863
+ ) -> LLMResult:
864
+ """Pass a sequence of prompts to a model and return generations.
865
+
866
+ This method should make use of batched calls for models that expose a batched
867
+ API.
868
+
869
+ Use this method when you want to:
870
+
871
+ 1. Take advantage of batched calls,
872
+ 2. Need more output from the model than just the top generated value,
873
+ 3. Are building chains that are agnostic to the underlying language model
874
+ type (e.g., pure text completion models vs chat models).
875
+
876
+ Args:
877
+ prompts: List of string prompts.
878
+ stop: Stop words to use when generating.
879
+
880
+ Model output is cut off at the first occurrence of any of these
881
+ substrings.
882
+ callbacks: `Callbacks` to pass through.
883
+
884
+ Used for executing additional functionality, such as logging or
885
+ streaming, throughout generation.
886
+ tags: List of tags to associate with each prompt. If provided, the length
887
+ of the list must match the length of the prompts list.
888
+ metadata: List of metadata dictionaries to associate with each prompt. If
889
+ provided, the length of the list must match the length of the prompts
890
+ list.
891
+ run_name: List of run names to associate with each prompt. If provided, the
892
+ length of the list must match the length of the prompts list.
893
+ run_id: List of run IDs to associate with each prompt. If provided, the
894
+ length of the list must match the length of the prompts list.
895
+ **kwargs: Arbitrary additional keyword arguments.
896
+
897
+ These are usually passed to the model provider API call.
898
+
899
+ Raises:
900
+ ValueError: If prompts is not a list.
901
+ ValueError: If the length of `callbacks`, `tags`, `metadata`, or
902
+ `run_name` (if provided) does not match the length of prompts.
903
+
904
+ Returns:
905
+ An `LLMResult`, which contains a list of candidate `Generations` for each
906
+ input prompt and additional model provider-specific output.
907
+ """
908
+ if not isinstance(prompts, list):
909
+ msg = ( # type: ignore[unreachable]
910
+ "Argument 'prompts' is expected to be of type list[str], received"
911
+ f" argument of type {type(prompts)}."
912
+ )
913
+ raise ValueError(msg) # noqa: TRY004
914
+ # Create callback managers
915
+ if isinstance(metadata, list):
916
+ metadata = [
917
+ {
918
+ **(meta or {}),
919
+ **self._get_ls_params_with_defaults(stop=stop, **kwargs),
920
+ }
921
+ for meta in metadata
922
+ ]
923
+ elif isinstance(metadata, dict):
924
+ metadata = {
925
+ **(metadata or {}),
926
+ **self._get_ls_params_with_defaults(stop=stop, **kwargs),
927
+ }
928
+ if (
929
+ isinstance(callbacks, list)
930
+ and callbacks
931
+ and (
932
+ isinstance(callbacks[0], (list, BaseCallbackManager))
933
+ or callbacks[0] is None
934
+ )
935
+ ):
936
+ # We've received a list of callbacks args to apply to each input
937
+ if len(callbacks) != len(prompts):
938
+ msg = "callbacks must be the same length as prompts"
939
+ raise ValueError(msg)
940
+ if tags is not None and not (
941
+ isinstance(tags, list) and len(tags) == len(prompts)
942
+ ):
943
+ msg = "tags must be a list of the same length as prompts"
944
+ raise ValueError(msg)
945
+ if metadata is not None and not (
946
+ isinstance(metadata, list) and len(metadata) == len(prompts)
947
+ ):
948
+ msg = "metadata must be a list of the same length as prompts"
949
+ raise ValueError(msg)
950
+ if run_name is not None and not (
951
+ isinstance(run_name, list) and len(run_name) == len(prompts)
952
+ ):
953
+ msg = "run_name must be a list of the same length as prompts"
954
+ raise ValueError(msg)
955
+ tags_list = cast("list[list[str] | None]", tags or ([None] * len(prompts)))
956
+ metadata_list = cast(
957
+ "list[builtins.dict[str, Any] | None]",
958
+ metadata or ([{}] * len(prompts)),
959
+ )
960
+ run_name_list = run_name or cast(
961
+ "list[str | None]", ([None] * len(prompts))
962
+ )
963
+ params = self._dict_for_compat()
964
+ params["stop"] = stop
965
+ callback_managers = [
966
+ CallbackManager.configure(
967
+ callback,
968
+ self.callbacks,
969
+ self.verbose,
970
+ tag,
971
+ self.tags,
972
+ meta,
973
+ self.metadata,
974
+ langsmith_inheritable_metadata=_filter_invocation_params_for_tracing(
975
+ params
976
+ ),
977
+ )
978
+ for callback, tag, meta in zip(
979
+ callbacks, tags_list, metadata_list, strict=False
980
+ )
981
+ ]
982
+ else:
983
+ # We've received a single callbacks arg to apply to all inputs
984
+ params = self._dict_for_compat()
985
+ params["stop"] = stop
986
+ callback_managers = [
987
+ CallbackManager.configure(
988
+ cast("Callbacks", callbacks),
989
+ self.callbacks,
990
+ self.verbose,
991
+ cast("list[str]", tags),
992
+ self.tags,
993
+ cast("builtins.dict[str, Any]", metadata),
994
+ self.metadata,
995
+ langsmith_inheritable_metadata=_filter_invocation_params_for_tracing(
996
+ params
997
+ ),
998
+ )
999
+ ] * len(prompts)
1000
+ run_name_list = [cast("str | None", run_name)] * len(prompts)
1001
+ run_ids_list = self._get_run_ids_list(run_id, prompts)
1002
+ options = {"stop": stop}
1003
+ (
1004
+ existing_prompts,
1005
+ llm_string,
1006
+ missing_prompt_idxs,
1007
+ missing_prompts,
1008
+ ) = get_prompts(params, prompts, self.cache)
1009
+ new_arg_supported = inspect.signature(self._generate).parameters.get(
1010
+ "run_manager"
1011
+ )
1012
+ if (self.cache is None and get_llm_cache() is None) or self.cache is False:
1013
+ run_managers = [
1014
+ callback_manager.on_llm_start(
1015
+ self._serialized,
1016
+ [prompt],
1017
+ invocation_params=params,
1018
+ options=options,
1019
+ name=run_name,
1020
+ batch_size=len(prompts),
1021
+ run_id=run_id_,
1022
+ )[0]
1023
+ for callback_manager, prompt, run_name, run_id_ in zip(
1024
+ callback_managers,
1025
+ prompts,
1026
+ run_name_list,
1027
+ run_ids_list,
1028
+ strict=False,
1029
+ )
1030
+ ]
1031
+ return self._generate_helper(
1032
+ prompts,
1033
+ stop,
1034
+ run_managers,
1035
+ new_arg_supported=bool(new_arg_supported),
1036
+ **kwargs,
1037
+ )
1038
+ if len(missing_prompts) > 0:
1039
+ run_managers = [
1040
+ callback_managers[idx].on_llm_start(
1041
+ self._serialized,
1042
+ [prompts[idx]],
1043
+ invocation_params=params,
1044
+ options=options,
1045
+ name=run_name_list[idx],
1046
+ batch_size=len(missing_prompts),
1047
+ )[0]
1048
+ for idx in missing_prompt_idxs
1049
+ ]
1050
+ new_results = self._generate_helper(
1051
+ missing_prompts,
1052
+ stop,
1053
+ run_managers,
1054
+ new_arg_supported=bool(new_arg_supported),
1055
+ **kwargs,
1056
+ )
1057
+ llm_output = update_cache(
1058
+ self.cache,
1059
+ existing_prompts,
1060
+ llm_string,
1061
+ missing_prompt_idxs,
1062
+ new_results,
1063
+ prompts,
1064
+ )
1065
+ run_info = (
1066
+ [RunInfo(run_id=run_manager.run_id) for run_manager in run_managers]
1067
+ if run_managers
1068
+ else None
1069
+ )
1070
+ else:
1071
+ llm_output = {}
1072
+ run_info = None
1073
+ generations = [existing_prompts[i] for i in range(len(prompts))]
1074
+ return LLMResult(generations=generations, llm_output=llm_output, run=run_info)
1075
+
1076
+ @staticmethod
1077
+ def _get_run_ids_list(
1078
+ run_id: uuid.UUID | list[uuid.UUID | None] | None, prompts: list[str]
1079
+ ) -> list[uuid.UUID | None]:
1080
+ if run_id is None:
1081
+ return [None] * len(prompts)
1082
+ if isinstance(run_id, list):
1083
+ if len(run_id) != len(prompts):
1084
+ msg = (
1085
+ "Number of manually provided run_id's does not match batch length."
1086
+ f" {len(run_id)} != {len(prompts)}"
1087
+ )
1088
+ raise ValueError(msg)
1089
+ return run_id
1090
+ return [run_id] + [None] * (len(prompts) - 1)
1091
+
1092
+ async def _agenerate_helper(
1093
+ self,
1094
+ prompts: list[str],
1095
+ stop: list[str] | None,
1096
+ run_managers: list[AsyncCallbackManagerForLLMRun],
1097
+ *,
1098
+ new_arg_supported: bool,
1099
+ **kwargs: Any,
1100
+ ) -> LLMResult:
1101
+ try:
1102
+ output = (
1103
+ await self._agenerate(
1104
+ prompts,
1105
+ stop=stop,
1106
+ run_manager=run_managers[0] if run_managers else None,
1107
+ **kwargs,
1108
+ )
1109
+ if new_arg_supported
1110
+ else await self._agenerate(prompts, stop=stop)
1111
+ )
1112
+ except BaseException as e:
1113
+ await asyncio.gather(
1114
+ *[
1115
+ run_manager.on_llm_error(e, response=LLMResult(generations=[]))
1116
+ for run_manager in run_managers
1117
+ ]
1118
+ )
1119
+ raise
1120
+ flattened_outputs = output.flatten()
1121
+ await asyncio.gather(
1122
+ *[
1123
+ run_manager.on_llm_end(flattened_output)
1124
+ for run_manager, flattened_output in zip(
1125
+ run_managers, flattened_outputs, strict=False
1126
+ )
1127
+ ]
1128
+ )
1129
+ if run_managers:
1130
+ output.run = [
1131
+ RunInfo(run_id=run_manager.run_id) for run_manager in run_managers
1132
+ ]
1133
+ return output
1134
+
1135
+ async def agenerate(
1136
+ self,
1137
+ prompts: list[str],
1138
+ stop: list[str] | None = None,
1139
+ callbacks: Callbacks | list[Callbacks] | None = None,
1140
+ *,
1141
+ tags: list[str] | list[list[str]] | None = None,
1142
+ metadata: builtins.dict[str, Any] | list[builtins.dict[str, Any]] | None = None,
1143
+ run_name: str | list[str] | None = None,
1144
+ run_id: uuid.UUID | list[uuid.UUID | None] | None = None,
1145
+ **kwargs: Any,
1146
+ ) -> LLMResult:
1147
+ """Asynchronously pass a sequence of prompts to a model and return generations.
1148
+
1149
+ This method should make use of batched calls for models that expose a batched
1150
+ API.
1151
+
1152
+ Use this method when you want to:
1153
+
1154
+ 1. Take advantage of batched calls,
1155
+ 2. Need more output from the model than just the top generated value,
1156
+ 3. Are building chains that are agnostic to the underlying language model
1157
+ type (e.g., pure text completion models vs chat models).
1158
+
1159
+ Args:
1160
+ prompts: List of string prompts.
1161
+ stop: Stop words to use when generating.
1162
+
1163
+ Model output is cut off at the first occurrence of any of these
1164
+ substrings.
1165
+ callbacks: `Callbacks` to pass through.
1166
+
1167
+ Used for executing additional functionality, such as logging or
1168
+ streaming, throughout generation.
1169
+ tags: List of tags to associate with each prompt. If provided, the length
1170
+ of the list must match the length of the prompts list.
1171
+ metadata: List of metadata dictionaries to associate with each prompt. If
1172
+ provided, the length of the list must match the length of the prompts
1173
+ list.
1174
+ run_name: List of run names to associate with each prompt. If provided, the
1175
+ length of the list must match the length of the prompts list.
1176
+ run_id: List of run IDs to associate with each prompt. If provided, the
1177
+ length of the list must match the length of the prompts list.
1178
+ **kwargs: Arbitrary additional keyword arguments.
1179
+
1180
+ These are usually passed to the model provider API call.
1181
+
1182
+ Raises:
1183
+ ValueError: If the length of `callbacks`, `tags`, `metadata`, or
1184
+ `run_name` (if provided) does not match the length of prompts.
1185
+
1186
+ Returns:
1187
+ An `LLMResult`, which contains a list of candidate `Generations` for each
1188
+ input prompt and additional model provider-specific output.
1189
+ """
1190
+ if isinstance(metadata, list):
1191
+ metadata = [
1192
+ {
1193
+ **(meta or {}),
1194
+ **self._get_ls_params_with_defaults(stop=stop, **kwargs),
1195
+ }
1196
+ for meta in metadata
1197
+ ]
1198
+ elif isinstance(metadata, dict):
1199
+ metadata = {
1200
+ **(metadata or {}),
1201
+ **self._get_ls_params_with_defaults(stop=stop, **kwargs),
1202
+ }
1203
+ # Create callback managers
1204
+ if isinstance(callbacks, list) and (
1205
+ isinstance(callbacks[0], (list, BaseCallbackManager))
1206
+ or callbacks[0] is None
1207
+ ):
1208
+ # We've received a list of callbacks args to apply to each input
1209
+ if len(callbacks) != len(prompts):
1210
+ msg = "callbacks must be the same length as prompts"
1211
+ raise ValueError(msg)
1212
+ if tags is not None and not (
1213
+ isinstance(tags, list) and len(tags) == len(prompts)
1214
+ ):
1215
+ msg = "tags must be a list of the same length as prompts"
1216
+ raise ValueError(msg)
1217
+ if metadata is not None and not (
1218
+ isinstance(metadata, list) and len(metadata) == len(prompts)
1219
+ ):
1220
+ msg = "metadata must be a list of the same length as prompts"
1221
+ raise ValueError(msg)
1222
+ if run_name is not None and not (
1223
+ isinstance(run_name, list) and len(run_name) == len(prompts)
1224
+ ):
1225
+ msg = "run_name must be a list of the same length as prompts"
1226
+ raise ValueError(msg)
1227
+ tags_list = cast("list[list[str] | None]", tags or ([None] * len(prompts)))
1228
+ metadata_list = cast(
1229
+ "list[builtins.dict[str, Any] | None]",
1230
+ metadata or ([{}] * len(prompts)),
1231
+ )
1232
+ run_name_list = run_name or cast(
1233
+ "list[str | None]", ([None] * len(prompts))
1234
+ )
1235
+ params = self._dict_for_compat()
1236
+ params["stop"] = stop
1237
+ callback_managers = [
1238
+ AsyncCallbackManager.configure(
1239
+ callback,
1240
+ self.callbacks,
1241
+ self.verbose,
1242
+ tag,
1243
+ self.tags,
1244
+ meta,
1245
+ self.metadata,
1246
+ langsmith_inheritable_metadata=_filter_invocation_params_for_tracing(
1247
+ params
1248
+ ),
1249
+ )
1250
+ for callback, tag, meta in zip(
1251
+ callbacks, tags_list, metadata_list, strict=False
1252
+ )
1253
+ ]
1254
+ else:
1255
+ # We've received a single callbacks arg to apply to all inputs
1256
+ params = self._dict_for_compat()
1257
+ params["stop"] = stop
1258
+ callback_managers = [
1259
+ AsyncCallbackManager.configure(
1260
+ callbacks,
1261
+ self.callbacks,
1262
+ self.verbose,
1263
+ cast("list[str]", tags),
1264
+ self.tags,
1265
+ cast("builtins.dict[str, Any]", metadata),
1266
+ self.metadata,
1267
+ langsmith_inheritable_metadata=_filter_invocation_params_for_tracing(
1268
+ params
1269
+ ),
1270
+ )
1271
+ ] * len(prompts)
1272
+ run_name_list = [cast("str | None", run_name)] * len(prompts)
1273
+ run_ids_list = self._get_run_ids_list(run_id, prompts)
1274
+ options = {"stop": stop}
1275
+ (
1276
+ existing_prompts,
1277
+ llm_string,
1278
+ missing_prompt_idxs,
1279
+ missing_prompts,
1280
+ ) = await aget_prompts(params, prompts, self.cache)
1281
+
1282
+ # Verify whether the cache is set, and if the cache is set,
1283
+ # verify whether the cache is available.
1284
+ new_arg_supported = inspect.signature(self._agenerate).parameters.get(
1285
+ "run_manager"
1286
+ )
1287
+ if (self.cache is None and get_llm_cache() is None) or self.cache is False:
1288
+ run_managers = await asyncio.gather(
1289
+ *[
1290
+ callback_manager.on_llm_start(
1291
+ self._serialized,
1292
+ [prompt],
1293
+ invocation_params=params,
1294
+ options=options,
1295
+ name=run_name,
1296
+ batch_size=len(prompts),
1297
+ run_id=run_id_,
1298
+ )
1299
+ for callback_manager, prompt, run_name, run_id_ in zip(
1300
+ callback_managers,
1301
+ prompts,
1302
+ run_name_list,
1303
+ run_ids_list,
1304
+ strict=False,
1305
+ )
1306
+ ]
1307
+ )
1308
+ run_managers = [r[0] for r in run_managers] # type: ignore[misc]
1309
+ return await self._agenerate_helper(
1310
+ prompts,
1311
+ stop,
1312
+ run_managers, # type: ignore[arg-type]
1313
+ new_arg_supported=bool(new_arg_supported),
1314
+ **kwargs,
1315
+ )
1316
+ if len(missing_prompts) > 0:
1317
+ run_managers = await asyncio.gather(
1318
+ *[
1319
+ callback_managers[idx].on_llm_start(
1320
+ self._serialized,
1321
+ [prompts[idx]],
1322
+ invocation_params=params,
1323
+ options=options,
1324
+ name=run_name_list[idx],
1325
+ batch_size=len(missing_prompts),
1326
+ )
1327
+ for idx in missing_prompt_idxs
1328
+ ]
1329
+ )
1330
+ run_managers = [r[0] for r in run_managers] # type: ignore[misc]
1331
+ new_results = await self._agenerate_helper(
1332
+ missing_prompts,
1333
+ stop,
1334
+ run_managers, # type: ignore[arg-type]
1335
+ new_arg_supported=bool(new_arg_supported),
1336
+ **kwargs,
1337
+ )
1338
+ llm_output = await aupdate_cache(
1339
+ self.cache,
1340
+ existing_prompts,
1341
+ llm_string,
1342
+ missing_prompt_idxs,
1343
+ new_results,
1344
+ prompts,
1345
+ )
1346
+ run_info = (
1347
+ [RunInfo(run_id=run_manager.run_id) for run_manager in run_managers] # type: ignore[attr-defined]
1348
+ if run_managers
1349
+ else None
1350
+ )
1351
+ else:
1352
+ llm_output = {}
1353
+ run_info = None
1354
+ generations = [existing_prompts[i] for i in range(len(prompts))]
1355
+ return LLMResult(generations=generations, llm_output=llm_output, run=run_info)
1356
+
1357
+ async def _call_async(
1358
+ self,
1359
+ prompt: str,
1360
+ stop: list[str] | None = None,
1361
+ callbacks: Callbacks = None,
1362
+ *,
1363
+ tags: list[str] | None = None,
1364
+ metadata: builtins.dict[str, Any] | None = None,
1365
+ **kwargs: Any,
1366
+ ) -> str:
1367
+ """Check Cache and run the LLM on the given prompt and input."""
1368
+ result = await self.agenerate(
1369
+ [prompt],
1370
+ stop=stop,
1371
+ callbacks=callbacks,
1372
+ tags=tags,
1373
+ metadata=metadata,
1374
+ **kwargs,
1375
+ )
1376
+ return result.generations[0][0].text
1377
+
1378
+ def __str__(self) -> str:
1379
+ """Return a string representation of the object for printing."""
1380
+ cls_name = f"\033[1m{self.__class__.__name__}\033[0m"
1381
+ return f"{cls_name}\nParams: {self._identifying_params}"
1382
+
1383
+ @property
1384
+ @abstractmethod
1385
+ def _llm_type(self) -> str:
1386
+ """Return type of llm."""
1387
+
1388
+ @deprecated("1.4.2", alternative="asdict", removal="2.0.0")
1389
+ @override
1390
+ def dict(self, **_kwargs: Any) -> builtins.dict[str, Any]:
1391
+ """DEPRECATED - use `asdict()` instead.
1392
+
1393
+ Return a dictionary representation of the LLM.
1394
+ """
1395
+ return self.asdict()
1396
+
1397
+ def asdict(self) -> builtins.dict[str, Any]:
1398
+ """Return a dictionary representation of the LLM."""
1399
+ starter_dict = dict(self._identifying_params)
1400
+ starter_dict["_type"] = self._llm_type
1401
+ return starter_dict
1402
+
1403
+ def _dict_for_compat(self) -> builtins.dict[str, Any]:
1404
+ """Return the LLM dictionary while preserving deprecated overrides."""
1405
+ with suppress_coco_deprecation_warning():
1406
+ return self.dict()
1407
+
1408
+ def save(self, file_path: Path | str) -> None:
1409
+ """Save the LLM.
1410
+
1411
+ Args:
1412
+ file_path: Path to file to save the LLM to.
1413
+
1414
+ Raises:
1415
+ ValueError: If the file path is not a string or Path object.
1416
+
1417
+ Example:
1418
+ ```python
1419
+ llm.save(file_path="path/llm.yaml")
1420
+ ```
1421
+ """
1422
+ # Convert file to Path object.
1423
+ save_path = Path(file_path)
1424
+
1425
+ directory_path = save_path.parent
1426
+ directory_path.mkdir(parents=True, exist_ok=True)
1427
+
1428
+ # Fetch dictionary to save
1429
+ prompt_dict = self._dict_for_compat()
1430
+
1431
+ if save_path.suffix == ".json":
1432
+ with save_path.open("w", encoding="utf-8") as f:
1433
+ json.dump(prompt_dict, f, indent=4)
1434
+ elif save_path.suffix.endswith((".yaml", ".yml")):
1435
+ with save_path.open("w", encoding="utf-8") as f:
1436
+ yaml.dump(prompt_dict, f, default_flow_style=False)
1437
+ else:
1438
+ msg = f"{save_path} must be json or yaml"
1439
+ raise ValueError(msg)
1440
+
1441
+
1442
+ class LLM(BaseLLM):
1443
+ """Simple interface for implementing a custom LLM.
1444
+
1445
+ You should subclass this class and implement the following:
1446
+
1447
+ - `_call` method: Run the LLM on the given prompt and input (used by `invoke`).
1448
+ - `_identifying_params` property: Return a dictionary of the identifying parameters
1449
+ This is critical for caching and tracing purposes. Identifying parameters
1450
+ is a dict that identifies the LLM.
1451
+ It should mostly include a `model_name`.
1452
+
1453
+ Optional: Override the following methods to provide more optimizations:
1454
+
1455
+ - `_acall`: Provide a native async version of the `_call` method.
1456
+ If not provided, will delegate to the synchronous version using
1457
+ `run_in_executor`. (Used by `ainvoke`).
1458
+ - `_stream`: Stream the LLM on the given prompt and input.
1459
+ `stream` will use `_stream` if provided, otherwise it
1460
+ use `_call` and output will arrive in one chunk.
1461
+ - `_astream`: Override to provide a native async version of the `_stream` method.
1462
+ `astream` will use `_astream` if provided, otherwise it will implement
1463
+ a fallback behavior that will use `_stream` if `_stream` is implemented,
1464
+ and use `_acall` if `_stream` is not implemented.
1465
+ """
1466
+
1467
+ @abstractmethod
1468
+ def _call(
1469
+ self,
1470
+ prompt: str,
1471
+ stop: list[str] | None = None,
1472
+ run_manager: CallbackManagerForLLMRun | None = None,
1473
+ **kwargs: Any,
1474
+ ) -> str:
1475
+ """Run the LLM on the given input.
1476
+
1477
+ Override this method to implement the LLM logic.
1478
+
1479
+ Args:
1480
+ prompt: The prompt to generate from.
1481
+ stop: Stop words to use when generating.
1482
+
1483
+ Model output is cut off at the first occurrence of any of these
1484
+ substrings.
1485
+
1486
+ If stop tokens are not supported consider raising `NotImplementedError`.
1487
+ run_manager: Callback manager for the run.
1488
+ **kwargs: Arbitrary additional keyword arguments.
1489
+
1490
+ These are usually passed to the model provider API call.
1491
+
1492
+ Returns:
1493
+ The model output as a string. SHOULD NOT include the prompt.
1494
+ """
1495
+
1496
+ async def _acall(
1497
+ self,
1498
+ prompt: str,
1499
+ stop: list[str] | None = None,
1500
+ run_manager: AsyncCallbackManagerForLLMRun | None = None,
1501
+ **kwargs: Any,
1502
+ ) -> str:
1503
+ """Async version of the _call method.
1504
+
1505
+ The default implementation delegates to the synchronous _call method using
1506
+ `run_in_executor`. Subclasses that need to provide a true async implementation
1507
+ should override this method to reduce the overhead of using `run_in_executor`.
1508
+
1509
+ Args:
1510
+ prompt: The prompt to generate from.
1511
+ stop: Stop words to use when generating.
1512
+
1513
+ Model output is cut off at the first occurrence of any of these
1514
+ substrings.
1515
+
1516
+ If stop tokens are not supported consider raising `NotImplementedError`.
1517
+ run_manager: Callback manager for the run.
1518
+ **kwargs: Arbitrary additional keyword arguments.
1519
+
1520
+ These are usually passed to the model provider API call.
1521
+
1522
+ Returns:
1523
+ The model output as a string. SHOULD NOT include the prompt.
1524
+ """
1525
+ return await run_in_executor(
1526
+ None,
1527
+ self._call,
1528
+ prompt,
1529
+ stop,
1530
+ run_manager.get_sync() if run_manager else None,
1531
+ **kwargs,
1532
+ )
1533
+
1534
+ def _generate(
1535
+ self,
1536
+ prompts: list[str],
1537
+ stop: list[str] | None = None,
1538
+ run_manager: CallbackManagerForLLMRun | None = None,
1539
+ **kwargs: Any,
1540
+ ) -> LLMResult:
1541
+ # TODO: add caching here.
1542
+ generations = []
1543
+ new_arg_supported = inspect.signature(self._call).parameters.get("run_manager")
1544
+ for prompt in prompts:
1545
+ text = (
1546
+ self._call(prompt, stop=stop, run_manager=run_manager, **kwargs)
1547
+ if new_arg_supported
1548
+ else self._call(prompt, stop=stop, **kwargs)
1549
+ )
1550
+ generations.append([Generation(text=text)])
1551
+ return LLMResult(generations=generations)
1552
+
1553
+ async def _agenerate(
1554
+ self,
1555
+ prompts: list[str],
1556
+ stop: list[str] | None = None,
1557
+ run_manager: AsyncCallbackManagerForLLMRun | None = None,
1558
+ **kwargs: Any,
1559
+ ) -> LLMResult:
1560
+ generations = []
1561
+ new_arg_supported = inspect.signature(self._acall).parameters.get("run_manager")
1562
+ for prompt in prompts:
1563
+ text = (
1564
+ await self._acall(prompt, stop=stop, run_manager=run_manager, **kwargs)
1565
+ if new_arg_supported
1566
+ else await self._acall(prompt, stop=stop, **kwargs)
1567
+ )
1568
+ generations.append([Generation(text=text)])
1569
+ return LLMResult(generations=generations)