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.
- coco/__init__.py +3 -0
- coco/agents/__init__.py +9 -0
- coco/agents/_subagent_transformer.py +307 -0
- coco/agents/factory.py +2164 -0
- coco/agents/middleware/__init__.py +100 -0
- coco/agents/middleware/_execution.py +385 -0
- coco/agents/middleware/_redaction.py +454 -0
- coco/agents/middleware/_retry.py +134 -0
- coco/agents/middleware/_trace_policy.py +61 -0
- coco/agents/middleware/context_editing.py +306 -0
- coco/agents/middleware/file_search.py +433 -0
- coco/agents/middleware/human_in_the_loop.py +680 -0
- coco/agents/middleware/internal_call_transformer.py +108 -0
- coco/agents/middleware/model_call_limit.py +267 -0
- coco/agents/middleware/model_fallback.py +417 -0
- coco/agents/middleware/model_retry.py +327 -0
- coco/agents/middleware/pii.py +878 -0
- coco/agents/middleware/provider_tool_search.py +307 -0
- coco/agents/middleware/shell_tool.py +949 -0
- coco/agents/middleware/summarization.py +910 -0
- coco/agents/middleware/todo.py +357 -0
- coco/agents/middleware/tool_call_limit.py +495 -0
- coco/agents/middleware/tool_emulator.py +251 -0
- coco/agents/middleware/tool_error.py +209 -0
- coco/agents/middleware/tool_retry.py +421 -0
- coco/agents/middleware/tool_selection.py +515 -0
- coco/agents/middleware/types.py +2201 -0
- coco/agents/structured_output.py +472 -0
- coco/chat_models/__init__.py +7 -0
- coco/chat_models/base.py +1082 -0
- coco/embeddings/__init__.py +18 -0
- coco/embeddings/base.py +279 -0
- coco/mcp/__init__.py +36 -0
- coco/mcp/adapter.py +245 -0
- coco/mcp/elicitation.py +394 -0
- coco/mcp/tools.py +307 -0
- coco/messages/__init__.py +73 -0
- coco/py.typed +0 -0
- coco/rate_limiters/__init__.py +13 -0
- coco/tools/__init__.py +22 -0
- coco/tools/tool_node.py +20 -0
- coco_anthropic/__init__.py +15 -0
- coco_anthropic/_client_utils.py +81 -0
- coco_anthropic/_compat.py +228 -0
- coco_anthropic/_sdk_compat.py +124 -0
- coco_anthropic/_version.py +3 -0
- coco_anthropic/chat_models.py +3349 -0
- coco_anthropic/data/__init__.py +1 -0
- coco_anthropic/data/_profiles.py +491 -0
- coco_anthropic/data/profile_augmentations.toml +69 -0
- coco_anthropic/experimental.py +140 -0
- coco_anthropic/llms.py +428 -0
- coco_anthropic/middleware/__init__.py +25 -0
- coco_anthropic/middleware/anthropic_tools.py +1197 -0
- coco_anthropic/middleware/bash.py +84 -0
- coco_anthropic/middleware/file_search.py +360 -0
- coco_anthropic/middleware/prompt_caching.py +276 -0
- coco_anthropic/output_parsers.py +93 -0
- coco_anthropic/py.typed +0 -0
- coco_core/__init__.py +20 -0
- coco_core/_api/__init__.py +87 -0
- coco_core/_api/beta_decorator.py +266 -0
- coco_core/_api/deprecation.py +637 -0
- coco_core/_api/internal.py +23 -0
- coco_core/_api/path.py +50 -0
- coco_core/_import_utils.py +41 -0
- coco_core/_protocol.py +625 -0
- coco_core/_security/__init__.py +36 -0
- coco_core/_security/_exceptions.py +9 -0
- coco_core/_security/_policy.py +313 -0
- coco_core/_security/_ssrf_protection.py +155 -0
- coco_core/_security/_transport.py +254 -0
- coco_core/agents.py +258 -0
- coco_core/caches.py +272 -0
- coco_core/callbacks/__init__.py +132 -0
- coco_core/callbacks/base.py +1229 -0
- coco_core/callbacks/file.py +266 -0
- coco_core/callbacks/manager.py +2826 -0
- coco_core/callbacks/stdout.py +123 -0
- coco_core/callbacks/streaming_stdout.py +154 -0
- coco_core/callbacks/usage.py +124 -0
- coco_core/chat_history.py +267 -0
- coco_core/chat_loaders.py +26 -0
- coco_core/chat_sessions.py +19 -0
- coco_core/cross_encoders.py +18 -0
- coco_core/document_loaders/__init__.py +36 -0
- coco_core/document_loaders/base.py +155 -0
- coco_core/document_loaders/blob_loaders.py +38 -0
- coco_core/documents/__init__.py +55 -0
- coco_core/documents/base.py +347 -0
- coco_core/documents/compressor.py +74 -0
- coco_core/documents/transformers.py +79 -0
- coco_core/embeddings/__init__.py +31 -0
- coco_core/embeddings/embeddings.py +78 -0
- coco_core/embeddings/fake.py +129 -0
- coco_core/env.py +22 -0
- coco_core/example_selectors/__init__.py +47 -0
- coco_core/example_selectors/base.py +60 -0
- coco_core/example_selectors/length_based.py +131 -0
- coco_core/example_selectors/semantic_similarity.py +366 -0
- coco_core/exceptions.py +166 -0
- coco_core/globals.py +72 -0
- coco_core/indexing/__init__.py +53 -0
- coco_core/indexing/api.py +954 -0
- coco_core/indexing/base.py +661 -0
- coco_core/indexing/in_memory.py +104 -0
- coco_core/language_models/__init__.py +116 -0
- coco_core/language_models/_compat_bridge.py +844 -0
- coco_core/language_models/_utils.py +343 -0
- coco_core/language_models/base.py +500 -0
- coco_core/language_models/chat_model_stream.py +1525 -0
- coco_core/language_models/chat_models.py +2748 -0
- coco_core/language_models/fake.py +137 -0
- coco_core/language_models/fake_chat_models.py +396 -0
- coco_core/language_models/llms.py +1569 -0
- coco_core/language_models/model_profile.py +183 -0
- coco_core/load/__init__.py +44 -0
- coco_core/load/_validation.py +191 -0
- coco_core/load/dump.py +120 -0
- coco_core/load/load.py +828 -0
- coco_core/load/mapping.py +1097 -0
- coco_core/load/serializable.py +406 -0
- coco_core/messages/__init__.py +198 -0
- coco_core/messages/ai.py +854 -0
- coco_core/messages/base.py +518 -0
- coco_core/messages/block_translators/__init__.py +112 -0
- coco_core/messages/block_translators/anthropic.py +534 -0
- coco_core/messages/block_translators/bedrock.py +108 -0
- coco_core/messages/block_translators/bedrock_converse.py +329 -0
- coco_core/messages/block_translators/coco_v0.py +303 -0
- coco_core/messages/block_translators/google_genai.py +610 -0
- coco_core/messages/block_translators/google_vertexai.py +21 -0
- coco_core/messages/block_translators/groq.py +157 -0
- coco_core/messages/block_translators/openai.py +1089 -0
- coco_core/messages/chat.py +64 -0
- coco_core/messages/content.py +1488 -0
- coco_core/messages/function.py +62 -0
- coco_core/messages/human.py +70 -0
- coco_core/messages/modifier.py +33 -0
- coco_core/messages/system.py +70 -0
- coco_core/messages/tool.py +418 -0
- coco_core/messages/utils.py +2442 -0
- coco_core/output_parsers/__init__.py +101 -0
- coco_core/output_parsers/base.py +361 -0
- coco_core/output_parsers/format_instructions.py +16 -0
- coco_core/output_parsers/json.py +139 -0
- coco_core/output_parsers/list.py +249 -0
- coco_core/output_parsers/openai_functions.py +324 -0
- coco_core/output_parsers/openai_tools.py +383 -0
- coco_core/output_parsers/pydantic.py +141 -0
- coco_core/output_parsers/string.py +63 -0
- coco_core/output_parsers/transform.py +175 -0
- coco_core/output_parsers/xml.py +301 -0
- coco_core/outputs/__init__.py +66 -0
- coco_core/outputs/chat_generation.py +147 -0
- coco_core/outputs/chat_result.py +39 -0
- coco_core/outputs/generation.py +80 -0
- coco_core/outputs/llm_result.py +112 -0
- coco_core/outputs/run_info.py +22 -0
- coco_core/prompt_values.py +161 -0
- coco_core/prompts/__init__.py +101 -0
- coco_core/prompts/base.py +508 -0
- coco_core/prompts/chat.py +1495 -0
- coco_core/prompts/dict.py +177 -0
- coco_core/prompts/few_shot.py +483 -0
- coco_core/prompts/few_shot_with_templates.py +239 -0
- coco_core/prompts/image.py +177 -0
- coco_core/prompts/loading.py +296 -0
- coco_core/prompts/message.py +97 -0
- coco_core/prompts/prompt.py +312 -0
- coco_core/prompts/string.py +399 -0
- coco_core/prompts/structured.py +214 -0
- coco_core/py.typed +0 -0
- coco_core/rate_limiters.py +254 -0
- coco_core/retrievers.py +328 -0
- coco_core/runnables/__init__.py +136 -0
- coco_core/runnables/base.py +6725 -0
- coco_core/runnables/branch.py +457 -0
- coco_core/runnables/config.py +712 -0
- coco_core/runnables/configurable.py +710 -0
- coco_core/runnables/fallbacks.py +663 -0
- coco_core/runnables/graph.py +741 -0
- coco_core/runnables/graph_ascii.py +366 -0
- coco_core/runnables/graph_mermaid.py +503 -0
- coco_core/runnables/graph_png.py +215 -0
- coco_core/runnables/history.py +631 -0
- coco_core/runnables/passthrough.py +849 -0
- coco_core/runnables/retry.py +379 -0
- coco_core/runnables/router.py +239 -0
- coco_core/runnables/schema.py +188 -0
- coco_core/runnables/utils.py +798 -0
- coco_core/stores.py +291 -0
- coco_core/structured_query.py +203 -0
- coco_core/sys_info.py +154 -0
- coco_core/tools/__init__.py +95 -0
- coco_core/tools/base.py +1948 -0
- coco_core/tools/convert.py +497 -0
- coco_core/tools/render.py +67 -0
- coco_core/tools/retriever.py +94 -0
- coco_core/tools/simple.py +208 -0
- coco_core/tools/structured.py +322 -0
- coco_core/tracers/__init__.py +47 -0
- coco_core/tracers/_compat.py +95 -0
- coco_core/tracers/_context.py +337 -0
- coco_core/tracers/_streaming.py +53 -0
- coco_core/tracers/base.py +955 -0
- coco_core/tracers/coco.py +279 -0
- coco_core/tracers/context.py +205 -0
- coco_core/tracers/core.py +766 -0
- coco_core/tracers/event_stream.py +1101 -0
- coco_core/tracers/log_stream.py +765 -0
- coco_core/tracers/memory_stream.py +200 -0
- coco_core/tracers/root_listeners.py +130 -0
- coco_core/tracers/run_collector.py +40 -0
- coco_core/tracers/schemas.py +14 -0
- coco_core/tracers/stdout.py +205 -0
- coco_core/utils/__init__.py +111 -0
- coco_core/utils/_gateway.py +274 -0
- coco_core/utils/_merge.py +218 -0
- coco_core/utils/aiter.py +357 -0
- coco_core/utils/env.py +86 -0
- coco_core/utils/formatting.py +80 -0
- coco_core/utils/function_calling.py +869 -0
- coco_core/utils/html.py +132 -0
- coco_core/utils/image.py +15 -0
- coco_core/utils/input.py +82 -0
- coco_core/utils/interactive_env.py +12 -0
- coco_core/utils/iter.py +229 -0
- coco_core/utils/json.py +230 -0
- coco_core/utils/json_schema.py +273 -0
- coco_core/utils/mustache.py +706 -0
- coco_core/utils/pydantic.py +631 -0
- coco_core/utils/strings.py +69 -0
- coco_core/utils/usage.py +61 -0
- coco_core/utils/utils.py +528 -0
- coco_core/utils/uuid.py +57 -0
- coco_core/vectorstores/__init__.py +53 -0
- coco_core/vectorstores/base.py +1111 -0
- coco_core/vectorstores/in_memory.py +546 -0
- coco_core/vectorstores/utils.py +163 -0
- coco_core/version.py +3 -0
- coco_framework-0.1.0.dist-info/METADATA +184 -0
- coco_framework-0.1.0.dist-info/RECORD +400 -0
- coco_framework-0.1.0.dist-info/WHEEL +4 -0
- coco_framework-0.1.0.dist-info/licenses/LICENSE +24 -0
- coco_framework-0.1.0.dist-info/licenses/NOTICE +15 -0
- coco_graph/_internal/__init__.py +4 -0
- coco_graph/_internal/_cache.py +31 -0
- coco_graph/_internal/_config.py +474 -0
- coco_graph/_internal/_constants.py +140 -0
- coco_graph/_internal/_fields.py +213 -0
- coco_graph/_internal/_future.py +220 -0
- coco_graph/_internal/_pydantic.py +275 -0
- coco_graph/_internal/_queue.py +124 -0
- coco_graph/_internal/_replay.py +90 -0
- coco_graph/_internal/_retry.py +29 -0
- coco_graph/_internal/_runnable.py +969 -0
- coco_graph/_internal/_scratchpad.py +19 -0
- coco_graph/_internal/_serde.py +253 -0
- coco_graph/_internal/_timeout.py +25 -0
- coco_graph/_internal/_typing.py +54 -0
- coco_graph/cache/base/__init__.py +48 -0
- coco_graph/cache/base/py.typed +0 -0
- coco_graph/cache/memory/__init__.py +73 -0
- coco_graph/cache/redis/__init__.py +144 -0
- coco_graph/cache/sqlite/__init__.py +120 -0
- coco_graph/callbacks.py +394 -0
- coco_graph/channels/__init__.py +29 -0
- coco_graph/channels/any_value.py +72 -0
- coco_graph/channels/base.py +121 -0
- coco_graph/channels/binop.py +155 -0
- coco_graph/channels/delta.py +202 -0
- coco_graph/channels/ephemeral_value.py +79 -0
- coco_graph/channels/last_value.py +151 -0
- coco_graph/channels/named_barrier_value.py +167 -0
- coco_graph/channels/topic.py +94 -0
- coco_graph/channels/untracked_value.py +73 -0
- coco_graph/checkpoint/base/__init__.py +861 -0
- coco_graph/checkpoint/base/id.py +109 -0
- coco_graph/checkpoint/base/py.typed +0 -0
- coco_graph/checkpoint/memory/__init__.py +698 -0
- coco_graph/checkpoint/memory/py.typed +0 -0
- coco_graph/checkpoint/postgres/__init__.py +602 -0
- coco_graph/checkpoint/postgres/_ainternal.py +23 -0
- coco_graph/checkpoint/postgres/_internal.py +21 -0
- coco_graph/checkpoint/postgres/aio.py +691 -0
- coco_graph/checkpoint/postgres/base.py +666 -0
- coco_graph/checkpoint/postgres/py.typed +0 -0
- coco_graph/checkpoint/postgres/shallow.py +967 -0
- coco_graph/checkpoint/serde/__init__.py +0 -0
- coco_graph/checkpoint/serde/_msgpack.py +97 -0
- coco_graph/checkpoint/serde/base.py +64 -0
- coco_graph/checkpoint/serde/encrypted.py +80 -0
- coco_graph/checkpoint/serde/event_hooks.py +52 -0
- coco_graph/checkpoint/serde/jsonplus.py +883 -0
- coco_graph/checkpoint/serde/py.typed +0 -0
- coco_graph/checkpoint/serde/types.py +68 -0
- coco_graph/checkpoint/sqlite/__init__.py +645 -0
- coco_graph/checkpoint/sqlite/_delta.py +172 -0
- coco_graph/checkpoint/sqlite/aio.py +738 -0
- coco_graph/checkpoint/sqlite/py.typed +0 -0
- coco_graph/checkpoint/sqlite/utils.py +116 -0
- coco_graph/config.py +196 -0
- coco_graph/constants.py +64 -0
- coco_graph/errors.py +241 -0
- coco_graph/func/__init__.py +620 -0
- coco_graph/graph/__init__.py +12 -0
- coco_graph/graph/_branch.py +225 -0
- coco_graph/graph/_node.py +103 -0
- coco_graph/graph/message.py +437 -0
- coco_graph/graph/state.py +1978 -0
- coco_graph/graph/ui.py +227 -0
- coco_graph/managed/__init__.py +3 -0
- coco_graph/managed/base.py +31 -0
- coco_graph/managed/is_last_step.py +24 -0
- coco_graph/prebuilt/__init__.py +23 -0
- coco_graph/prebuilt/_tool_call_stream.py +117 -0
- coco_graph/prebuilt/_tool_call_transformer.py +165 -0
- coco_graph/prebuilt/chat_agent_executor.py +1015 -0
- coco_graph/prebuilt/interrupt.py +105 -0
- coco_graph/prebuilt/py.typed +0 -0
- coco_graph/prebuilt/tool_node.py +2030 -0
- coco_graph/prebuilt/tool_validator.py +221 -0
- coco_graph/pregel/__init__.py +3 -0
- coco_graph/pregel/_algo.py +1460 -0
- coco_graph/pregel/_call.py +298 -0
- coco_graph/pregel/_checkpoint.py +331 -0
- coco_graph/pregel/_config.py +0 -0
- coco_graph/pregel/_draw.py +294 -0
- coco_graph/pregel/_executor.py +223 -0
- coco_graph/pregel/_io.py +174 -0
- coco_graph/pregel/_log.py +3 -0
- coco_graph/pregel/_loop.py +1988 -0
- coco_graph/pregel/_messages.py +461 -0
- coco_graph/pregel/_read.py +312 -0
- coco_graph/pregel/_retry.py +854 -0
- coco_graph/pregel/_runner.py +941 -0
- coco_graph/pregel/_tools.py +268 -0
- coco_graph/pregel/_utils.py +221 -0
- coco_graph/pregel/_validate.py +120 -0
- coco_graph/pregel/_write.py +192 -0
- coco_graph/pregel/debug.py +302 -0
- coco_graph/pregel/main.py +4364 -0
- coco_graph/pregel/protocol.py +288 -0
- coco_graph/pregel/types.py +38 -0
- coco_graph/py.typed +0 -0
- coco_graph/runtime.py +350 -0
- coco_graph/store/base/__init__.py +1322 -0
- coco_graph/store/base/batch.py +371 -0
- coco_graph/store/base/embed.py +436 -0
- coco_graph/store/base/py.typed +0 -0
- coco_graph/store/memory/__init__.py +592 -0
- coco_graph/store/memory/py.typed +0 -0
- coco_graph/store/postgres/__init__.py +4 -0
- coco_graph/store/postgres/aio.py +592 -0
- coco_graph/store/postgres/base.py +1480 -0
- coco_graph/store/postgres/py.typed +0 -0
- coco_graph/store/sqlite/__init__.py +4 -0
- coco_graph/store/sqlite/aio.py +632 -0
- coco_graph/store/sqlite/base.py +1514 -0
- coco_graph/stream/__init__.py +45 -0
- coco_graph/stream/_convert.py +32 -0
- coco_graph/stream/_mux.py +523 -0
- coco_graph/stream/_types.py +330 -0
- coco_graph/stream/run_stream.py +735 -0
- coco_graph/stream/stream_channel.py +341 -0
- coco_graph/stream/transformers.py +1039 -0
- coco_graph/types.py +1079 -0
- coco_graph/typing.py +48 -0
- coco_graph/utils/__init__.py +1 -0
- coco_graph/utils/config.py +4 -0
- coco_graph/utils/runnable.py +3 -0
- coco_graph/version.py +13 -0
- coco_graph/warnings.py +69 -0
- coco_openai/__init__.py +20 -0
- coco_openai/_compat.py +45 -0
- coco_openai/_version.py +3 -0
- coco_openai/chat_models/__init__.py +6 -0
- coco_openai/chat_models/_client_utils.py +684 -0
- coco_openai/chat_models/_compat.py +525 -0
- coco_openai/chat_models/azure.py +1227 -0
- coco_openai/chat_models/base.py +5654 -0
- coco_openai/chat_models/codex.py +557 -0
- coco_openai/chatgpt_oauth.py +1063 -0
- coco_openai/data/__init__.py +1 -0
- coco_openai/data/_profiles.py +1655 -0
- coco_openai/data/profile_augmentations.toml +127 -0
- coco_openai/embeddings/__init__.py +6 -0
- coco_openai/embeddings/azure.py +240 -0
- coco_openai/embeddings/base.py +820 -0
- coco_openai/llms/__init__.py +6 -0
- coco_openai/llms/azure.py +249 -0
- coco_openai/llms/base.py +919 -0
- coco_openai/middleware/__init__.py +11 -0
- coco_openai/middleware/openai_moderation.py +484 -0
- coco_openai/output_parsers/__init__.py +9 -0
- coco_openai/output_parsers/tools.py +9 -0
- coco_openai/py.typed +0 -0
- coco_openai/tools/__init__.py +5 -0
- 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
|