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,1495 @@
1
+ """Chat prompt template."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from abc import ABC, abstractmethod
6
+ from collections.abc import Sequence
7
+ from pathlib import Path
8
+ from typing import (
9
+ Annotated,
10
+ Any,
11
+ TypedDict,
12
+ TypeVar,
13
+ cast,
14
+ overload,
15
+ )
16
+
17
+ from pydantic import (
18
+ Field,
19
+ PositiveInt,
20
+ SkipValidation,
21
+ model_validator,
22
+ )
23
+ from typing_extensions import Self, override
24
+
25
+ from coco_core._api import deprecated
26
+ from coco_core.messages import (
27
+ AIMessage,
28
+ AnyMessage,
29
+ BaseMessage,
30
+ ChatMessage,
31
+ HumanMessage,
32
+ SystemMessage,
33
+ convert_to_messages,
34
+ )
35
+ from coco_core.messages.base import get_msg_title_repr
36
+ from coco_core.prompt_values import ChatPromptValue
37
+ from coco_core.prompts.base import BasePromptTemplate
38
+ from coco_core.prompts.dict import DictPromptTemplate
39
+ from coco_core.prompts.image import ImagePromptTemplate
40
+ from coco_core.prompts.message import (
41
+ BaseMessagePromptTemplate,
42
+ )
43
+ from coco_core.prompts.prompt import PromptTemplate
44
+ from coco_core.prompts.string import (
45
+ PromptTemplateFormat,
46
+ StringPromptTemplate,
47
+ get_template_variables,
48
+ )
49
+ from coco_core.utils import get_colored_text
50
+ from coco_core.utils.interactive_env import is_interactive_env
51
+
52
+
53
+ class MessagesPlaceholder(BaseMessagePromptTemplate):
54
+ """Prompt template that assumes variable is already list of messages.
55
+
56
+ A placeholder which can be used to pass in a list of messages.
57
+
58
+ !!! example "Direct usage"
59
+
60
+ ```python
61
+ from coco_core.prompts import MessagesPlaceholder
62
+
63
+ prompt = MessagesPlaceholder("history")
64
+ prompt.format_messages() # raises KeyError
65
+
66
+ prompt = MessagesPlaceholder("history", optional=True)
67
+ prompt.format_messages() # returns empty list []
68
+
69
+ prompt.format_messages(
70
+ history=[
71
+ ("system", "You are an AI assistant."),
72
+ ("human", "Hello!"),
73
+ ]
74
+ )
75
+ # -> [
76
+ # SystemMessage(content="You are an AI assistant."),
77
+ # HumanMessage(content="Hello!"),
78
+ # ]
79
+ ```
80
+
81
+ !!! example "Building a prompt with chat history"
82
+
83
+ ```python
84
+ from coco_core.prompts import ChatPromptTemplate, MessagesPlaceholder
85
+
86
+ prompt = ChatPromptTemplate.from_messages(
87
+ [
88
+ ("system", "You are a helpful assistant."),
89
+ MessagesPlaceholder("history"),
90
+ ("human", "{question}"),
91
+ ]
92
+ )
93
+ prompt.invoke(
94
+ {
95
+ "history": [("human", "what's 5 + 2"), ("ai", "5 + 2 is 7")],
96
+ "question": "now multiply that by 4",
97
+ }
98
+ )
99
+ # -> ChatPromptValue(messages=[
100
+ # SystemMessage(content="You are a helpful assistant."),
101
+ # HumanMessage(content="what's 5 + 2"),
102
+ # AIMessage(content="5 + 2 is 7"),
103
+ # HumanMessage(content="now multiply that by 4"),
104
+ # ])
105
+ ```
106
+
107
+ !!! example "Limiting the number of messages"
108
+
109
+ ```python
110
+ from coco_core.prompts import MessagesPlaceholder
111
+
112
+ prompt = MessagesPlaceholder("history", n_messages=1)
113
+
114
+ prompt.format_messages(
115
+ history=[
116
+ ("system", "You are an AI assistant."),
117
+ ("human", "Hello!"),
118
+ ]
119
+ )
120
+ # -> [
121
+ # HumanMessage(content="Hello!"),
122
+ # ]
123
+ ```
124
+ """
125
+
126
+ variable_name: str
127
+ """Name of variable to use as messages."""
128
+
129
+ optional: bool = False
130
+ """Whether `format_messages` must be provided.
131
+
132
+ If `True` `format_messages` can be called with no arguments and will return an empty
133
+ list.
134
+
135
+ If `False` then a named argument with name `variable_name` must be passed in, even
136
+ if the value is an empty list.
137
+ """
138
+
139
+ n_messages: PositiveInt | None = None
140
+ """Maximum number of messages to include.
141
+
142
+ If `None`, then will include all.
143
+ """
144
+
145
+ def __init__(
146
+ self, variable_name: str, *, optional: bool = False, **kwargs: Any
147
+ ) -> None:
148
+ """Create a messages placeholder.
149
+
150
+ Args:
151
+ variable_name: Name of variable to use as messages.
152
+ optional: Whether `format_messages` must be provided.
153
+
154
+ If `True` format_messages can be called with no arguments and will
155
+ return an empty list.
156
+
157
+ If `False` then a named argument with name `variable_name` must be
158
+ passed in, even if the value is an empty list.
159
+ """
160
+ # mypy can't detect the init which is defined in the parent class
161
+ # b/c these are BaseModel classes.
162
+ super().__init__(variable_name=variable_name, optional=optional, **kwargs) # type: ignore[call-arg,unused-ignore]
163
+
164
+ def format_messages(self, **kwargs: Any) -> list[BaseMessage]:
165
+ """Format messages from kwargs.
166
+
167
+ Args:
168
+ **kwargs: Keyword arguments to use for formatting.
169
+
170
+ Returns:
171
+ List of `BaseMessage` objects.
172
+
173
+ Raises:
174
+ ValueError: If variable is not a list of messages.
175
+ """
176
+ value = (
177
+ kwargs.get(self.variable_name, [])
178
+ if self.optional
179
+ else kwargs[self.variable_name]
180
+ )
181
+ if not isinstance(value, list):
182
+ msg = (
183
+ f"variable {self.variable_name} should be a list of base messages, "
184
+ f"got {value} of type {type(value)}"
185
+ )
186
+ raise ValueError(msg) # noqa: TRY004
187
+ value = convert_to_messages(value)
188
+ if self.n_messages:
189
+ value = value[-self.n_messages :]
190
+ return value
191
+
192
+ @property
193
+ def input_variables(self) -> list[str]:
194
+ """Input variables for this prompt template.
195
+
196
+ Returns:
197
+ List of input variable names.
198
+ """
199
+ return [self.variable_name] if not self.optional else []
200
+
201
+ @override
202
+ def pretty_repr(self, html: bool = False) -> str:
203
+ """Human-readable representation.
204
+
205
+ Args:
206
+ html: Whether to format as HTML.
207
+
208
+ Returns:
209
+ Human-readable representation.
210
+ """
211
+ var = "{" + self.variable_name + "}"
212
+ if html:
213
+ title = get_msg_title_repr("Messages Placeholder", bold=True)
214
+ var = get_colored_text(var, "yellow")
215
+ else:
216
+ title = get_msg_title_repr("Messages Placeholder")
217
+ return f"{title}\n\n{var}"
218
+
219
+
220
+ MessagePromptTemplateT = TypeVar(
221
+ "MessagePromptTemplateT", bound="BaseStringMessagePromptTemplate"
222
+ )
223
+ """Type variable for message prompt templates."""
224
+
225
+
226
+ class BaseStringMessagePromptTemplate(BaseMessagePromptTemplate, ABC):
227
+ """Base class for message prompt templates that use a string prompt template."""
228
+
229
+ prompt: StringPromptTemplate
230
+ """String prompt template."""
231
+
232
+ additional_kwargs: dict[str, Any] = Field(default_factory=dict)
233
+ """Additional keyword arguments to pass to the prompt template."""
234
+
235
+ @classmethod
236
+ def from_template(
237
+ cls,
238
+ template: str,
239
+ template_format: PromptTemplateFormat = "f-string",
240
+ partial_variables: dict[str, Any] | None = None,
241
+ **kwargs: Any,
242
+ ) -> Self:
243
+ """Create a class from a string template.
244
+
245
+ Args:
246
+ template: a template.
247
+ template_format: format of the template.
248
+ partial_variables: A dictionary of variables that can be used to partially
249
+ fill in the template.
250
+
251
+ For example, if the template is `"{variable1} {variable2}"`, and
252
+ `partial_variables` is `{"variable1": "foo"}`, then the final prompt
253
+ will be `"foo {variable2}"`.
254
+
255
+ **kwargs: Keyword arguments to pass to the constructor.
256
+
257
+ Returns:
258
+ A new instance of this class.
259
+ """
260
+ prompt = PromptTemplate.from_template(
261
+ template,
262
+ template_format=template_format,
263
+ partial_variables=partial_variables,
264
+ )
265
+ return cls(prompt=prompt, **kwargs)
266
+
267
+ @classmethod
268
+ def from_template_file(
269
+ cls,
270
+ template_file: str | Path,
271
+ **kwargs: Any,
272
+ ) -> Self:
273
+ """Create a class from a template file.
274
+
275
+ Args:
276
+ template_file: path to a template file.
277
+ **kwargs: Keyword arguments to pass to the constructor.
278
+
279
+ Returns:
280
+ A new instance of this class.
281
+ """
282
+ prompt = PromptTemplate.from_file(template_file)
283
+ return cls(prompt=prompt, **kwargs)
284
+
285
+ @abstractmethod
286
+ def format(self, **kwargs: Any) -> BaseMessage:
287
+ """Format the prompt template.
288
+
289
+ Args:
290
+ **kwargs: Keyword arguments to use for formatting.
291
+
292
+ Returns:
293
+ Formatted message.
294
+ """
295
+
296
+ async def aformat(self, **kwargs: Any) -> BaseMessage:
297
+ """Async format the prompt template.
298
+
299
+ Args:
300
+ **kwargs: Keyword arguments to use for formatting.
301
+
302
+ Returns:
303
+ Formatted message.
304
+ """
305
+ return self.format(**kwargs)
306
+
307
+ def format_messages(self, **kwargs: Any) -> list[BaseMessage]:
308
+ """Format messages from kwargs.
309
+
310
+ Args:
311
+ **kwargs: Keyword arguments to use for formatting.
312
+
313
+ Returns:
314
+ List of `BaseMessage` objects.
315
+ """
316
+ return [self.format(**kwargs)]
317
+
318
+ async def aformat_messages(self, **kwargs: Any) -> list[BaseMessage]:
319
+ """Async format messages from kwargs.
320
+
321
+ Args:
322
+ **kwargs: Keyword arguments to use for formatting.
323
+
324
+ Returns:
325
+ List of `BaseMessage` objects.
326
+ """
327
+ return [await self.aformat(**kwargs)]
328
+
329
+ @property
330
+ def input_variables(self) -> list[str]:
331
+ """Input variables for this prompt template.
332
+
333
+ Returns:
334
+ List of input variable names.
335
+ """
336
+ return self.prompt.input_variables
337
+
338
+ @override
339
+ def pretty_repr(self, html: bool = False) -> str:
340
+ """Human-readable representation.
341
+
342
+ Args:
343
+ html: Whether to format as HTML.
344
+
345
+ Returns:
346
+ Human-readable representation.
347
+ """
348
+ # TODO: Handle partials
349
+ title = self.__class__.__name__.replace("MessagePromptTemplate", " Message")
350
+ title = get_msg_title_repr(title, bold=html)
351
+ return f"{title}\n\n{self.prompt.pretty_repr(html=html)}"
352
+
353
+
354
+ class ChatMessagePromptTemplate(BaseStringMessagePromptTemplate):
355
+ """Chat message prompt template."""
356
+
357
+ role: str
358
+ """Role of the message."""
359
+
360
+ def format(self, **kwargs: Any) -> BaseMessage:
361
+ """Format the prompt template.
362
+
363
+ Args:
364
+ **kwargs: Keyword arguments to use for formatting.
365
+
366
+ Returns:
367
+ Formatted message.
368
+ """
369
+ text = self.prompt.format(**kwargs)
370
+ return ChatMessage(
371
+ content=text, role=self.role, additional_kwargs=self.additional_kwargs
372
+ )
373
+
374
+ async def aformat(self, **kwargs: Any) -> BaseMessage:
375
+ """Async format the prompt template.
376
+
377
+ Args:
378
+ **kwargs: Keyword arguments to use for formatting.
379
+
380
+ Returns:
381
+ Formatted message.
382
+ """
383
+ text = await self.prompt.aformat(**kwargs)
384
+ return ChatMessage(
385
+ content=text, role=self.role, additional_kwargs=self.additional_kwargs
386
+ )
387
+
388
+
389
+ class _TextTemplateParam(TypedDict, total=False):
390
+ text: str | dict[str, Any]
391
+
392
+
393
+ class _ImageTemplateParam(TypedDict, total=False):
394
+ image_url: str | dict[str, Any]
395
+
396
+
397
+ class _StringImageMessagePromptTemplate(BaseMessagePromptTemplate):
398
+ """Human message prompt template. This is a message sent from the user."""
399
+
400
+ prompt: (
401
+ StringPromptTemplate
402
+ | list[StringPromptTemplate | ImagePromptTemplate | DictPromptTemplate]
403
+ )
404
+ """Prompt template."""
405
+ additional_kwargs: dict[str, Any] = Field(default_factory=dict)
406
+ """Additional keyword arguments to pass to the prompt template."""
407
+
408
+ _msg_class: type[BaseMessage]
409
+
410
+ @classmethod
411
+ def from_template(
412
+ cls: type[Self],
413
+ template: str
414
+ | Sequence[str | _TextTemplateParam | _ImageTemplateParam | dict[str, Any]],
415
+ template_format: PromptTemplateFormat = "f-string",
416
+ *,
417
+ partial_variables: dict[str, Any] | None = None,
418
+ **kwargs: Any,
419
+ ) -> Self:
420
+ """Create a class from a string template.
421
+
422
+ Args:
423
+ template: a template.
424
+ template_format: format of the template.
425
+
426
+ Options are: `'f-string'`, `'mustache'`, `'jinja2'`.
427
+ partial_variables: A dictionary of variables that can be used too partially.
428
+
429
+ **kwargs: Keyword arguments to pass to the constructor.
430
+
431
+ Returns:
432
+ A new instance of this class.
433
+
434
+ Raises:
435
+ ValueError: If the template is not a string or list of strings.
436
+ """
437
+ prompt: (
438
+ StringPromptTemplate
439
+ | list[StringPromptTemplate | ImagePromptTemplate | DictPromptTemplate]
440
+ )
441
+ if isinstance(template, str):
442
+ prompt = PromptTemplate.from_template(
443
+ template,
444
+ template_format=template_format,
445
+ partial_variables=partial_variables,
446
+ )
447
+ return cls(prompt=prompt, **kwargs)
448
+ if isinstance(template, Sequence):
449
+ if (partial_variables is not None) and len(partial_variables) > 0:
450
+ msg = "Partial variables are not supported for list of templates."
451
+ raise ValueError(msg)
452
+ prompt = []
453
+ for tmpl in template:
454
+ if isinstance(tmpl, str) or (
455
+ isinstance(tmpl, dict)
456
+ and "text" in tmpl
457
+ and set(tmpl.keys()) <= {"type", "text"}
458
+ ):
459
+ if isinstance(tmpl, str):
460
+ text: str = tmpl
461
+ else:
462
+ text = cast("_TextTemplateParam", tmpl)["text"] # type: ignore[assignment]
463
+ prompt.append(
464
+ PromptTemplate.from_template(
465
+ text, template_format=template_format
466
+ )
467
+ )
468
+ elif (
469
+ isinstance(tmpl, dict)
470
+ and "image_url" in tmpl
471
+ and set(tmpl.keys())
472
+ <= {
473
+ "type",
474
+ "image_url",
475
+ }
476
+ ):
477
+ img_template = cast("_ImageTemplateParam", tmpl)["image_url"]
478
+ input_variables = []
479
+ if isinstance(img_template, str):
480
+ variables = get_template_variables(
481
+ img_template, template_format
482
+ )
483
+ if variables:
484
+ if len(variables) > 1:
485
+ msg = (
486
+ "Only one format variable allowed per image"
487
+ f" template.\nGot: {variables}"
488
+ f"\nFrom: {tmpl}"
489
+ )
490
+ raise ValueError(msg)
491
+ input_variables = [variables[0]]
492
+ img_template = {"url": img_template}
493
+ img_template_obj = ImagePromptTemplate(
494
+ input_variables=input_variables,
495
+ template=img_template,
496
+ template_format=template_format,
497
+ )
498
+ elif isinstance(img_template, dict):
499
+ img_template = dict(img_template)
500
+ for key in ["url", "path", "detail"]:
501
+ if key in img_template:
502
+ input_variables.extend(
503
+ get_template_variables(
504
+ img_template[key], template_format
505
+ )
506
+ )
507
+ img_template_obj = ImagePromptTemplate(
508
+ input_variables=input_variables,
509
+ template=img_template,
510
+ template_format=template_format,
511
+ )
512
+ else:
513
+ msg = f"Invalid image template: {tmpl}" # type: ignore[unreachable]
514
+ raise ValueError(msg)
515
+ prompt.append(img_template_obj)
516
+ elif isinstance(tmpl, dict):
517
+ if template_format == "jinja2":
518
+ msg = (
519
+ "jinja2 is unsafe and is not supported for templates "
520
+ "expressed as dicts. Please use 'f-string' or 'mustache' "
521
+ "format."
522
+ )
523
+ raise ValueError(msg)
524
+ data_template_obj = DictPromptTemplate(
525
+ template=cast("dict[str, Any]", tmpl),
526
+ template_format=template_format,
527
+ )
528
+ prompt.append(data_template_obj)
529
+ else:
530
+ msg = f"Invalid template: {tmpl}" # type: ignore[unreachable]
531
+ raise ValueError(msg)
532
+ return cls(prompt=prompt, **kwargs)
533
+ msg = f"Invalid template: {template}" # type: ignore[unreachable]
534
+ raise ValueError(msg)
535
+
536
+ @classmethod
537
+ def from_template_file(
538
+ cls: type[Self],
539
+ template_file: str | Path,
540
+ input_variables: list[str],
541
+ **kwargs: Any,
542
+ ) -> Self:
543
+ """Create a class from a template file.
544
+
545
+ Args:
546
+ template_file: path to a template file.
547
+ input_variables: list of input variables.
548
+ **kwargs: Keyword arguments to pass to the constructor.
549
+
550
+ Returns:
551
+ A new instance of this class.
552
+ """
553
+ template = Path(template_file).read_text(encoding="utf-8")
554
+ return cls.from_template(template, input_variables=input_variables, **kwargs)
555
+
556
+ def format_messages(self, **kwargs: Any) -> list[BaseMessage]:
557
+ """Format messages from kwargs.
558
+
559
+ Args:
560
+ **kwargs: Keyword arguments to use for formatting.
561
+
562
+ Returns:
563
+ List of `BaseMessage` objects.
564
+ """
565
+ return [self.format(**kwargs)]
566
+
567
+ async def aformat_messages(self, **kwargs: Any) -> list[BaseMessage]:
568
+ """Async format messages from kwargs.
569
+
570
+ Args:
571
+ **kwargs: Keyword arguments to use for formatting.
572
+
573
+ Returns:
574
+ List of `BaseMessage` objects.
575
+ """
576
+ return [await self.aformat(**kwargs)]
577
+
578
+ @property
579
+ def input_variables(self) -> list[str]:
580
+ """Input variables for this prompt template.
581
+
582
+ Returns:
583
+ List of input variable names.
584
+ """
585
+ prompts = self.prompt if isinstance(self.prompt, list) else [self.prompt]
586
+ return [iv for prompt in prompts for iv in prompt.input_variables]
587
+
588
+ def format(self, **kwargs: Any) -> BaseMessage:
589
+ """Format the prompt template.
590
+
591
+ Args:
592
+ **kwargs: Keyword arguments to use for formatting.
593
+
594
+ Returns:
595
+ Formatted message.
596
+ """
597
+ if isinstance(self.prompt, StringPromptTemplate):
598
+ text = self.prompt.format(**kwargs)
599
+ return self._msg_class(
600
+ content=text, additional_kwargs=self.additional_kwargs
601
+ )
602
+ content: list[str | dict[str, Any]] = []
603
+ for prompt in self.prompt:
604
+ inputs = {var: kwargs[var] for var in prompt.input_variables}
605
+ if isinstance(prompt, StringPromptTemplate):
606
+ formatted_text = prompt.format(**inputs)
607
+ if formatted_text != "":
608
+ content.append({"type": "text", "text": formatted_text})
609
+ elif isinstance(prompt, ImagePromptTemplate):
610
+ formatted_image = prompt.format(**inputs)
611
+ content.append({"type": "image_url", "image_url": formatted_image})
612
+ elif isinstance(prompt, DictPromptTemplate):
613
+ formatted_dict = prompt.format(**inputs)
614
+ content.append(formatted_dict)
615
+ return self._msg_class(
616
+ content=content, additional_kwargs=self.additional_kwargs
617
+ )
618
+
619
+ async def aformat(self, **kwargs: Any) -> BaseMessage:
620
+ """Async format the prompt template.
621
+
622
+ Args:
623
+ **kwargs: Keyword arguments to use for formatting.
624
+
625
+ Returns:
626
+ Formatted message.
627
+ """
628
+ if isinstance(self.prompt, StringPromptTemplate):
629
+ text = await self.prompt.aformat(**kwargs)
630
+ return self._msg_class(
631
+ content=text, additional_kwargs=self.additional_kwargs
632
+ )
633
+ content: list[str | dict[str, Any]] = []
634
+ for prompt in self.prompt:
635
+ inputs = {var: kwargs[var] for var in prompt.input_variables}
636
+ if isinstance(prompt, StringPromptTemplate):
637
+ formatted_text = await prompt.aformat(**inputs)
638
+ if formatted_text != "":
639
+ content.append({"type": "text", "text": formatted_text})
640
+ elif isinstance(prompt, ImagePromptTemplate):
641
+ formatted_image = await prompt.aformat(**inputs)
642
+ content.append({"type": "image_url", "image_url": formatted_image})
643
+ elif isinstance(prompt, DictPromptTemplate):
644
+ formatted_dict = prompt.format(**inputs)
645
+ content.append(formatted_dict)
646
+ return self._msg_class(
647
+ content=content, additional_kwargs=self.additional_kwargs
648
+ )
649
+
650
+ @override
651
+ def pretty_repr(self, html: bool = False) -> str:
652
+ """Human-readable representation.
653
+
654
+ Args:
655
+ html: Whether to format as HTML.
656
+
657
+ Returns:
658
+ Human-readable representation.
659
+ """
660
+ # TODO: Handle partials
661
+ title = self.__class__.__name__.replace("MessagePromptTemplate", " Message")
662
+ title = get_msg_title_repr(title, bold=html)
663
+ prompts = self.prompt if isinstance(self.prompt, list) else [self.prompt]
664
+ prompt_reprs = "\n\n".join(prompt.pretty_repr(html=html) for prompt in prompts)
665
+ return f"{title}\n\n{prompt_reprs}"
666
+
667
+
668
+ class HumanMessagePromptTemplate(_StringImageMessagePromptTemplate):
669
+ """Human message prompt template.
670
+
671
+ This is a message sent from the user.
672
+ """
673
+
674
+ _msg_class: type[BaseMessage] = HumanMessage
675
+
676
+
677
+ class AIMessagePromptTemplate(_StringImageMessagePromptTemplate):
678
+ """AI message prompt template.
679
+
680
+ This is a message sent from the AI.
681
+ """
682
+
683
+ _msg_class: type[BaseMessage] = AIMessage
684
+
685
+
686
+ class SystemMessagePromptTemplate(_StringImageMessagePromptTemplate):
687
+ """System message prompt template.
688
+
689
+ This is a message that is not sent to the user.
690
+ """
691
+
692
+ _msg_class: type[BaseMessage] = SystemMessage
693
+
694
+
695
+ class BaseChatPromptTemplate(BasePromptTemplate[str], ABC):
696
+ """Base class for chat prompt templates."""
697
+
698
+ @property
699
+ @override
700
+ def lc_attributes(self) -> dict[str, Any]:
701
+ return {"input_variables": self.input_variables}
702
+
703
+ def format(self, **kwargs: Any) -> str:
704
+ """Format the chat template into a string.
705
+
706
+ Args:
707
+ **kwargs: Keyword arguments to use for filling in template variables in all
708
+ the template messages in this chat template.
709
+
710
+ Returns:
711
+ Formatted string.
712
+ """
713
+ return self.format_prompt(**kwargs).to_string()
714
+
715
+ async def aformat(self, **kwargs: Any) -> str:
716
+ """Async format the chat template into a string.
717
+
718
+ Args:
719
+ **kwargs: Keyword arguments to use for filling in template variables in all
720
+ the template messages in this chat template.
721
+
722
+ Returns:
723
+ Formatted string.
724
+ """
725
+ return (await self.aformat_prompt(**kwargs)).to_string()
726
+
727
+ def format_prompt(self, **kwargs: Any) -> ChatPromptValue:
728
+ """Format prompt.
729
+
730
+ Should return a `ChatPromptValue`.
731
+
732
+ Args:
733
+ **kwargs: Keyword arguments to use for formatting.
734
+ """
735
+ messages = self.format_messages(**kwargs)
736
+ return ChatPromptValue(messages=messages)
737
+
738
+ async def aformat_prompt(self, **kwargs: Any) -> ChatPromptValue:
739
+ """Async format prompt.
740
+
741
+ Should return a `ChatPromptValue`.
742
+
743
+ Args:
744
+ **kwargs: Keyword arguments to use for formatting.
745
+ """
746
+ messages = await self.aformat_messages(**kwargs)
747
+ return ChatPromptValue(messages=messages)
748
+
749
+ @abstractmethod
750
+ def format_messages(self, **kwargs: Any) -> list[BaseMessage]:
751
+ """Format kwargs into a list of messages.
752
+
753
+ Returns:
754
+ List of `BaseMessage` objects.
755
+ """
756
+
757
+ async def aformat_messages(self, **kwargs: Any) -> list[BaseMessage]:
758
+ """Async format kwargs into a list of messages.
759
+
760
+ Returns:
761
+ List of `BaseMessage` objects.
762
+ """
763
+ return self.format_messages(**kwargs)
764
+
765
+ def pretty_repr(
766
+ self,
767
+ html: bool = False, # noqa: FBT001,FBT002
768
+ ) -> str:
769
+ """Human-readable representation.
770
+
771
+ Args:
772
+ html: Whether to format as HTML.
773
+
774
+ Returns:
775
+ Human-readable representation.
776
+ """
777
+ raise NotImplementedError
778
+
779
+ def pretty_print(self) -> None:
780
+ """Print a human-readable representation."""
781
+ print(self.pretty_repr(html=is_interactive_env())) # noqa: T201
782
+
783
+
784
+ MessageLike = BaseMessagePromptTemplate | BaseMessage | BaseChatPromptTemplate
785
+
786
+ MessageLikeRepresentation = (
787
+ MessageLike
788
+ | tuple[str | type, str | Sequence[dict[str, Any]] | Sequence[object]]
789
+ | str
790
+ | dict[str, Any]
791
+ )
792
+
793
+
794
+ class ChatPromptTemplate(BaseChatPromptTemplate):
795
+ """Prompt template for chat models.
796
+
797
+ Use to create flexible templated prompts for chat models.
798
+
799
+ !!! example
800
+
801
+ ```python
802
+ from coco_core.prompts import ChatPromptTemplate
803
+
804
+ template = ChatPromptTemplate(
805
+ [
806
+ ("system", "You are a helpful AI bot. Your name is {name}."),
807
+ ("human", "Hello, how are you doing?"),
808
+ ("ai", "I'm doing well, thanks!"),
809
+ ("human", "{user_input}"),
810
+ ]
811
+ )
812
+
813
+ prompt_value = template.invoke(
814
+ {
815
+ "name": "Bob",
816
+ "user_input": "What is your name?",
817
+ }
818
+ )
819
+ # Output:
820
+ # ChatPromptValue(
821
+ # messages=[
822
+ # SystemMessage(content='You are a helpful AI bot. Your name is Bob.'),
823
+ # HumanMessage(content='Hello, how are you doing?'),
824
+ # AIMessage(content="I'm doing well, thanks!"),
825
+ # HumanMessage(content='What is your name?')
826
+ # ]
827
+ # )
828
+ ```
829
+
830
+ !!! note "Messages Placeholder"
831
+
832
+ ```python
833
+ # In addition to Human/AI/Tool/Function messages,
834
+ # you can initialize the template with a MessagesPlaceholder
835
+ # either using the class directly or with the shorthand tuple syntax:
836
+
837
+ template = ChatPromptTemplate(
838
+ [
839
+ ("system", "You are a helpful AI bot."),
840
+ # Means the template will receive an optional list of messages under
841
+ # the "conversation" key
842
+ ("placeholder", "{conversation}"),
843
+ # Equivalently:
844
+ # MessagesPlaceholder(variable_name="conversation", optional=True)
845
+ ]
846
+ )
847
+
848
+ prompt_value = template.invoke(
849
+ {
850
+ "conversation": [
851
+ ("human", "Hi!"),
852
+ ("ai", "How can I assist you today?"),
853
+ ("human", "Can you make me an ice cream sundae?"),
854
+ ("ai", "No."),
855
+ ]
856
+ }
857
+ )
858
+
859
+ # Output:
860
+ # ChatPromptValue(
861
+ # messages=[
862
+ # SystemMessage(content='You are a helpful AI bot.'),
863
+ # HumanMessage(content='Hi!'),
864
+ # AIMessage(content='How can I assist you today?'),
865
+ # HumanMessage(content='Can you make me an ice cream sundae?'),
866
+ # AIMessage(content='No.'),
867
+ # ]
868
+ # )
869
+ ```
870
+
871
+ !!! note "Single-variable template"
872
+
873
+ If your prompt has only a single input variable (i.e., one instance of
874
+ `'{variable_nams}'`), and you invoke the template with a non-dict object, the
875
+ prompt template will inject the provided argument into that variable location.
876
+
877
+ ```python
878
+ from coco_core.prompts import ChatPromptTemplate
879
+
880
+ template = ChatPromptTemplate(
881
+ [
882
+ ("system", "You are a helpful AI bot. Your name is Carl."),
883
+ ("human", "{user_input}"),
884
+ ]
885
+ )
886
+
887
+ prompt_value = template.invoke("Hello, there!")
888
+ # Equivalent to
889
+ # prompt_value = template.invoke({"user_input": "Hello, there!"})
890
+
891
+ # Output:
892
+ # ChatPromptValue(
893
+ # messages=[
894
+ # SystemMessage(content='You are a helpful AI bot. Your name is Carl.'),
895
+ # HumanMessage(content='Hello, there!'),
896
+ # ]
897
+ # )
898
+ ```
899
+ """
900
+
901
+ messages: Annotated[list[MessageLike], SkipValidation()]
902
+ """List of messages consisting of either message prompt templates or messages."""
903
+
904
+ validate_template: bool = False
905
+ """Whether or not to try validating the template."""
906
+
907
+ def __init__(
908
+ self,
909
+ messages: Sequence[MessageLikeRepresentation],
910
+ *,
911
+ template_format: PromptTemplateFormat = "f-string",
912
+ **kwargs: Any,
913
+ ) -> None:
914
+ """Create a chat prompt template from a variety of message formats.
915
+
916
+ Args:
917
+ messages: Sequence of message representations.
918
+
919
+ A message can be represented using the following formats:
920
+
921
+ 1. `BaseMessagePromptTemplate`
922
+ 2. `BaseMessage`
923
+ 3. 2-tuple of `(message type, template)`; e.g.,
924
+ `('human', '{user_input}')`
925
+ 4. 2-tuple of `(message class, template)`
926
+ 5. A string which is shorthand for `('human', template)`; e.g.,
927
+ `'{user_input}'`
928
+ template_format: Format of the template.
929
+ **kwargs: Additional keyword arguments passed to `BasePromptTemplate`,
930
+ including (but not limited to):
931
+
932
+ - `input_variables`: A list of the names of the variables whose values
933
+ are required as inputs to the prompt.
934
+ - `optional_variables`: A list of the names of the variables for
935
+ placeholder or `MessagePlaceholder` that are optional.
936
+
937
+ These variables are auto inferred from the prompt and user need not
938
+ provide them.
939
+
940
+ - `partial_variables`: A dictionary of the partial variables the prompt
941
+ template carries.
942
+
943
+ Partial variables populate the template so that you don't need to
944
+ pass them in every time you call the prompt.
945
+
946
+ - `validate_template`: Whether to validate the template.
947
+ - `input_types`: A dictionary of the types of the variables the prompt
948
+ template expects.
949
+
950
+ If not provided, all variables are assumed to be strings.
951
+
952
+ Examples:
953
+ Instantiation from a list of message templates:
954
+
955
+ ```python
956
+ template = ChatPromptTemplate(
957
+ [
958
+ ("human", "Hello, how are you?"),
959
+ ("ai", "I'm doing well, thanks!"),
960
+ ("human", "That's good to hear."),
961
+ ]
962
+ )
963
+ ```
964
+
965
+ Instantiation from mixed message formats:
966
+
967
+ ```python
968
+ template = ChatPromptTemplate(
969
+ [
970
+ SystemMessage(content="hello"),
971
+ ("human", "Hello, how are you?"),
972
+ ]
973
+ )
974
+ ```
975
+ """
976
+ messages_ = [
977
+ _convert_to_message_template(message, template_format)
978
+ for message in messages
979
+ ]
980
+
981
+ # Automatically infer input variables from messages
982
+ input_vars: set[str] = set()
983
+ optional_variables: set[str] = set()
984
+ partial_vars: dict[str, Any] = {}
985
+ for message in messages_:
986
+ if isinstance(message, MessagesPlaceholder) and message.optional:
987
+ partial_vars[message.variable_name] = []
988
+ optional_variables.add(message.variable_name)
989
+ elif isinstance(
990
+ message, (BaseChatPromptTemplate, BaseMessagePromptTemplate)
991
+ ):
992
+ input_vars.update(message.input_variables)
993
+
994
+ kwargs = {
995
+ "input_variables": sorted(input_vars),
996
+ "optional_variables": sorted(optional_variables),
997
+ "partial_variables": partial_vars,
998
+ **kwargs,
999
+ }
1000
+ cast("type[ChatPromptTemplate]", super()).__init__(messages=messages_, **kwargs)
1001
+
1002
+ @classmethod
1003
+ def get_lc_namespace(cls) -> list[str]:
1004
+ """Get the namespace of the Coco object.
1005
+
1006
+ Returns:
1007
+ `["coco", "prompts", "chat"]`
1008
+ """
1009
+ return ["coco", "prompts", "chat"]
1010
+
1011
+ def __add__(self, other: Any) -> ChatPromptTemplate:
1012
+ """Combine two prompt templates.
1013
+
1014
+ Args:
1015
+ other: Another prompt template.
1016
+
1017
+ Returns:
1018
+ Combined prompt template.
1019
+ """
1020
+ partials = {**self.partial_variables}
1021
+
1022
+ # Need to check that other has partial variables since it may not be
1023
+ # a ChatPromptTemplate.
1024
+ if hasattr(other, "partial_variables") and other.partial_variables:
1025
+ partials.update(other.partial_variables)
1026
+
1027
+ # Allow for easy combining
1028
+ if isinstance(other, ChatPromptTemplate):
1029
+ return ChatPromptTemplate(messages=self.messages + other.messages).partial(
1030
+ **partials
1031
+ )
1032
+ if isinstance(
1033
+ other, (BaseMessagePromptTemplate, BaseMessage, BaseChatPromptTemplate)
1034
+ ):
1035
+ return ChatPromptTemplate(messages=[*self.messages, other]).partial(
1036
+ **partials
1037
+ )
1038
+ if isinstance(other, (list, tuple)):
1039
+ other_ = ChatPromptTemplate.from_messages(other)
1040
+ return ChatPromptTemplate(messages=self.messages + other_.messages).partial(
1041
+ **partials
1042
+ )
1043
+ if isinstance(other, str):
1044
+ prompt = HumanMessagePromptTemplate.from_template(other)
1045
+ return ChatPromptTemplate(messages=[*self.messages, prompt]).partial(
1046
+ **partials
1047
+ )
1048
+ msg = f"Unsupported operand type for +: {type(other)}"
1049
+ raise NotImplementedError(msg)
1050
+
1051
+ @model_validator(mode="before")
1052
+ @classmethod
1053
+ def validate_input_variables(cls, values: dict[str, Any]) -> Any:
1054
+ """Validate input variables.
1055
+
1056
+ If `input_variables` is not set, it will be set to the union of all input
1057
+ variables in the messages.
1058
+
1059
+ Args:
1060
+ values: values to validate.
1061
+
1062
+ Returns:
1063
+ Validated values.
1064
+
1065
+ Raises:
1066
+ ValueError: If input variables do not match.
1067
+ """
1068
+ messages = values["messages"]
1069
+ input_vars: set[str] = set()
1070
+ optional_variables = set()
1071
+ input_types: dict[str, Any] = values.get("input_types", {})
1072
+ for message in messages:
1073
+ if isinstance(message, (BaseMessagePromptTemplate, BaseChatPromptTemplate)):
1074
+ input_vars.update(message.input_variables)
1075
+ if isinstance(message, MessagesPlaceholder):
1076
+ if "partial_variables" not in values:
1077
+ values["partial_variables"] = {}
1078
+ if (
1079
+ message.optional
1080
+ and message.variable_name not in values["partial_variables"]
1081
+ ):
1082
+ values["partial_variables"][message.variable_name] = []
1083
+ optional_variables.add(message.variable_name)
1084
+ if message.variable_name not in input_types:
1085
+ input_types[message.variable_name] = list[AnyMessage]
1086
+ if "partial_variables" in values:
1087
+ input_vars -= set(values["partial_variables"])
1088
+ if optional_variables:
1089
+ input_vars -= optional_variables
1090
+ if "input_variables" in values and values.get("validate_template"):
1091
+ if input_vars != set(values["input_variables"]):
1092
+ msg = (
1093
+ "Got mismatched input_variables. "
1094
+ f"Expected: {input_vars}. "
1095
+ f"Got: {values['input_variables']}"
1096
+ )
1097
+ raise ValueError(msg)
1098
+ else:
1099
+ values["input_variables"] = sorted(input_vars)
1100
+ if optional_variables:
1101
+ values["optional_variables"] = sorted(optional_variables)
1102
+ values["input_types"] = input_types
1103
+ return values
1104
+
1105
+ @classmethod
1106
+ def from_template(cls, template: str, **kwargs: Any) -> ChatPromptTemplate:
1107
+ """Create a chat prompt template from a template string.
1108
+
1109
+ Creates a chat template consisting of a single message assumed to be from the
1110
+ human.
1111
+
1112
+ Args:
1113
+ template: Template string
1114
+ **kwargs: Keyword arguments to pass to the constructor.
1115
+
1116
+ Returns:
1117
+ A new instance of this class.
1118
+ """
1119
+ prompt_template = PromptTemplate.from_template(template, **kwargs)
1120
+ message = HumanMessagePromptTemplate(prompt=prompt_template)
1121
+ return cls.from_messages([message])
1122
+
1123
+ @classmethod
1124
+ def from_messages(
1125
+ cls,
1126
+ messages: Sequence[MessageLikeRepresentation],
1127
+ template_format: PromptTemplateFormat = "f-string",
1128
+ ) -> ChatPromptTemplate:
1129
+ """Create a chat prompt template from a variety of message formats.
1130
+
1131
+ Examples:
1132
+ Instantiation from a list of message templates:
1133
+
1134
+ ```python
1135
+ template = ChatPromptTemplate.from_messages(
1136
+ [
1137
+ ("human", "Hello, how are you?"),
1138
+ ("ai", "I'm doing well, thanks!"),
1139
+ ("human", "That's good to hear."),
1140
+ ]
1141
+ )
1142
+ ```
1143
+
1144
+ Instantiation from mixed message formats:
1145
+
1146
+ ```python
1147
+ template = ChatPromptTemplate.from_messages(
1148
+ [
1149
+ SystemMessage(content="hello"),
1150
+ ("human", "Hello, how are you?"),
1151
+ ]
1152
+ )
1153
+ ```
1154
+ Args:
1155
+ messages: Sequence of message representations.
1156
+
1157
+ A message can be represented using the following formats:
1158
+
1159
+ 1. `BaseMessagePromptTemplate`
1160
+ 2. `BaseMessage`
1161
+ 3. 2-tuple of `(message type, template)`; e.g.,
1162
+ `('human', '{user_input}')`
1163
+ 4. 2-tuple of `(message class, template)`
1164
+ 5. A string which is shorthand for `('human', template)`; e.g.,
1165
+ `'{user_input}'`
1166
+ template_format: Format of the template.
1167
+
1168
+ Returns:
1169
+ A chat prompt template.
1170
+
1171
+ """
1172
+ return cls(messages, template_format=template_format)
1173
+
1174
+ def format_messages(self, **kwargs: Any) -> list[BaseMessage]:
1175
+ """Format the chat template into a list of finalized messages.
1176
+
1177
+ Args:
1178
+ **kwargs: Keyword arguments to use for filling in template variables
1179
+ in all the template messages in this chat template.
1180
+
1181
+ Raises:
1182
+ ValueError: If messages are of unexpected types.
1183
+
1184
+ Returns:
1185
+ List of formatted messages.
1186
+ """
1187
+ kwargs = self._merge_partial_and_user_variables(**kwargs)
1188
+ result = []
1189
+ for message_template in self.messages:
1190
+ if isinstance(message_template, BaseMessage):
1191
+ result.extend([message_template])
1192
+ elif isinstance(
1193
+ message_template, (BaseMessagePromptTemplate, BaseChatPromptTemplate)
1194
+ ):
1195
+ message = message_template.format_messages(**kwargs)
1196
+ result.extend(message)
1197
+ else:
1198
+ msg = f"Unexpected input: {message_template}" # type: ignore[unreachable]
1199
+ raise ValueError(msg) # noqa: TRY004
1200
+ return result
1201
+
1202
+ async def aformat_messages(self, **kwargs: Any) -> list[BaseMessage]:
1203
+ """Async format the chat template into a list of finalized messages.
1204
+
1205
+ Args:
1206
+ **kwargs: Keyword arguments to use for filling in template variables
1207
+ in all the template messages in this chat template.
1208
+
1209
+ Returns:
1210
+ List of formatted messages.
1211
+
1212
+ Raises:
1213
+ ValueError: If unexpected input.
1214
+ """
1215
+ kwargs = self._merge_partial_and_user_variables(**kwargs)
1216
+ result = []
1217
+ for message_template in self.messages:
1218
+ if isinstance(message_template, BaseMessage):
1219
+ result.extend([message_template])
1220
+ elif isinstance(
1221
+ message_template, (BaseMessagePromptTemplate, BaseChatPromptTemplate)
1222
+ ):
1223
+ message = await message_template.aformat_messages(**kwargs)
1224
+ result.extend(message)
1225
+ else:
1226
+ msg = f"Unexpected input: {message_template}" # type: ignore[unreachable]
1227
+ raise ValueError(msg) # noqa:TRY004
1228
+ return result
1229
+
1230
+ def partial(self, **kwargs: Any) -> ChatPromptTemplate:
1231
+ """Get a new `ChatPromptTemplate` with some input variables already filled in.
1232
+
1233
+ Args:
1234
+ **kwargs: Keyword arguments to use for filling in template variables.
1235
+
1236
+ Ought to be a subset of the input variables.
1237
+
1238
+ Returns:
1239
+ A new `ChatPromptTemplate`.
1240
+
1241
+ Example:
1242
+ ```python
1243
+ from coco_core.prompts import ChatPromptTemplate
1244
+
1245
+ template = ChatPromptTemplate.from_messages(
1246
+ [
1247
+ ("system", "You are an AI assistant named {name}."),
1248
+ ("human", "Hi I'm {user}"),
1249
+ ("ai", "Hi there, {user}, I'm {name}."),
1250
+ ("human", "{input}"),
1251
+ ]
1252
+ )
1253
+ template2 = template.partial(user="Lucy", name="R2D2")
1254
+
1255
+ template2.format_messages(input="hello")
1256
+ ```
1257
+ """
1258
+ prompt_dict = self.__dict__.copy()
1259
+ prompt_dict["input_variables"] = list(
1260
+ set(self.input_variables).difference(kwargs)
1261
+ )
1262
+ prompt_dict["partial_variables"] = {**self.partial_variables, **kwargs}
1263
+ return type(self)(**prompt_dict)
1264
+
1265
+ def append(self, message: MessageLikeRepresentation) -> None:
1266
+ """Append a message to the end of the chat template.
1267
+
1268
+ Args:
1269
+ message: representation of a message to append.
1270
+ """
1271
+ self.messages.append(_convert_to_message_template(message))
1272
+
1273
+ def extend(self, messages: Sequence[MessageLikeRepresentation]) -> None:
1274
+ """Extend the chat template with a sequence of messages.
1275
+
1276
+ Args:
1277
+ messages: Sequence of message representations to append.
1278
+ """
1279
+ self.messages.extend(
1280
+ [_convert_to_message_template(message) for message in messages]
1281
+ )
1282
+
1283
+ @overload
1284
+ def __getitem__(self, index: int) -> MessageLike: ...
1285
+
1286
+ @overload
1287
+ def __getitem__(self, index: slice) -> ChatPromptTemplate: ...
1288
+
1289
+ def __getitem__(self, index: int | slice) -> MessageLike | ChatPromptTemplate:
1290
+ """Use to index into the chat template.
1291
+
1292
+ Returns:
1293
+ If index is an int, returns the message at that index.
1294
+
1295
+ If index is a slice, returns a new `ChatPromptTemplate` containing the
1296
+ messages in that slice.
1297
+ """
1298
+ if isinstance(index, slice):
1299
+ start, stop, step = index.indices(len(self.messages))
1300
+ messages = self.messages[start:stop:step]
1301
+ return ChatPromptTemplate.from_messages(messages)
1302
+ return self.messages[index]
1303
+
1304
+ def __len__(self) -> int:
1305
+ """Return the length of the chat template."""
1306
+ return len(self.messages)
1307
+
1308
+ @property
1309
+ def _prompt_type(self) -> str:
1310
+ """Name of prompt type. Used for serialization."""
1311
+ return "chat"
1312
+
1313
+ @deprecated(
1314
+ since="1.2.21",
1315
+ removal="2.0.0",
1316
+ alternative="Use `dumpd`/`dumps` from `coco_core.load` to serialize "
1317
+ "prompts and `load`/`loads` to deserialize them.",
1318
+ )
1319
+ def save(self, file_path: Path | str) -> None:
1320
+ """Save prompt to file.
1321
+
1322
+ Args:
1323
+ file_path: path to file.
1324
+ """
1325
+ raise NotImplementedError
1326
+
1327
+ @override
1328
+ def pretty_repr(self, html: bool = False) -> str:
1329
+ """Human-readable representation.
1330
+
1331
+ Args:
1332
+ html: Whether to format as HTML.
1333
+
1334
+ Returns:
1335
+ Human-readable representation.
1336
+ """
1337
+ # TODO: handle partials
1338
+ return "\n\n".join(msg.pretty_repr(html=html) for msg in self.messages)
1339
+
1340
+
1341
+ def _create_template_from_message_type(
1342
+ message_type: str,
1343
+ template: str | list[str | dict[str, Any] | bool],
1344
+ template_format: PromptTemplateFormat = "f-string",
1345
+ ) -> BaseMessagePromptTemplate:
1346
+ """Create a message prompt template from a message type and template string.
1347
+
1348
+ Args:
1349
+ message_type: The type of the message template (e.g., `'human'`, `'ai'`, etc.)
1350
+ template: The template string.
1351
+ template_format: Format of the template.
1352
+
1353
+ Returns:
1354
+ A message prompt template of the appropriate type.
1355
+
1356
+ Raises:
1357
+ ValueError: If unexpected message type.
1358
+ """
1359
+ if message_type in {"human", "user"}:
1360
+ message: BaseMessagePromptTemplate = HumanMessagePromptTemplate.from_template(
1361
+ cast("str", template), template_format=template_format
1362
+ )
1363
+ elif message_type in {"ai", "assistant"}:
1364
+ message = AIMessagePromptTemplate.from_template(
1365
+ cast("str", template), template_format=template_format
1366
+ )
1367
+ elif message_type == "system":
1368
+ message = SystemMessagePromptTemplate.from_template(
1369
+ cast("str", template), template_format=template_format
1370
+ )
1371
+ elif message_type == "placeholder":
1372
+ if isinstance(template, str):
1373
+ if template[0] != "{" or template[-1] != "}":
1374
+ msg = (
1375
+ f"Invalid placeholder template: {template}."
1376
+ " Expected a variable name surrounded by curly braces."
1377
+ )
1378
+ raise ValueError(msg)
1379
+ var_name = template[1:-1]
1380
+ message = MessagesPlaceholder(variable_name=var_name, optional=True)
1381
+ else:
1382
+ try:
1383
+ var_name_wrapped, is_optional = template
1384
+ except ValueError as e:
1385
+ msg = (
1386
+ "Unexpected arguments for placeholder message type."
1387
+ " Expected either a single string variable name"
1388
+ " or a list of [variable_name: str, is_optional: bool]."
1389
+ f" Got: {template}"
1390
+ )
1391
+ raise ValueError(msg) from e
1392
+
1393
+ if not isinstance(is_optional, bool):
1394
+ msg = f"Expected is_optional to be a boolean. Got: {is_optional}"
1395
+ raise ValueError(msg) # noqa: TRY004
1396
+
1397
+ if not isinstance(var_name_wrapped, str):
1398
+ msg = f"Expected variable name to be a string. Got: {var_name_wrapped}"
1399
+ raise ValueError(msg) # noqa: TRY004
1400
+ if var_name_wrapped[0] != "{" or var_name_wrapped[-1] != "}":
1401
+ msg = (
1402
+ f"Invalid placeholder template: {var_name_wrapped}."
1403
+ " Expected a variable name surrounded by curly braces."
1404
+ )
1405
+ raise ValueError(msg)
1406
+ var_name = var_name_wrapped[1:-1]
1407
+
1408
+ message = MessagesPlaceholder(variable_name=var_name, optional=is_optional)
1409
+ else:
1410
+ msg = (
1411
+ f"Unexpected message type: {message_type}. Use one of 'human',"
1412
+ f" 'user', 'ai', 'assistant', or 'system'."
1413
+ )
1414
+ raise ValueError(msg)
1415
+ return message
1416
+
1417
+
1418
+ def _convert_to_message_template(
1419
+ message: MessageLikeRepresentation,
1420
+ template_format: PromptTemplateFormat = "f-string",
1421
+ ) -> BaseMessage | BaseMessagePromptTemplate | BaseChatPromptTemplate:
1422
+ """Instantiate a message from a variety of message formats.
1423
+
1424
+ A message can be represented using the following formats:
1425
+
1426
+ 1. `BaseMessagePromptTemplate`
1427
+ 2. `BaseMessage`
1428
+ 3. 2-tuple of `(message type, template)`; e.g., `('human', '{user_input}')`
1429
+ 4. 2-tuple of `(message class, template)`
1430
+ 5. A string which is shorthand for `('human', template)`; e.g., `'{user_input}'`
1431
+
1432
+ Args:
1433
+ message: A representation of a message in one of the supported formats.
1434
+ template_format: Format of the template.
1435
+
1436
+ Returns:
1437
+ An instance of a message or a message template.
1438
+
1439
+ Raises:
1440
+ ValueError: If unexpected message type.
1441
+ ValueError: If 2-tuple does not have 2 elements.
1442
+ """
1443
+ if isinstance(message, (BaseMessagePromptTemplate, BaseChatPromptTemplate)):
1444
+ message_: BaseMessage | BaseMessagePromptTemplate | BaseChatPromptTemplate = (
1445
+ message
1446
+ )
1447
+ elif isinstance(message, BaseMessage):
1448
+ message_ = message
1449
+ elif isinstance(message, str):
1450
+ message_ = _create_template_from_message_type(
1451
+ "human", message, template_format=template_format
1452
+ )
1453
+ elif isinstance(message, (tuple, dict)):
1454
+ if isinstance(message, dict):
1455
+ if set(message.keys()) != {"content", "role"}:
1456
+ msg = (
1457
+ "Expected dict to have exact keys 'role' and 'content'."
1458
+ f" Got: {message}"
1459
+ )
1460
+ raise ValueError(msg)
1461
+ message_type_str = message["role"]
1462
+ template = message["content"]
1463
+ else:
1464
+ if len(message) != 2: # noqa: PLR2004
1465
+ msg = f"Expected 2-tuple of (role, template), got {message}" # type: ignore[unreachable]
1466
+ raise ValueError(msg)
1467
+ message_type_str, template = message
1468
+
1469
+ if isinstance(message_type_str, str):
1470
+ message_ = _create_template_from_message_type(
1471
+ message_type_str, template, template_format=template_format
1472
+ )
1473
+ elif (
1474
+ hasattr(message_type_str, "model_fields")
1475
+ and "type" in message_type_str.model_fields
1476
+ ):
1477
+ message_type = message_type_str.model_fields["type"].default
1478
+ message_ = _create_template_from_message_type(
1479
+ message_type, template, template_format=template_format
1480
+ )
1481
+ else:
1482
+ message_ = message_type_str(
1483
+ prompt=PromptTemplate.from_template(
1484
+ cast("str", template), template_format=template_format
1485
+ )
1486
+ )
1487
+ else:
1488
+ msg = f"Unsupported message type: {type(message)}" # type: ignore[unreachable]
1489
+ raise NotImplementedError(msg)
1490
+
1491
+ return message_
1492
+
1493
+
1494
+ # For backwards compat:
1495
+ _convert_to_message = _convert_to_message_template