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,296 @@
1
+ """Load prompts."""
2
+
3
+ import json
4
+ import logging
5
+ from collections.abc import Callable
6
+ from pathlib import Path
7
+ from typing import Any
8
+
9
+ import yaml
10
+
11
+ from coco_core._api import deprecated
12
+ from coco_core.output_parsers.string import StrOutputParser
13
+ from coco_core.prompts.base import BasePromptTemplate
14
+ from coco_core.prompts.chat import ChatPromptTemplate
15
+ from coco_core.prompts.few_shot import FewShotPromptTemplate
16
+ from coco_core.prompts.prompt import PromptTemplate
17
+
18
+ URL_BASE = "https://raw.githubusercontent.com/hwchase17/langchain-hub/master/prompts/"
19
+ logger = logging.getLogger(__name__)
20
+
21
+
22
+ def _validate_path(path: Path) -> None:
23
+ """Reject absolute paths and `..` traversal components.
24
+
25
+ Args:
26
+ path: The path to validate.
27
+
28
+ Raises:
29
+ ValueError: If the path is absolute or contains `..` components.
30
+ """
31
+ if path.is_absolute():
32
+ msg = (
33
+ f"Path '{path}' is absolute. Absolute paths are not allowed "
34
+ f"when loading prompt configurations to prevent path traversal "
35
+ f"attacks. Use relative paths instead, or pass "
36
+ f"`allow_dangerous_paths=True` if you trust the input."
37
+ )
38
+ raise ValueError(msg)
39
+ if ".." in path.parts:
40
+ msg = (
41
+ f"Path '{path}' contains '..' components. Directory traversal "
42
+ f"sequences are not allowed when loading prompt configurations. "
43
+ f"Use direct relative paths instead, or pass "
44
+ f"`allow_dangerous_paths=True` if you trust the input."
45
+ )
46
+ raise ValueError(msg)
47
+
48
+
49
+ @deprecated(
50
+ since="1.2.21",
51
+ removal="2.0.0",
52
+ alternative="Use `dumpd`/`dumps` from `coco_core.load` to serialize "
53
+ "prompts and `load`/`loads` to deserialize them.",
54
+ )
55
+ def load_prompt_from_config(
56
+ config: dict[str, Any], *, allow_dangerous_paths: bool = False
57
+ ) -> BasePromptTemplate[str]:
58
+ """Load prompt from config dict.
59
+
60
+ Args:
61
+ config: Dict containing the prompt configuration.
62
+ allow_dangerous_paths: If `False` (default), file paths in the
63
+ config (such as `template_path`, `examples`, and
64
+ `example_prompt_path`) are validated to reject absolute paths
65
+ and directory traversal (`..`) sequences. Set to `True` only
66
+ if you trust the source of the config.
67
+
68
+ Returns:
69
+ A `PromptTemplate` object.
70
+
71
+ Raises:
72
+ ValueError: If the prompt type is not supported.
73
+ """
74
+ if "_type" not in config:
75
+ logger.warning("No `_type` key found, defaulting to `prompt`.")
76
+ config_type = config.pop("_type", "prompt")
77
+
78
+ if config_type not in type_to_loader_dict:
79
+ msg = f"Loading {config_type} prompt not supported"
80
+ raise ValueError(msg)
81
+
82
+ prompt_loader = type_to_loader_dict[config_type]
83
+ return prompt_loader(config, allow_dangerous_paths=allow_dangerous_paths)
84
+
85
+
86
+ def _load_template(
87
+ var_name: str, config: dict[str, Any], *, allow_dangerous_paths: bool = False
88
+ ) -> dict[str, Any]:
89
+ """Load template from the path if applicable."""
90
+ # Check if template_path exists in config.
91
+ if f"{var_name}_path" in config:
92
+ # If it does, make sure template variable doesn't also exist.
93
+ if var_name in config:
94
+ msg = f"Both `{var_name}_path` and `{var_name}` cannot be provided."
95
+ raise ValueError(msg)
96
+ # Pop the template path from the config.
97
+ template_path = Path(config.pop(f"{var_name}_path"))
98
+ if not allow_dangerous_paths:
99
+ _validate_path(template_path)
100
+ # Resolve symlinks before checking the suffix so that a symlink named
101
+ # "exploit.txt" pointing to a non-.txt file is caught.
102
+ resolved_path = template_path.resolve()
103
+ # Load the template.
104
+ if resolved_path.suffix == ".txt":
105
+ template = resolved_path.read_text(encoding="utf-8")
106
+ else:
107
+ msg = (
108
+ f"Unsupported template file format: '{resolved_path.suffix}'. "
109
+ "Only '.txt' files are supported."
110
+ )
111
+ raise ValueError(msg)
112
+ # Set the template variable to the extracted variable.
113
+ config[var_name] = template
114
+ return config
115
+
116
+
117
+ def _load_examples(
118
+ config: dict[str, Any], *, allow_dangerous_paths: bool = False
119
+ ) -> dict[str, Any]:
120
+ """Load examples if necessary."""
121
+ if isinstance(config["examples"], list):
122
+ pass
123
+ elif isinstance(config["examples"], str):
124
+ path = Path(config["examples"])
125
+ if not allow_dangerous_paths:
126
+ _validate_path(path)
127
+ with path.open(encoding="utf-8") as f:
128
+ if path.suffix == ".json":
129
+ examples = json.load(f)
130
+ elif path.suffix in {".yaml", ".yml"}:
131
+ examples = yaml.safe_load(f)
132
+ else:
133
+ msg = "Invalid file format. Only json or yaml formats are supported."
134
+ raise ValueError(msg)
135
+ config["examples"] = examples
136
+ else:
137
+ msg = "Invalid examples format. Only list or string are supported."
138
+ raise ValueError(msg) # noqa:TRY004
139
+ return config
140
+
141
+
142
+ def _load_output_parser(config: dict[str, Any]) -> dict[str, Any]:
143
+ """Load output parser."""
144
+ if config_ := config.get("output_parser"):
145
+ if output_parser_type := config_.get("_type") != "default":
146
+ msg = f"Unsupported output parser {output_parser_type}"
147
+ raise ValueError(msg)
148
+ config["output_parser"] = StrOutputParser(**config_)
149
+ return config
150
+
151
+
152
+ def _load_few_shot_prompt(
153
+ config: dict[str, Any], *, allow_dangerous_paths: bool = False
154
+ ) -> FewShotPromptTemplate:
155
+ """Load the "few shot" prompt from the config."""
156
+ # Load the suffix and prefix templates.
157
+ config = _load_template(
158
+ "suffix", config, allow_dangerous_paths=allow_dangerous_paths
159
+ )
160
+ config = _load_template(
161
+ "prefix", config, allow_dangerous_paths=allow_dangerous_paths
162
+ )
163
+ # Load the example prompt.
164
+ if "example_prompt_path" in config:
165
+ if "example_prompt" in config:
166
+ msg = (
167
+ "Only one of example_prompt and example_prompt_path should "
168
+ "be specified."
169
+ )
170
+ raise ValueError(msg)
171
+ example_prompt_path = Path(config.pop("example_prompt_path"))
172
+ if not allow_dangerous_paths:
173
+ _validate_path(example_prompt_path)
174
+ config["example_prompt"] = load_prompt(
175
+ example_prompt_path, allow_dangerous_paths=allow_dangerous_paths
176
+ )
177
+ else:
178
+ config["example_prompt"] = load_prompt_from_config(
179
+ config["example_prompt"], allow_dangerous_paths=allow_dangerous_paths
180
+ )
181
+ # Load the examples.
182
+ config = _load_examples(config, allow_dangerous_paths=allow_dangerous_paths)
183
+ config = _load_output_parser(config)
184
+ return FewShotPromptTemplate(**config)
185
+
186
+
187
+ def _load_prompt(
188
+ config: dict[str, Any], *, allow_dangerous_paths: bool = False
189
+ ) -> PromptTemplate:
190
+ """Load the prompt template from config."""
191
+ # Load the template from disk if necessary.
192
+ config = _load_template(
193
+ "template", config, allow_dangerous_paths=allow_dangerous_paths
194
+ )
195
+ config = _load_output_parser(config)
196
+
197
+ template_format = config.get("template_format", "f-string")
198
+ if template_format == "jinja2":
199
+ # Disabled due to:
200
+ # https://github.com/langchain-ai/langchain/issues/4394
201
+ msg = (
202
+ f"Loading templates with '{template_format}' format is no longer supported "
203
+ f"since it can lead to arbitrary code execution. Please migrate to using "
204
+ f"the 'f-string' template format, which does not suffer from this issue."
205
+ )
206
+ raise ValueError(msg)
207
+
208
+ return PromptTemplate(**config)
209
+
210
+
211
+ @deprecated(
212
+ since="1.2.21",
213
+ removal="2.0.0",
214
+ alternative="Use `dumpd`/`dumps` from `coco_core.load` to serialize "
215
+ "prompts and `load`/`loads` to deserialize them.",
216
+ )
217
+ def load_prompt(
218
+ path: str | Path,
219
+ encoding: str | None = None,
220
+ *,
221
+ allow_dangerous_paths: bool = False,
222
+ ) -> BasePromptTemplate[str]:
223
+ """Unified method for loading a prompt from CocoHub or local filesystem.
224
+
225
+ Args:
226
+ path: Path to the prompt file.
227
+ encoding: Encoding of the file.
228
+ allow_dangerous_paths: If `False` (default), file paths referenced
229
+ inside the loaded config (such as `template_path`, `examples`,
230
+ and `example_prompt_path`) are validated to reject absolute paths
231
+ and directory traversal (`..`) sequences. Set to `True` only
232
+ if you trust the source of the config.
233
+
234
+ Returns:
235
+ A `PromptTemplate` object.
236
+
237
+ Raises:
238
+ RuntimeError: If the path is a CocoHub path.
239
+ """
240
+ if isinstance(path, str) and path.startswith("lc://"):
241
+ msg = (
242
+ "Loading from the deprecated github-based Hub is no longer supported. "
243
+ "Please use the new Coco Hub at https://smith.langchain.com/hub "
244
+ "instead."
245
+ )
246
+ raise RuntimeError(msg)
247
+ return _load_prompt_from_file(
248
+ path, encoding, allow_dangerous_paths=allow_dangerous_paths
249
+ )
250
+
251
+
252
+ def _load_prompt_from_file(
253
+ file: str | Path,
254
+ encoding: str | None = None,
255
+ *,
256
+ allow_dangerous_paths: bool = False,
257
+ ) -> BasePromptTemplate[str]:
258
+ """Load prompt from file."""
259
+ # Convert file to a Path object.
260
+ file_path = Path(file)
261
+ # Load from either json or yaml.
262
+ if file_path.suffix == ".json":
263
+ with file_path.open(encoding=encoding) as f:
264
+ config = json.load(f)
265
+ elif file_path.suffix.endswith((".yaml", ".yml")):
266
+ with file_path.open(encoding=encoding) as f:
267
+ config = yaml.safe_load(f)
268
+ else:
269
+ msg = f"Got unsupported file type {file_path.suffix}"
270
+ raise ValueError(msg)
271
+ # Load the prompt from the config now.
272
+ return load_prompt_from_config(config, allow_dangerous_paths=allow_dangerous_paths)
273
+
274
+
275
+ def _load_chat_prompt(
276
+ config: dict[str, Any],
277
+ *,
278
+ allow_dangerous_paths: bool = False, # noqa: ARG001
279
+ ) -> ChatPromptTemplate:
280
+ """Load chat prompt from config."""
281
+ messages = config.pop("messages")
282
+ template = messages[0]["prompt"].pop("template") if messages else None
283
+ config.pop("input_variables")
284
+
285
+ if not template:
286
+ msg = "Can't load chat prompt without template"
287
+ raise ValueError(msg)
288
+
289
+ return ChatPromptTemplate.from_template(template=template, **config)
290
+
291
+
292
+ type_to_loader_dict: dict[str, Callable[..., BasePromptTemplate[str]]] = {
293
+ "prompt": _load_prompt,
294
+ "few_shot": _load_few_shot_prompt,
295
+ "chat": _load_chat_prompt,
296
+ }
@@ -0,0 +1,97 @@
1
+ """Message prompt templates."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from abc import ABC, abstractmethod
6
+ from typing import TYPE_CHECKING, Any
7
+
8
+ from coco_core.load import Serializable
9
+ from coco_core.utils.interactive_env import is_interactive_env
10
+
11
+ if TYPE_CHECKING:
12
+ from coco_core.messages import BaseMessage
13
+ from coco_core.prompts.chat import ChatPromptTemplate
14
+
15
+
16
+ class BaseMessagePromptTemplate(Serializable, ABC):
17
+ """Base class for message prompt templates."""
18
+
19
+ @classmethod
20
+ def is_lc_serializable(cls) -> bool:
21
+ """Return `True` as this class is serializable."""
22
+ return True
23
+
24
+ @classmethod
25
+ def get_lc_namespace(cls) -> list[str]:
26
+ """Get the namespace of the Coco object.
27
+
28
+ Returns:
29
+ `["coco", "prompts", "chat"]`
30
+ """
31
+ return ["coco", "prompts", "chat"]
32
+
33
+ @abstractmethod
34
+ def format_messages(self, **kwargs: Any) -> list[BaseMessage]:
35
+ """Format messages from kwargs.
36
+
37
+ Should return a list of `BaseMessage` objects.
38
+
39
+ Args:
40
+ **kwargs: Keyword arguments to use for formatting.
41
+
42
+ Returns:
43
+ List of `BaseMessage` objects.
44
+ """
45
+
46
+ async def aformat_messages(self, **kwargs: Any) -> list[BaseMessage]:
47
+ """Async format messages from kwargs.
48
+
49
+ Args:
50
+ **kwargs: Keyword arguments to use for formatting.
51
+
52
+ Returns:
53
+ List of `BaseMessage` objects.
54
+ """
55
+ return self.format_messages(**kwargs)
56
+
57
+ @property
58
+ @abstractmethod
59
+ def input_variables(self) -> list[str]:
60
+ """Input variables for this prompt template.
61
+
62
+ Returns:
63
+ List of input variables.
64
+ """
65
+
66
+ def pretty_repr(
67
+ self,
68
+ html: bool = False, # noqa: FBT001,FBT002
69
+ ) -> str:
70
+ """Human-readable representation.
71
+
72
+ Args:
73
+ html: Whether to format as HTML.
74
+
75
+ Returns:
76
+ Human-readable representation.
77
+ """
78
+ raise NotImplementedError
79
+
80
+ def pretty_print(self) -> None:
81
+ """Print a human-readable representation."""
82
+ print(self.pretty_repr(html=is_interactive_env())) # noqa: T201
83
+
84
+ def __add__(self, other: Any) -> ChatPromptTemplate:
85
+ """Combine two prompt templates.
86
+
87
+ Args:
88
+ other: Another prompt template.
89
+
90
+ Returns:
91
+ Combined prompt template.
92
+ """
93
+ # Import locally to avoid circular import.
94
+ from coco_core.prompts.chat import ChatPromptTemplate # noqa: PLC0415
95
+
96
+ prompt = ChatPromptTemplate(messages=[self])
97
+ return prompt.__add__(other)
@@ -0,0 +1,312 @@
1
+ """Prompt schema definition."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+ from typing import TYPE_CHECKING, Any
7
+
8
+ from pydantic import BaseModel, model_validator
9
+ from typing_extensions import override
10
+
11
+ from coco_core.prompts.string import (
12
+ DEFAULT_FORMATTER_MAPPING,
13
+ PromptTemplateFormat,
14
+ StringPromptTemplate,
15
+ check_valid_template,
16
+ get_template_variables,
17
+ mustache_schema,
18
+ )
19
+
20
+ if TYPE_CHECKING:
21
+ from coco_core.runnables.config import RunnableConfig
22
+
23
+
24
+ class PromptTemplate(StringPromptTemplate):
25
+ """Prompt template for a language model.
26
+
27
+ A prompt template consists of a string template. It accepts a set of parameters
28
+ from the user that can be used to generate a prompt for a language model.
29
+
30
+ The template can be formatted using either f-strings (default), jinja2, or mustache
31
+ syntax.
32
+
33
+ !!! warning "Security"
34
+
35
+ Prefer using `template_format='f-string'` instead of `template_format='jinja2'`,
36
+ or make sure to NEVER accept jinja2 templates from untrusted sources as they may
37
+ lead to arbitrary Python code execution.
38
+
39
+ As of Coco 0.0.329, Jinja2 templates will be rendered using Jinja2's
40
+ SandboxedEnvironment by default. This sand-boxing should be treated as a
41
+ best-effort approach rather than a guarantee of security, as it is an opt-out
42
+ rather than opt-in approach.
43
+
44
+ Despite the sandboxing, we recommend to never use jinja2 templates from
45
+ untrusted sources.
46
+
47
+ Example:
48
+ ```python
49
+ from coco_core.prompts import PromptTemplate
50
+
51
+ # Instantiation using from_template (recommended)
52
+ prompt = PromptTemplate.from_template("Say {foo}")
53
+ prompt.format(foo="bar")
54
+
55
+ # Instantiation using initializer
56
+ prompt = PromptTemplate(template="Say {foo}")
57
+ ```
58
+ """
59
+
60
+ @property
61
+ @override
62
+ def lc_attributes(self) -> dict[str, Any]:
63
+ return {
64
+ "template_format": self.template_format,
65
+ }
66
+
67
+ @classmethod
68
+ @override
69
+ def get_lc_namespace(cls) -> list[str]:
70
+ """Get the namespace of the Coco object.
71
+
72
+ Returns:
73
+ `["coco", "prompts", "prompt"]`
74
+ """
75
+ return ["coco", "prompts", "prompt"]
76
+
77
+ template: str
78
+ """The prompt template."""
79
+
80
+ template_format: PromptTemplateFormat = "f-string"
81
+ """The format of the prompt template.
82
+
83
+ Options are: `'f-string'`, `'mustache'`, `'jinja2'`.
84
+ """
85
+
86
+ validate_template: bool = False
87
+ """Whether or not to try validating the template."""
88
+
89
+ @model_validator(mode="before")
90
+ @classmethod
91
+ def pre_init_validation(cls, values: dict[str, Any]) -> Any:
92
+ """Check that template and input variables are consistent."""
93
+ if values.get("template") is None:
94
+ # Will let pydantic fail with a ValidationError if template
95
+ # is not provided.
96
+ return values
97
+
98
+ # Set some default values based on the field defaults
99
+ values.setdefault("template_format", "f-string")
100
+ values.setdefault("partial_variables", {})
101
+
102
+ if values.get("validate_template"):
103
+ if values["template_format"] == "mustache":
104
+ msg = "Mustache templates cannot be validated."
105
+ raise ValueError(msg)
106
+
107
+ if "input_variables" not in values:
108
+ msg = "Input variables must be provided to validate the template."
109
+ raise ValueError(msg)
110
+
111
+ all_inputs = values["input_variables"] + list(values["partial_variables"])
112
+ check_valid_template(
113
+ values["template"], values["template_format"], all_inputs
114
+ )
115
+
116
+ if values["template_format"]:
117
+ values["input_variables"] = [
118
+ var
119
+ for var in get_template_variables(
120
+ values["template"], values["template_format"]
121
+ )
122
+ if var not in values["partial_variables"]
123
+ ]
124
+
125
+ return values
126
+
127
+ @override
128
+ def get_input_schema(self, config: RunnableConfig | None = None) -> type[BaseModel]:
129
+ """Get the input schema for the prompt.
130
+
131
+ Args:
132
+ config: The runnable configuration.
133
+
134
+ Returns:
135
+ The input schema for the prompt.
136
+ """
137
+ if self.template_format != "mustache":
138
+ return super().get_input_schema(config)
139
+
140
+ return mustache_schema(self.template)
141
+
142
+ def __add__(self, other: Any) -> PromptTemplate:
143
+ """Override the `+` operator to allow for combining prompt templates.
144
+
145
+ Raises:
146
+ ValueError: If the template formats are not f-string or if there are
147
+ conflicting partial variables.
148
+ NotImplementedError: If the other object is not a `PromptTemplate` or str.
149
+
150
+ Returns:
151
+ A new `PromptTemplate` that is the combination of the two.
152
+ """
153
+ # Allow for easy combining
154
+ if isinstance(other, PromptTemplate):
155
+ if self.template_format != other.template_format:
156
+ msg = "Cannot add templates of different formats"
157
+ raise ValueError(msg)
158
+ input_variables = list(
159
+ set(self.input_variables) | set(other.input_variables)
160
+ )
161
+ template = self.template + other.template
162
+ # If any do not want to validate, then don't
163
+ validate_template = self.validate_template and other.validate_template
164
+ partial_variables = dict(self.partial_variables.items())
165
+ for k, v in other.partial_variables.items():
166
+ if k in partial_variables:
167
+ msg = "Cannot have same variable partialed twice."
168
+ raise ValueError(msg)
169
+ partial_variables[k] = v
170
+ return PromptTemplate(
171
+ template=template,
172
+ input_variables=input_variables,
173
+ partial_variables=partial_variables,
174
+ template_format=self.template_format,
175
+ validate_template=validate_template,
176
+ )
177
+ if isinstance(other, str):
178
+ prompt = PromptTemplate.from_template(
179
+ other,
180
+ template_format=self.template_format,
181
+ )
182
+ return self + prompt
183
+ msg = f"Unsupported operand type for +: {type(other)}"
184
+ raise NotImplementedError(msg)
185
+
186
+ @property
187
+ def _prompt_type(self) -> str:
188
+ """Return the prompt type key."""
189
+ return "prompt"
190
+
191
+ def format(self, **kwargs: Any) -> str:
192
+ """Format the prompt with the inputs.
193
+
194
+ Args:
195
+ **kwargs: Any arguments to be passed to the prompt template.
196
+
197
+ Returns:
198
+ A formatted string.
199
+ """
200
+ kwargs = self._merge_partial_and_user_variables(**kwargs)
201
+ return DEFAULT_FORMATTER_MAPPING[self.template_format](self.template, **kwargs)
202
+
203
+ @classmethod
204
+ def from_examples(
205
+ cls,
206
+ examples: list[str],
207
+ suffix: str,
208
+ input_variables: list[str],
209
+ example_separator: str = "\n\n",
210
+ prefix: str = "",
211
+ **kwargs: Any,
212
+ ) -> PromptTemplate:
213
+ """Take examples in list format with prefix and suffix to create a prompt.
214
+
215
+ Intended to be used as a way to dynamically create a prompt from examples.
216
+
217
+ Args:
218
+ examples: List of examples to use in the prompt.
219
+ suffix: String to go after the list of examples.
220
+
221
+ Should generally set up the user's input.
222
+ input_variables: A list of variable names the final prompt template will
223
+ expect.
224
+ example_separator: The separator to use in between examples.
225
+ prefix: String that should go before any examples.
226
+
227
+ Generally includes examples.
228
+
229
+ Returns:
230
+ The final prompt generated.
231
+ """
232
+ template = example_separator.join([prefix, *examples, suffix])
233
+ return cls(input_variables=input_variables, template=template, **kwargs)
234
+
235
+ @classmethod
236
+ def from_file(
237
+ cls,
238
+ template_file: str | Path,
239
+ encoding: str | None = None,
240
+ **kwargs: Any,
241
+ ) -> PromptTemplate:
242
+ """Load a prompt from a file.
243
+
244
+ Args:
245
+ template_file: The path to the file containing the prompt template.
246
+ encoding: The encoding system for opening the template file.
247
+
248
+ If not provided, will use the OS default.
249
+
250
+ Returns:
251
+ The prompt loaded from the file.
252
+ """
253
+ template = Path(template_file).read_text(encoding=encoding)
254
+ return cls.from_template(template=template, **kwargs)
255
+
256
+ @classmethod
257
+ def from_template(
258
+ cls,
259
+ template: str,
260
+ *,
261
+ template_format: PromptTemplateFormat = "f-string",
262
+ partial_variables: dict[str, Any] | None = None,
263
+ **kwargs: Any,
264
+ ) -> PromptTemplate:
265
+ """Load a prompt template from a template.
266
+
267
+ !!! warning "Security"
268
+
269
+ Prefer using `template_format='f-string'` instead of
270
+ `template_format='jinja2'`, or make sure to NEVER accept jinja2 templates
271
+ from untrusted sources as they may lead to arbitrary Python code execution.
272
+
273
+ As of Coco 0.0.329, Jinja2 templates will be rendered using Jinja2's
274
+ SandboxedEnvironment by default. This sand-boxing should be treated as a
275
+ best-effort approach rather than a guarantee of security, as it is an
276
+ opt-out rather than opt-in approach.
277
+
278
+ Despite the sandboxing, we recommend to never use jinja2 templates from
279
+ untrusted sources.
280
+
281
+ Args:
282
+ template: The template to load.
283
+ template_format: The format of the template.
284
+
285
+ Use `jinja2` for jinja2, `mustache` for mustache, and `f-string` for
286
+ f-strings.
287
+ partial_variables: A dictionary of variables that can be used to partially
288
+ fill in the template.
289
+
290
+ For example, if the template is `'{variable1} {variable2}'`, and
291
+ `partial_variables` is `{"variable1": "foo"}`, then the final prompt
292
+ will be `'foo {variable2}'`.
293
+ **kwargs: Any other arguments to pass to the prompt template.
294
+
295
+ Returns:
296
+ The prompt template loaded from the template.
297
+ """
298
+ input_variables = get_template_variables(template, template_format)
299
+ partial_variables_ = partial_variables or {}
300
+
301
+ if partial_variables_:
302
+ input_variables = [
303
+ var for var in input_variables if var not in partial_variables_
304
+ ]
305
+
306
+ return cls(
307
+ input_variables=input_variables,
308
+ template=template,
309
+ template_format=template_format,
310
+ partial_variables=partial_variables_,
311
+ **kwargs,
312
+ )