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,399 @@
1
+ """`BasePrompt` schema definition."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import warnings
6
+ from abc import ABC, abstractmethod
7
+ from string import Formatter
8
+ from typing import TYPE_CHECKING, Any, Literal, cast
9
+
10
+ from pydantic import BaseModel, create_model
11
+ from typing_extensions import override
12
+
13
+ from coco_core.prompt_values import PromptValue, StringPromptValue
14
+ from coco_core.prompts.base import BasePromptTemplate
15
+ from coco_core.utils import get_colored_text, mustache
16
+ from coco_core.utils.formatting import formatter
17
+ from coco_core.utils.interactive_env import is_interactive_env
18
+
19
+ if TYPE_CHECKING:
20
+ from collections.abc import Callable, Sequence
21
+
22
+ try:
23
+ from jinja2 import meta
24
+ from jinja2.sandbox import SandboxedEnvironment
25
+
26
+ _HAS_JINJA2 = True
27
+ except ImportError:
28
+ _HAS_JINJA2 = False
29
+
30
+ PromptTemplateFormat = Literal["f-string", "mustache", "jinja2"]
31
+
32
+
33
+ def jinja2_formatter(template: str, /, **kwargs: Any) -> str:
34
+ """Format a template using jinja2.
35
+
36
+ !!! warning "Security"
37
+
38
+ As of Coco 0.0.329, this method uses Jinja2's `SandboxedEnvironment` by
39
+ default. However, this sandboxing should be treated as a best-effort approach
40
+ rather than a guarantee of security.
41
+
42
+ Do not accept jinja2 templates from untrusted sources as they may lead
43
+ to arbitrary Python code execution.
44
+
45
+ [More information.](https://jinja.palletsprojects.com/en/3.1.x/sandbox/)
46
+
47
+ Args:
48
+ template: The template string.
49
+ **kwargs: The variables to format the template with.
50
+
51
+ Returns:
52
+ The formatted string.
53
+
54
+ Raises:
55
+ ImportError: If jinja2 is not installed.
56
+ """
57
+ if not _HAS_JINJA2:
58
+ msg = (
59
+ "jinja2 not installed, which is needed to use the jinja2_formatter. "
60
+ "Please install it with `pip install jinja2`."
61
+ "Please be cautious when using jinja2 templates. "
62
+ "Do not expand jinja2 templates using unverified or user-controlled "
63
+ "inputs as that can result in arbitrary Python code execution."
64
+ )
65
+ raise ImportError(msg)
66
+
67
+ # Use Jinja2's SandboxedEnvironment which blocks access to dunder attributes
68
+ # (e.g., __class__, __globals__) to prevent sandbox escapes.
69
+ # Note: regular attribute access (e.g., {{obj.attr}}) and method calls are
70
+ # still allowed. This is a best-effort measure — do not use with untrusted
71
+ # templates.
72
+ return SandboxedEnvironment().from_string(template).render(**kwargs)
73
+
74
+
75
+ def validate_jinja2(template: str, input_variables: list[str]) -> None:
76
+ """Validate that the input variables are valid for the template.
77
+
78
+ Issues a warning if missing or extra variables are found.
79
+
80
+ Args:
81
+ template: The template string.
82
+ input_variables: The input variables.
83
+ """
84
+ input_variables_set = set(input_variables)
85
+ valid_variables = _get_jinja2_variables_from_template(template)
86
+ missing_variables = valid_variables - input_variables_set
87
+ extra_variables = input_variables_set - valid_variables
88
+
89
+ warning_message = ""
90
+ if missing_variables:
91
+ warning_message += f"Missing variables: {missing_variables} "
92
+
93
+ if extra_variables:
94
+ warning_message += f"Extra variables: {extra_variables}"
95
+
96
+ if warning_message:
97
+ warnings.warn(warning_message.strip(), stacklevel=7)
98
+
99
+
100
+ def _get_jinja2_variables_from_template(template: str) -> set[str]:
101
+ if not _HAS_JINJA2:
102
+ msg = (
103
+ "jinja2 not installed, which is needed to use the jinja2_formatter. "
104
+ "Please install it with `pip install jinja2`."
105
+ )
106
+ raise ImportError(msg)
107
+ env = SandboxedEnvironment()
108
+ ast = env.parse(template)
109
+ return meta.find_undeclared_variables(ast)
110
+
111
+
112
+ def mustache_formatter(template: str, /, **kwargs: Any) -> str:
113
+ """Format a template using mustache.
114
+
115
+ Args:
116
+ template: The template string.
117
+ **kwargs: The variables to format the template with.
118
+
119
+ Returns:
120
+ The formatted string.
121
+ """
122
+ return mustache.render(template, kwargs)
123
+
124
+
125
+ def mustache_template_vars(
126
+ template: str,
127
+ ) -> set[str]:
128
+ """Get the top-level variables from a mustache template.
129
+
130
+ For nested variables like `{{person.name}}`, only the top-level key (`person`) is
131
+ returned.
132
+
133
+ Args:
134
+ template: The template string.
135
+
136
+ Returns:
137
+ The top-level variables from the template.
138
+ """
139
+ variables: set[str] = set()
140
+ section_depth = 0
141
+ for type_, key in mustache.tokenize(template):
142
+ if type_ == "end":
143
+ section_depth -= 1
144
+ elif (
145
+ type_ in {"variable", "section", "inverted section", "no escape"}
146
+ and key != "."
147
+ and section_depth == 0
148
+ ):
149
+ variables.add(key.split(".")[0])
150
+ if type_ in {"section", "inverted section"}:
151
+ section_depth += 1
152
+ return variables
153
+
154
+
155
+ Defs = dict[str, "Defs"]
156
+
157
+
158
+ def mustache_schema(template: str) -> type[BaseModel]:
159
+ """Get the variables from a mustache template.
160
+
161
+ Args:
162
+ template: The template string.
163
+
164
+ Returns:
165
+ The variables from the template as a Pydantic model.
166
+ """
167
+ fields = {}
168
+ prefix: tuple[str, ...] = ()
169
+ section_stack: list[tuple[str, ...]] = []
170
+ for type_, key in mustache.tokenize(template):
171
+ if key == ".":
172
+ continue
173
+ if type_ == "end":
174
+ if section_stack:
175
+ prefix = section_stack.pop()
176
+ elif type_ in {"section", "inverted section"}:
177
+ section_stack.append(prefix)
178
+ prefix += tuple(key.split("."))
179
+ fields[prefix] = False
180
+ elif type_ in {"variable", "no escape"}:
181
+ fields[prefix + tuple(key.split("."))] = True
182
+
183
+ for fkey, fval in fields.items():
184
+ fields[fkey] = fval and not any(
185
+ is_subsequence(fkey, k) for k in fields if k != fkey
186
+ )
187
+ defs: Defs = {} # None means leaf node
188
+ while fields:
189
+ field, is_leaf = fields.popitem()
190
+ current = defs
191
+ for part in field[:-1]:
192
+ current = current.setdefault(part, {})
193
+ current.setdefault(field[-1], "" if is_leaf else {}) # type: ignore[arg-type]
194
+ return _create_model_recursive("PromptInput", defs)
195
+
196
+
197
+ def _create_model_recursive(name: str, defs: Defs) -> type[BaseModel]:
198
+ return cast(
199
+ "type[BaseModel]",
200
+ create_model( # type: ignore[call-overload]
201
+ name,
202
+ **{
203
+ k: (_create_model_recursive(k, v), None) if v else (type(v), None)
204
+ for k, v in defs.items()
205
+ },
206
+ ),
207
+ )
208
+
209
+
210
+ DEFAULT_FORMATTER_MAPPING: dict[str, Callable[..., str]] = {
211
+ "f-string": formatter.format,
212
+ "mustache": mustache_formatter,
213
+ "jinja2": jinja2_formatter,
214
+ }
215
+
216
+ DEFAULT_VALIDATOR_MAPPING: dict[str, Callable[[str, list[str]], None]] = {
217
+ "f-string": formatter.validate_input_variables,
218
+ "jinja2": validate_jinja2,
219
+ }
220
+
221
+
222
+ def _parse_f_string_fields(template: str) -> list[tuple[str, str | None]]:
223
+ fields: list[tuple[str, str | None]] = []
224
+ for _, field_name, format_spec, _ in Formatter().parse(template):
225
+ if field_name is not None:
226
+ fields.append((field_name, format_spec))
227
+ return fields
228
+
229
+
230
+ def validate_f_string_template(template: str) -> list[str]:
231
+ """Validate an f-string template and return its input variables."""
232
+ input_variables = set()
233
+ for var, format_spec in _parse_f_string_fields(template):
234
+ if "." in var or "[" in var or "]" in var:
235
+ msg = (
236
+ f"Invalid variable name {var!r} in f-string template. "
237
+ f"Variable names cannot contain attribute "
238
+ f"access (.) or indexing ([])."
239
+ )
240
+ raise ValueError(msg)
241
+
242
+ if var.isdigit():
243
+ msg = (
244
+ f"Invalid variable name {var!r} in f-string template. "
245
+ f"Variable names cannot be all digits as they are interpreted "
246
+ f"as positional arguments."
247
+ )
248
+ raise ValueError(msg)
249
+
250
+ if format_spec and ("{" in format_spec or "}" in format_spec):
251
+ msg = (
252
+ "Invalid format specifier in f-string template. "
253
+ "Nested replacement fields are not allowed."
254
+ )
255
+ raise ValueError(msg)
256
+
257
+ input_variables.add(var)
258
+
259
+ return sorted(input_variables)
260
+
261
+
262
+ def check_valid_template(
263
+ template: str, template_format: str, input_variables: list[str]
264
+ ) -> None:
265
+ """Check that template string is valid.
266
+
267
+ Args:
268
+ template: The template string.
269
+ template_format: The template format.
270
+
271
+ Should be one of `'f-string'` or `'jinja2'`.
272
+ input_variables: The input variables.
273
+
274
+ Raises:
275
+ ValueError: If the template format is not supported.
276
+ ValueError: If the prompt schema is invalid.
277
+ """
278
+ try:
279
+ validator_func = DEFAULT_VALIDATOR_MAPPING[template_format]
280
+ except KeyError as exc:
281
+ msg = (
282
+ f"Invalid template format {template_format!r}, should be one of"
283
+ f" {list(DEFAULT_FORMATTER_MAPPING)}."
284
+ )
285
+ raise ValueError(msg) from exc
286
+ if template_format == "f-string":
287
+ validate_f_string_template(template)
288
+ try:
289
+ validator_func(template, input_variables)
290
+ except (KeyError, IndexError) as exc:
291
+ msg = (
292
+ "Invalid prompt schema; check for mismatched or missing input parameters"
293
+ f" from {input_variables}."
294
+ )
295
+ raise ValueError(msg) from exc
296
+
297
+
298
+ def get_template_variables(template: str, template_format: str) -> list[str]:
299
+ """Get the variables from the template.
300
+
301
+ Args:
302
+ template: The template string.
303
+ template_format: The template format.
304
+
305
+ Should be one of `'f-string'`, `'mustache'` or `'jinja2'`.
306
+
307
+ Returns:
308
+ The variables from the template.
309
+
310
+ Raises:
311
+ ValueError: If the template format is not supported.
312
+ """
313
+ input_variables: list[str] | set[str]
314
+ if template_format == "jinja2":
315
+ # Get the variables for the template
316
+ input_variables = sorted(_get_jinja2_variables_from_template(template))
317
+ elif template_format == "f-string":
318
+ input_variables = validate_f_string_template(template)
319
+ elif template_format == "mustache":
320
+ input_variables = mustache_template_vars(template)
321
+ else:
322
+ msg = f"Unsupported template format: {template_format}"
323
+ raise ValueError(msg)
324
+
325
+ return sorted(input_variables)
326
+
327
+
328
+ class StringPromptTemplate(BasePromptTemplate[str], ABC):
329
+ """String prompt that exposes the format method, returning a prompt."""
330
+
331
+ @classmethod
332
+ def get_lc_namespace(cls) -> list[str]:
333
+ """Get the namespace of the Coco object.
334
+
335
+ Returns:
336
+ `["coco", "prompts", "base"]`
337
+ """
338
+ return ["coco", "prompts", "base"]
339
+
340
+ def format_prompt(self, **kwargs: Any) -> PromptValue:
341
+ """Format the prompt with the inputs.
342
+
343
+ Args:
344
+ **kwargs: Any arguments to be passed to the prompt template.
345
+
346
+ Returns:
347
+ A formatted string.
348
+ """
349
+ return StringPromptValue(text=self.format(**kwargs))
350
+
351
+ async def aformat_prompt(self, **kwargs: Any) -> PromptValue:
352
+ """Async format the prompt with the inputs.
353
+
354
+ Args:
355
+ **kwargs: Any arguments to be passed to the prompt template.
356
+
357
+ Returns:
358
+ A formatted string.
359
+ """
360
+ return StringPromptValue(text=await self.aformat(**kwargs))
361
+
362
+ @override
363
+ @abstractmethod
364
+ def format(self, **kwargs: Any) -> str: ...
365
+
366
+ def pretty_repr(
367
+ self,
368
+ html: bool = False, # noqa: FBT001,FBT002
369
+ ) -> str:
370
+ """Get a pretty representation of the prompt.
371
+
372
+ Args:
373
+ html: Whether to return an HTML-formatted string.
374
+
375
+ Returns:
376
+ A pretty representation of the prompt.
377
+ """
378
+ # TODO: handle partials
379
+ dummy_vars = {
380
+ input_var: "{" + f"{input_var}" + "}" for input_var in self.input_variables
381
+ }
382
+ if html:
383
+ dummy_vars = {
384
+ k: get_colored_text(v, "yellow") for k, v in dummy_vars.items()
385
+ }
386
+ return self.format(**dummy_vars)
387
+
388
+ def pretty_print(self) -> None:
389
+ """Print a pretty representation of the prompt."""
390
+ print(self.pretty_repr(html=is_interactive_env())) # noqa: T201
391
+
392
+
393
+ def is_subsequence(child: Sequence[Any], parent: Sequence[Any]) -> bool:
394
+ """Return `True` if child is subsequence of parent."""
395
+ if len(child) == 0 or len(parent) == 0:
396
+ return False
397
+ if len(parent) < len(child):
398
+ return False
399
+ return all(child[i] == parent[i] for i in range(len(child)))
@@ -0,0 +1,214 @@
1
+ """Structured prompt template for a language model."""
2
+
3
+ from collections.abc import (
4
+ AsyncIterator,
5
+ Awaitable,
6
+ Callable,
7
+ Iterator,
8
+ Mapping,
9
+ Sequence,
10
+ )
11
+ from typing import (
12
+ Any,
13
+ overload,
14
+ )
15
+
16
+ from pydantic import BaseModel, Field
17
+ from typing_extensions import override
18
+
19
+ from coco_core._api.beta_decorator import beta
20
+ from coco_core.language_models.base import BaseLanguageModel
21
+ from coco_core.prompt_values import PromptValue
22
+ from coco_core.prompts.chat import (
23
+ ChatPromptTemplate,
24
+ MessageLikeRepresentation,
25
+ )
26
+ from coco_core.prompts.string import PromptTemplateFormat
27
+ from coco_core.runnables.base import (
28
+ Other,
29
+ Runnable,
30
+ RunnableSequence,
31
+ RunnableSerializable,
32
+ )
33
+ from coco_core.utils import get_pydantic_field_names
34
+
35
+
36
+ @beta()
37
+ class StructuredPrompt(ChatPromptTemplate):
38
+ """Structured prompt template for a language model."""
39
+
40
+ schema_: dict[str, Any] | type
41
+ """Schema for the structured prompt."""
42
+
43
+ structured_output_kwargs: dict[str, Any] = Field(default_factory=dict)
44
+
45
+ def __init__(
46
+ self,
47
+ messages: Sequence[MessageLikeRepresentation],
48
+ schema_: dict[str, Any] | type[BaseModel] | None = None,
49
+ *,
50
+ structured_output_kwargs: dict[str, Any] | None = None,
51
+ template_format: PromptTemplateFormat = "f-string",
52
+ **kwargs: Any,
53
+ ) -> None:
54
+ """Create a structured prompt template.
55
+
56
+ Args:
57
+ messages: Sequence of messages.
58
+ schema_: Schema for the structured prompt.
59
+ structured_output_kwargs: Additional kwargs for structured output.
60
+ template_format: Template format for the prompt.
61
+
62
+ Raises:
63
+ ValueError: If schema is not provided.
64
+ """
65
+ schema_ = schema_ or kwargs.pop("schema", None)
66
+ if not schema_:
67
+ err_msg = (
68
+ "Must pass in a non-empty structured output schema. Received: "
69
+ f"{schema_}"
70
+ )
71
+ raise ValueError(err_msg)
72
+ # Avoid mutating a caller-provided dict when merging extra kwargs.
73
+ structured_output_kwargs = dict(structured_output_kwargs or {})
74
+ for k in set(kwargs).difference(get_pydantic_field_names(self.__class__)):
75
+ structured_output_kwargs[k] = kwargs.pop(k)
76
+ super().__init__(
77
+ messages=messages,
78
+ schema_=schema_,
79
+ structured_output_kwargs=structured_output_kwargs,
80
+ template_format=template_format,
81
+ **kwargs,
82
+ )
83
+
84
+ @classmethod
85
+ def get_lc_namespace(cls) -> list[str]:
86
+ """Get the namespace of the Coco object.
87
+
88
+ For example, if the class is `coco.llms.openai.OpenAI`, then the namespace
89
+ is `["coco", "llms", "openai"]`
90
+
91
+ Returns:
92
+ The namespace of the Coco object.
93
+ """
94
+ return cls.__module__.split(".")
95
+
96
+ @classmethod
97
+ def from_messages_and_schema(
98
+ cls,
99
+ messages: Sequence[MessageLikeRepresentation],
100
+ schema: dict[str, Any] | type,
101
+ **kwargs: Any,
102
+ ) -> ChatPromptTemplate:
103
+ """Create a chat prompt template from a variety of message formats.
104
+
105
+ Examples:
106
+ Instantiation from a list of message templates:
107
+
108
+ ```python
109
+ from coco_core.prompts import StructuredPrompt
110
+
111
+
112
+ class OutputSchema(BaseModel):
113
+ name: str
114
+ value: int
115
+
116
+
117
+ template = StructuredPrompt(
118
+ [
119
+ ("human", "Hello, how are you?"),
120
+ ("ai", "I'm doing well, thanks!"),
121
+ ("human", "That's good to hear."),
122
+ ],
123
+ OutputSchema,
124
+ )
125
+ ```
126
+
127
+ Args:
128
+ messages: Sequence of message representations.
129
+
130
+ A message can be represented using the following formats:
131
+
132
+ 1. `BaseMessagePromptTemplate`
133
+ 2. `BaseMessage`
134
+ 3. 2-tuple of `(message type, template)`; e.g.,
135
+ `("human", "{user_input}")`
136
+ 4. 2-tuple of `(message class, template)`
137
+ 5. A string which is shorthand for `("human", template)`; e.g.,
138
+ `"{user_input}"`
139
+ schema: A dictionary representation of function call, or a Pydantic model.
140
+ **kwargs: Any additional kwargs to pass through to
141
+ `ChatModel.with_structured_output(schema, **kwargs)`.
142
+
143
+ Returns:
144
+ A structured prompt template
145
+ """
146
+ return cls(messages, schema, **kwargs)
147
+
148
+ @overload
149
+ def __or__(
150
+ self, other: Mapping[str, Any]
151
+ ) -> RunnableSerializable[dict[str, Any], dict[str, Any]]: ...
152
+
153
+ @overload
154
+ def __or__(
155
+ self,
156
+ other: Callable[[PromptValue], Runnable[PromptValue, Other]]
157
+ | Callable[[PromptValue], Awaitable[Runnable[PromptValue, Other]]],
158
+ ) -> RunnableSerializable[dict[str, Any], Other]: ...
159
+
160
+ @overload
161
+ def __or__(
162
+ self,
163
+ other: Runnable[PromptValue, Other]
164
+ | Callable[[Iterator[PromptValue]], Iterator[Other]]
165
+ | Callable[[AsyncIterator[PromptValue]], AsyncIterator[Other]]
166
+ | Callable[[PromptValue], Other],
167
+ ) -> RunnableSerializable[dict[str, Any], Other]: ...
168
+
169
+ @override
170
+ def __or__(
171
+ self,
172
+ other: Runnable[PromptValue, Other]
173
+ | Callable[[Iterator[PromptValue]], Iterator[Other]]
174
+ | Callable[[AsyncIterator[PromptValue]], AsyncIterator[Other]]
175
+ | Callable[[PromptValue], Other]
176
+ | Mapping[str, Runnable[PromptValue, Any] | Callable[[PromptValue], Any] | Any],
177
+ ) -> RunnableSerializable[dict[str, Any], Any]:
178
+ return self.pipe(other)
179
+
180
+ def pipe(
181
+ self,
182
+ *others: Runnable[Any, Other]
183
+ | Callable[[Iterator[Any]], Iterator[Other]]
184
+ | Callable[[AsyncIterator[Any]], AsyncIterator[Other]]
185
+ | Callable[[Any], Other]
186
+ | Mapping[str, Runnable[Any, Other] | Callable[[Any], Other] | Any],
187
+ name: str | None = None,
188
+ ) -> RunnableSerializable[dict[str, Any], Other]:
189
+ """Pipe the structured prompt to a language model.
190
+
191
+ Args:
192
+ others: The language model to pipe the structured prompt to.
193
+ name: The name of the pipeline.
194
+
195
+ Returns:
196
+ A `RunnableSequence` object.
197
+
198
+ Raises:
199
+ NotImplementedError: If the first element of `others` is not a language
200
+ model.
201
+ """
202
+ if (others and isinstance(others[0], BaseLanguageModel)) or hasattr(
203
+ others[0], "with_structured_output"
204
+ ):
205
+ return RunnableSequence(
206
+ self,
207
+ others[0].with_structured_output(
208
+ self.schema_, **self.structured_output_kwargs
209
+ ),
210
+ *others[1:],
211
+ name=name,
212
+ )
213
+ msg = "Structured prompts need to be piped to a language model."
214
+ raise NotImplementedError(msg)
coco_core/py.typed ADDED
File without changes