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,137 @@
1
+ """Fake LLMs for testing purposes."""
2
+
3
+ import asyncio
4
+ import time
5
+ from collections.abc import AsyncIterator, Iterator, Mapping
6
+ from typing import Any
7
+
8
+ from typing_extensions import override
9
+
10
+ from coco_core.callbacks import (
11
+ AsyncCallbackManagerForLLMRun,
12
+ CallbackManagerForLLMRun,
13
+ )
14
+ from coco_core.language_models import LanguageModelInput
15
+ from coco_core.language_models.llms import LLM
16
+ from coco_core.runnables import RunnableConfig
17
+
18
+
19
+ class FakeListLLM(LLM):
20
+ """Fake LLM for testing purposes."""
21
+
22
+ responses: list[str]
23
+ """List of responses to return in order."""
24
+ # This parameter should be removed from FakeListLLM since
25
+ # it's only used by sub-classes.
26
+ sleep: float | None = None
27
+ """Sleep time in seconds between responses.
28
+
29
+ Ignored by FakeListLLM, but used by sub-classes.
30
+ """
31
+ i: int = 0
32
+ """Internally incremented after every model invocation.
33
+
34
+ Useful primarily for testing purposes.
35
+ """
36
+
37
+ @property
38
+ @override
39
+ def _llm_type(self) -> str:
40
+ """Return type of llm."""
41
+ return "fake-list"
42
+
43
+ @override
44
+ def _call(
45
+ self,
46
+ prompt: str,
47
+ stop: list[str] | None = None,
48
+ run_manager: CallbackManagerForLLMRun | None = None,
49
+ **kwargs: Any,
50
+ ) -> str:
51
+ """Return next response."""
52
+ response = self.responses[self.i]
53
+ if self.i < len(self.responses) - 1:
54
+ self.i += 1
55
+ else:
56
+ self.i = 0
57
+ return response
58
+
59
+ @override
60
+ async def _acall(
61
+ self,
62
+ prompt: str,
63
+ stop: list[str] | None = None,
64
+ run_manager: AsyncCallbackManagerForLLMRun | None = None,
65
+ **kwargs: Any,
66
+ ) -> str:
67
+ """Return next response."""
68
+ response = self.responses[self.i]
69
+ if self.i < len(self.responses) - 1:
70
+ self.i += 1
71
+ else:
72
+ self.i = 0
73
+ return response
74
+
75
+ @property
76
+ @override
77
+ def _identifying_params(self) -> Mapping[str, Any]:
78
+ return {"responses": self.responses}
79
+
80
+
81
+ class FakeListLLMError(Exception):
82
+ """Fake error for testing purposes."""
83
+
84
+
85
+ class FakeStreamingListLLM(FakeListLLM):
86
+ """Fake streaming list LLM for testing purposes.
87
+
88
+ An LLM that will return responses from a list in order.
89
+
90
+ This model also supports optionally sleeping between successive
91
+ chunks in a streaming implementation.
92
+ """
93
+
94
+ error_on_chunk_number: int | None = None
95
+ """If set, will raise an exception on the specified chunk number."""
96
+
97
+ @override
98
+ def stream(
99
+ self,
100
+ input: LanguageModelInput,
101
+ config: RunnableConfig | None = None,
102
+ *,
103
+ stop: list[str] | None = None,
104
+ **kwargs: Any,
105
+ ) -> Iterator[str]:
106
+ result = self.invoke(input, config)
107
+ for i_c, c in enumerate(result):
108
+ if self.sleep is not None:
109
+ time.sleep(self.sleep)
110
+
111
+ if (
112
+ self.error_on_chunk_number is not None
113
+ and i_c == self.error_on_chunk_number
114
+ ):
115
+ raise FakeListLLMError
116
+ yield c
117
+
118
+ @override
119
+ async def astream(
120
+ self,
121
+ input: LanguageModelInput,
122
+ config: RunnableConfig | None = None,
123
+ *,
124
+ stop: list[str] | None = None,
125
+ **kwargs: Any,
126
+ ) -> AsyncIterator[str]:
127
+ result = await self.ainvoke(input, config)
128
+ for i_c, c in enumerate(result):
129
+ if self.sleep is not None:
130
+ await asyncio.sleep(self.sleep)
131
+
132
+ if (
133
+ self.error_on_chunk_number is not None
134
+ and i_c == self.error_on_chunk_number
135
+ ):
136
+ raise FakeListLLMError
137
+ yield c
@@ -0,0 +1,396 @@
1
+ """Fake chat models for testing purposes."""
2
+
3
+ import asyncio
4
+ import re
5
+ import time
6
+ from collections.abc import AsyncIterator, Iterator
7
+ from typing import Any, Literal, cast
8
+
9
+ from typing_extensions import override
10
+
11
+ from coco_core.callbacks import (
12
+ AsyncCallbackManagerForLLMRun,
13
+ CallbackManagerForLLMRun,
14
+ )
15
+ from coco_core.language_models.chat_models import BaseChatModel, SimpleChatModel
16
+ from coco_core.messages import AIMessage, AIMessageChunk, BaseMessage
17
+ from coco_core.outputs import ChatGeneration, ChatGenerationChunk, ChatResult
18
+ from coco_core.runnables import RunnableConfig
19
+
20
+
21
+ class FakeMessagesListChatModel(BaseChatModel):
22
+ """Fake chat model for testing purposes."""
23
+
24
+ responses: list[BaseMessage]
25
+ """List of responses to **cycle** through in order."""
26
+ sleep: float | None = None
27
+ """Sleep time in seconds between responses."""
28
+ i: int = 0
29
+ """Internally incremented after every model invocation."""
30
+
31
+ @override
32
+ def _generate(
33
+ self,
34
+ messages: list[BaseMessage],
35
+ stop: list[str] | None = None,
36
+ run_manager: CallbackManagerForLLMRun | None = None,
37
+ **kwargs: Any,
38
+ ) -> ChatResult:
39
+ if self.sleep is not None:
40
+ time.sleep(self.sleep)
41
+ response = self.responses[self.i]
42
+ if self.i < len(self.responses) - 1:
43
+ self.i += 1
44
+ else:
45
+ self.i = 0
46
+ generation = ChatGeneration(message=response)
47
+ return ChatResult(generations=[generation])
48
+
49
+ @property
50
+ @override
51
+ def _llm_type(self) -> str:
52
+ return "fake-messages-list-chat-model"
53
+
54
+
55
+ class FakeListChatModelError(Exception):
56
+ """Fake error for testing purposes."""
57
+
58
+
59
+ class FakeListChatModel(SimpleChatModel):
60
+ """Fake chat model for testing purposes."""
61
+
62
+ responses: list[str]
63
+ """List of responses to **cycle** through in order."""
64
+ sleep: float | None = None
65
+ i: int = 0
66
+ """Internally incremented after every model invocation."""
67
+ error_on_chunk_number: int | None = None
68
+ """If set, raise an error on the specified chunk number during streaming."""
69
+
70
+ @property
71
+ @override
72
+ def _llm_type(self) -> str:
73
+ return "fake-list-chat-model"
74
+
75
+ @override
76
+ def _call(
77
+ self,
78
+ *args: Any,
79
+ **kwargs: Any,
80
+ ) -> str:
81
+ """Return the next response in the list.
82
+
83
+ Cycle back to the start if at the end.
84
+ """
85
+ if self.sleep is not None:
86
+ time.sleep(self.sleep)
87
+ response = self.responses[self.i]
88
+ if self.i < len(self.responses) - 1:
89
+ self.i += 1
90
+ else:
91
+ self.i = 0
92
+ return response
93
+
94
+ @override
95
+ def _stream(
96
+ self,
97
+ messages: list[BaseMessage],
98
+ stop: list[str] | None = None,
99
+ run_manager: CallbackManagerForLLMRun | None = None,
100
+ **kwargs: Any,
101
+ ) -> Iterator[ChatGenerationChunk]:
102
+ response = self.responses[self.i]
103
+ if self.i < len(self.responses) - 1:
104
+ self.i += 1
105
+ else:
106
+ self.i = 0
107
+ for i_c, c in enumerate(response):
108
+ if self.sleep is not None:
109
+ time.sleep(self.sleep)
110
+ if (
111
+ self.error_on_chunk_number is not None
112
+ and i_c == self.error_on_chunk_number
113
+ ):
114
+ raise FakeListChatModelError
115
+
116
+ chunk_position: Literal["last"] | None = (
117
+ "last" if i_c == len(response) - 1 else None
118
+ )
119
+ yield ChatGenerationChunk(
120
+ message=AIMessageChunk(content=c, chunk_position=chunk_position)
121
+ )
122
+
123
+ @override
124
+ async def _astream(
125
+ self,
126
+ messages: list[BaseMessage],
127
+ stop: list[str] | None = None,
128
+ run_manager: AsyncCallbackManagerForLLMRun | None = None,
129
+ **kwargs: Any,
130
+ ) -> AsyncIterator[ChatGenerationChunk]:
131
+ response = self.responses[self.i]
132
+ if self.i < len(self.responses) - 1:
133
+ self.i += 1
134
+ else:
135
+ self.i = 0
136
+ for i_c, c in enumerate(response):
137
+ if self.sleep is not None:
138
+ await asyncio.sleep(self.sleep)
139
+ if (
140
+ self.error_on_chunk_number is not None
141
+ and i_c == self.error_on_chunk_number
142
+ ):
143
+ raise FakeListChatModelError
144
+ chunk_position: Literal["last"] | None = (
145
+ "last" if i_c == len(response) - 1 else None
146
+ )
147
+ yield ChatGenerationChunk(
148
+ message=AIMessageChunk(content=c, chunk_position=chunk_position)
149
+ )
150
+
151
+ @property
152
+ @override
153
+ def _identifying_params(self) -> dict[str, Any]:
154
+ return {"responses": self.responses}
155
+
156
+ @override
157
+ # manually override batch to preserve batch ordering with no concurrency
158
+ def batch(
159
+ self,
160
+ inputs: list[Any],
161
+ config: RunnableConfig | list[RunnableConfig] | None = None,
162
+ *,
163
+ return_exceptions: bool = False,
164
+ **kwargs: Any,
165
+ ) -> list[AIMessage]:
166
+ if isinstance(config, list):
167
+ return [
168
+ self.invoke(m, c, **kwargs)
169
+ for m, c in zip(inputs, config, strict=False)
170
+ ]
171
+ return [self.invoke(m, config, **kwargs) for m in inputs]
172
+
173
+ @override
174
+ async def abatch(
175
+ self,
176
+ inputs: list[Any],
177
+ config: RunnableConfig | list[RunnableConfig] | None = None,
178
+ *,
179
+ return_exceptions: bool = False,
180
+ **kwargs: Any,
181
+ ) -> list[AIMessage]:
182
+ if isinstance(config, list):
183
+ # do Not use an async iterator here because need explicit ordering
184
+ return [
185
+ await self.ainvoke(m, c, **kwargs)
186
+ for m, c in zip(inputs, config, strict=False)
187
+ ]
188
+ # do Not use an async iterator here because need explicit ordering
189
+ return [await self.ainvoke(m, config, **kwargs) for m in inputs]
190
+
191
+
192
+ class FakeChatModel(SimpleChatModel):
193
+ """Fake Chat Model wrapper for testing purposes."""
194
+
195
+ @override
196
+ def _call(
197
+ self,
198
+ messages: list[BaseMessage],
199
+ stop: list[str] | None = None,
200
+ run_manager: CallbackManagerForLLMRun | None = None,
201
+ **kwargs: Any,
202
+ ) -> str:
203
+ return "fake response"
204
+
205
+ @override
206
+ async def _agenerate(
207
+ self,
208
+ messages: list[BaseMessage],
209
+ stop: list[str] | None = None,
210
+ run_manager: AsyncCallbackManagerForLLMRun | None = None,
211
+ **kwargs: Any,
212
+ ) -> ChatResult:
213
+ output_str = "fake response"
214
+ message = AIMessage(content=output_str)
215
+ generation = ChatGeneration(message=message)
216
+ return ChatResult(generations=[generation])
217
+
218
+ @property
219
+ def _llm_type(self) -> str:
220
+ return "fake-chat-model"
221
+
222
+ @property
223
+ def _identifying_params(self) -> dict[str, Any]:
224
+ return {"key": "fake"}
225
+
226
+
227
+ class GenericFakeChatModel(BaseChatModel):
228
+ """Generic fake chat model that can be used to test the chat model interface.
229
+
230
+ * Chat model should be usable in both sync and async tests
231
+ * Invokes `on_llm_new_token` to allow for testing of callback related code for new
232
+ tokens.
233
+ * Includes logic to break messages into message chunk to facilitate testing of
234
+ streaming.
235
+
236
+ """
237
+
238
+ messages: Iterator[AIMessage | str]
239
+ """Get an iterator over messages.
240
+
241
+ This can be expanded to accept other types like Callables / dicts / strings
242
+ to make the interface more generic if needed.
243
+
244
+ !!! note
245
+ if you want to pass a list, you can use `iter` to convert it to an iterator.
246
+
247
+ !!! warning
248
+ Streaming is not implemented yet. We should try to implement it in the future by
249
+ delegating to invoke and then breaking the resulting output into message chunks.
250
+
251
+ """
252
+
253
+ @override
254
+ def _generate(
255
+ self,
256
+ messages: list[BaseMessage],
257
+ stop: list[str] | None = None,
258
+ run_manager: CallbackManagerForLLMRun | None = None,
259
+ **kwargs: Any,
260
+ ) -> ChatResult:
261
+ message = next(self.messages)
262
+ message_ = AIMessage(content=message) if isinstance(message, str) else message
263
+ generation = ChatGeneration(message=message_)
264
+ return ChatResult(generations=[generation])
265
+
266
+ def _stream(
267
+ self,
268
+ messages: list[BaseMessage],
269
+ stop: list[str] | None = None,
270
+ run_manager: CallbackManagerForLLMRun | None = None,
271
+ **kwargs: Any,
272
+ ) -> Iterator[ChatGenerationChunk]:
273
+ chat_result = self._generate(
274
+ messages, stop=stop, run_manager=run_manager, **kwargs
275
+ )
276
+ if not isinstance(chat_result, ChatResult):
277
+ msg = ( # type: ignore[unreachable]
278
+ f"Expected generate to return a ChatResult, "
279
+ f"but got {type(chat_result)} instead."
280
+ )
281
+ raise ValueError(msg) # noqa: TRY004
282
+
283
+ message = chat_result.generations[0].message
284
+
285
+ if not isinstance(message, AIMessage):
286
+ msg = (
287
+ f"Expected invoke to return an AIMessage, "
288
+ f"but got {type(message)} instead."
289
+ )
290
+ raise ValueError(msg) # noqa: TRY004
291
+
292
+ content = message.content
293
+
294
+ if content:
295
+ # Use a regular expression to split on whitespace with a capture group
296
+ # so that we can preserve the whitespace in the output.
297
+ if not isinstance(content, str):
298
+ msg = "Expected content to be a string."
299
+ raise ValueError(msg)
300
+
301
+ content_chunks = cast("list[str]", re.split(r"(\s)", content))
302
+
303
+ for idx, token in enumerate(content_chunks):
304
+ chunk = ChatGenerationChunk(
305
+ message=AIMessageChunk(content=token, id=message.id)
306
+ )
307
+ if (
308
+ idx == len(content_chunks) - 1
309
+ and isinstance(chunk.message, AIMessageChunk)
310
+ and not message.additional_kwargs
311
+ ):
312
+ chunk.message.chunk_position = "last"
313
+ if run_manager:
314
+ run_manager.on_llm_new_token(token, chunk=chunk)
315
+ yield chunk
316
+
317
+ if message.additional_kwargs:
318
+ for key, value in message.additional_kwargs.items():
319
+ # We should further break down the additional kwargs into chunks
320
+ # Special case for function call
321
+ if key == "function_call":
322
+ for fkey, fvalue in value.items():
323
+ if isinstance(fvalue, str):
324
+ # Break function call by `,`
325
+ fvalue_chunks = cast("list[str]", re.split(r"(,)", fvalue))
326
+ for fvalue_chunk in fvalue_chunks:
327
+ chunk = ChatGenerationChunk(
328
+ message=AIMessageChunk(
329
+ id=message.id,
330
+ content="",
331
+ additional_kwargs={
332
+ "function_call": {fkey: fvalue_chunk}
333
+ },
334
+ )
335
+ )
336
+ if run_manager:
337
+ run_manager.on_llm_new_token(
338
+ "",
339
+ chunk=chunk, # No token for function call
340
+ )
341
+ yield chunk
342
+ else:
343
+ chunk = ChatGenerationChunk(
344
+ message=AIMessageChunk(
345
+ id=message.id,
346
+ content="",
347
+ additional_kwargs={"function_call": {fkey: fvalue}},
348
+ )
349
+ )
350
+ if run_manager:
351
+ run_manager.on_llm_new_token(
352
+ "",
353
+ chunk=chunk, # No token for function call
354
+ )
355
+ yield chunk
356
+ else:
357
+ chunk = ChatGenerationChunk(
358
+ message=AIMessageChunk(
359
+ id=message.id, content="", additional_kwargs={key: value}
360
+ )
361
+ )
362
+ if run_manager:
363
+ run_manager.on_llm_new_token(
364
+ "",
365
+ chunk=chunk, # No token for function call
366
+ )
367
+ yield chunk
368
+
369
+ @property
370
+ def _llm_type(self) -> str:
371
+ return "generic-fake-chat-model"
372
+
373
+
374
+ class ParrotFakeChatModel(BaseChatModel):
375
+ """Generic fake chat model that can be used to test the chat model interface.
376
+
377
+ * Chat model should be usable in both sync and async tests
378
+
379
+ """
380
+
381
+ @override
382
+ def _generate(
383
+ self,
384
+ messages: list[BaseMessage],
385
+ stop: list[str] | None = None,
386
+ run_manager: CallbackManagerForLLMRun | None = None,
387
+ **kwargs: Any,
388
+ ) -> ChatResult:
389
+ if not messages:
390
+ msg = "messages list cannot be empty."
391
+ raise ValueError(msg)
392
+ return ChatResult(generations=[ChatGeneration(message=messages[-1])])
393
+
394
+ @property
395
+ def _llm_type(self) -> str:
396
+ return "parrot-fake-chat-model"