Enter the main heading, usually the same as the title.
');
+ });
});
diff --git a/pyproject.toml b/pyproject.toml
index 41be260e..1f9892ed 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -3,7 +3,7 @@ name = "poml"
version = "0.0.5"
description = "Prompt Orchestration Markup Language"
readme = "README.md"
-requires-python = ">=3.8"
+requires-python = ">=3.9"
license = {file = "LICENSE"}
dependencies = [
"nodejs-wheel"
diff --git a/python/poml/prompt.py b/python/poml/prompt.py
new file mode 100644
index 00000000..4848f4f9
--- /dev/null
+++ b/python/poml/prompt.py
@@ -0,0 +1,237 @@
+import xml.etree.ElementTree as ET
+import base64
+import json
+import tempfile
+import warnings
+
+from .api import poml # Assuming this exists in your project structure
+
+
+def _write_file_for_poml(content: str):
+ """Writes content to a named temporary file that is not deleted on close."""
+ # The caller is responsible for managing the lifecycle of this file, including deletion.
+ temp_file = tempfile.NamedTemporaryFile(mode="w", encoding="utf-8", delete=False)
+ temp_file.write(content)
+ temp_file.flush() # Ensure content is written to disk
+ # temp_file.close() # Consider if the file should be closed here or by the caller.
+ return temp_file
+
+
+class _ImplicitDualTagHandler:
+ """
+ Handles XML tags that can be self-closing or act as context managers for nested content.
+ It creates an ET.Element on initialization and adds it to the current parent.
+ If used with 'with', it pushes the element onto the Prompt's parent stack.
+ """
+
+ def __init__(self, prompt_instance: "Prompt", tag_name: str, attrs: dict):
+ self.prompt = prompt_instance
+ self.tag_name = tag_name
+
+ prepared_attrs = self.prompt._prepare_attrs(**attrs)
+ self.element = ET.Element(tag_name, prepared_attrs)
+
+ if self.prompt.current_parent_stack:
+ # Append as child to the currently open element
+ self.prompt.current_parent_stack[-1].append(self.element)
+ else:
+ # No parent on stack, so this is a root-level element
+ self.prompt.root_elements.append(self.element)
+
+ self._is_context_managed = False # True if __enter__ completes successfully
+
+ def __enter__(self):
+ # This element now becomes the current parent for any nested tags or text.
+ self.prompt.current_parent_stack.append(self.element)
+ self._is_context_managed = True
+ return self.prompt # Return Prompt instance for chained calls like p.text()
+
+ def __exit__(self, exc_type, exc_val, exc_tb):
+ if not self._is_context_managed:
+ # This means __enter__ did not complete successfully, or the handler
+ # was instantiated but not used correctly in a 'with' statement.
+ raise SystemError(
+ f"Exiting tag handler for '{self.element.tag}' that was not properly context-managed. "
+ "Ensure it's used in a 'with' statement and __enter__ completed."
+ )
+
+ # If __enter__ completed, self.element was pushed onto the stack.
+ if not self.prompt.current_parent_stack:
+ # This indicates a critical internal logic error.
+ raise SystemError(
+ f"Internal error: Tag stack empty while exiting context for '{self.element.tag}'. "
+ "_is_context_managed was True, implying a tag should be on stack."
+ )
+
+ popped_element = self.prompt.current_parent_stack.pop()
+ if popped_element is not self.element:
+ # This is a critical internal error, indicating mismatched tags or stack corruption.
+ self.prompt.current_parent_stack.append(popped_element) # Restore the stack to its previous state
+ raise SystemError(
+ f"XML structure error: Mismatched tag on context exit. Expected to pop '{self.element.tag}', "
+ f"but found '{popped_element.tag}'. This suggests an issue with nested contexts."
+ )
+
+
+class Prompt:
+ """
+ Builds an XML structure using ElementTree, supporting context-managed tags.
+ """
+
+ def __init__(self):
+ self.root_elements: list[ET.Element] = []
+ self.current_parent_stack: list[ET.Element] = [] # Stack of current ET.Element parents
+
+ def _prepare_attrs(self, **attrs) -> dict[str, str]:
+ """Converts attribute values to strings suitable for ElementTree."""
+ prepared = {}
+ for k, v in attrs.items():
+ if v is None: # Skip None attributes
+ continue
+ key_str = str(k) # Keys are typically strings
+ if isinstance(v, bool):
+ val_str = str(v).lower() # XML often uses "true"/"false"
+ elif isinstance(v, bytes):
+ b64 = base64.b64encode(v).decode()
+ if key_str == "buffer":
+ prepared["base64"] = b64
+ continue
+ else:
+ val_str = base64.b64encode(v).decode("ascii")
+ elif isinstance(v, (int, float, str)):
+ val_str = str(v)
+ else:
+ val_str = json.dumps(v) # Fallback for complex types, convert to JSON string
+ prepared[key_str] = val_str
+ return prepared
+
+ def text(self, content: str):
+ """Adds text content to the currently open XML element."""
+ if not self.current_parent_stack:
+ raise ValueError("Cannot add text: No tag is currently open. Use a 'with' block for a tag.")
+
+ current_el = self.current_parent_stack[-1]
+ # ElementTree handles XML escaping for text content automatically
+ content_str = str(content)
+
+ # Append text correctly for mixed content (text between child elements)
+ if len(current_el) > 0: # If current element has children
+ last_child = current_el[-1]
+ if last_child.tail is None:
+ last_child.tail = content_str
+ else:
+ last_child.tail += content_str
+ else: # No children yet in the current element, add to its primary text
+ if current_el.text is None:
+ current_el.text = content_str
+ else:
+ current_el.text += content_str
+
+ def _generate_xml_string(self, pretty: bool) -> str:
+ """
+ Serializes the built XML structure to a string.
+ Can optionally pretty-print the output.
+ """
+ if self.current_parent_stack:
+ # This warning is for cases where rendering/dumping happens with unclosed tags.
+ print(
+ f"Warning: Generating XML with open tags: {[el.tag for el in self.current_parent_stack]}. "
+ "Ensure all 'with' blocks for tags are properly exited before finalizing XML."
+ )
+
+ xml_strings = []
+ for root_el in self.root_elements:
+ if pretty:
+ # ET.indent modifies the element in-place (Python 3.9+)
+ ET.indent(root_el, space=" ", level=0)
+ xml_strings.append(ET.tostring(root_el, encoding="unicode", method="xml"))
+ else:
+ # Serialize compactly without extra whitespace
+ xml_strings.append(ET.tostring(root_el, encoding="unicode", method="xml"))
+
+ # Join the string representations of each root-level element.
+ # If pretty printing and multiple roots, join with newlines for readability.
+ # Otherwise, join directly to form a contiguous XML stream.
+ joiner = "\n" if pretty and len(xml_strings) > 0 else "" # Add newline between pretty roots
+ return joiner.join(xml_strings)
+
+ def render(self, chat: bool = True, context=None, stylesheet=None) -> list | dict | str:
+ """
+ Renders the final XML. Raises error if tags are still open.
+ """
+ if self.current_parent_stack:
+ raise ValueError(
+ f"Cannot render: Open tags remaining: {[el.tag for el in self.current_parent_stack]}. "
+ "Ensure all 'with' blocks for tags are properly exited."
+ )
+ # poml likely expects a compact, single XML string.
+ final_xml = self._generate_xml_string(pretty=False)
+ return poml(final_xml, context=context, stylesheet=stylesheet, chat=chat)
+
+ def dump_xml(self) -> str:
+ """
+ Dumps the generated XML string, pretty-printed by default (useful for debugging).
+ """
+ return self._generate_xml_string(pretty=True)
+
+ def __enter__(self):
+ """Initializes Prompt for a new XML construction session within a 'with' block."""
+ self.root_elements = []
+ self.current_parent_stack = []
+ return self
+
+ def __exit__(self, exc_type, exc_val, exc_tb):
+ """Cleans up Prompt state upon exiting a 'with' block."""
+ if self.current_parent_stack and exc_type is None:
+ # This means the Prompt context itself exited while some _ImplicitDualTagHandler
+ # contexts (tags) were still notionally open.
+ warnings.warn(
+ f"Warning: Prompt context exited with open tags: {[el.tag for el in self.current_parent_stack]}. "
+ "This may indicate nested tag context managers were not properly closed before the Prompt context ended."
+ )
+
+ # Consistent with original behavior: clear internal state on exit.
+ # This means results should typically be obtained (via render/dump_xml)
+ # before the Prompt's 'with' block finishes if Prompt itself is a context manager.
+ self.root_elements.clear()
+ self.current_parent_stack.clear()
+
+ # --- Tag-specific methods using the dual-use handler ---
+ # These methods provide a convenient API for creating specific XML tags.
+
+ def task(self, **attrs) -> _ImplicitDualTagHandler:
+ """Task tag. Use with `with` for content, or call directly for self-closing."""
+ return _ImplicitDualTagHandler(self, "Task", attrs)
+
+ def document(self, **attrs) -> _ImplicitDualTagHandler:
+ """Document tag. Use with `with` for content, or call directly for self-closing."""
+ return _ImplicitDualTagHandler(self, "Document", attrs)
+
+ def p(self, **attrs) -> _ImplicitDualTagHandler:
+ """Paragraph tag. Use with `with` for content, or call directly for self-closing."""
+ return _ImplicitDualTagHandler(self, "p", attrs)
+
+ # Add any other tag from your JSON schema here, following the same pattern:
+ # def some_tag_name(self, **attrs) -> _ImplicitDualTagHandler:
+ # return _ImplicitDualTagHandler(self, "SomeTagName", attrs)
+
+
+if __name__ == '__main__':
+ # Example usage of the Prompt class
+ with Prompt() as p:
+ with p.p():
+ with p.task(id="task1", status="open"):
+ p.text("This is a task description.")
+ with p.p():
+ p.text("This is a paragraph in the document.")
+
+ xml_output = p.dump_xml() # Get pretty-printed XML for debugging
+ print(xml_output)
+ prompt_output = p.render()
+ print(prompt_output)
+
+ #
+ # This is a task description.
+ #
This is a paragraph in the document.
+ #
+ # [{'speaker': 'human', 'content': '# Task\n\nThis is a task description.\n\nThis is a paragraph in the document.'}]
From cc7ff6e620d47552eaaac2159fe42fe9a09a74c9 Mon Sep 17 00:00:00 2001
From: Yuge Zhang
Date: Thu, 5 Jun 2025 17:40:15 +0800
Subject: [PATCH 2/4] taglib gen
---
docs/components.md | 2 +
packages/poml-vscode/lsp/parseComments.ts | 155 ++-
packages/poml/assets/componentDocs.json | 14 +
python/poml/_tags.py | 1277 +++++++++++++++++++++
python/poml/prompt.py | 29 +-
5 files changed, 1442 insertions(+), 35 deletions(-)
create mode 100644 python/poml/_tags.py
diff --git a/docs/components.md b/docs/components.md
index 2e248ca5..f8479803 100644
--- a/docs/components.md
+++ b/docs/components.md
@@ -748,6 +748,7 @@ To display a Word document without including the real multimedia:
- **src**: The source file to read the data from. This must be provided if records is not provided.
- **buffer**: Buffer. Document data buffer. Recommended to use `src` instead unless you want to use a string.
+- **base64**: Base64 encoded string of the document data. Mutually exclusive with `src` and `buffer`.
- **parser**: Can be one of: auto, pdf, docx, txt. The parser to use for reading the data. If not provided, it will be inferred from the file extension.
- **multimedia**: Boolean. If true, the multimedias will be displayed. If false, the alt strings will be displayed at best effort. Default is `true`.
- **selectedPages**: The pages to be selected. This is only available **for PDF documents**. If not provided, all pages will be selected.
@@ -998,6 +999,7 @@ Convert HTML to structured POML components:
- **url**: The URL of the webpage to fetch and display.
- **src**: Local file path to an HTML file to display.
- **buffer**: Buffer. HTML content as string or buffer.
+- **base64**: Base64 encoded HTML content.
- **extractText**: Boolean. Whether to extract plain text content (true) or convert HTML to structured POML (false). Default is false.
- **selector**: CSS selector to extract specific content from the page (e.g., "article", ".content", "#main"). Default is "body".
- **syntax**: Can be one of: markdown, html, json, yaml, xml, text. The syntax of the content.
diff --git a/packages/poml-vscode/lsp/parseComments.ts b/packages/poml-vscode/lsp/parseComments.ts
index 5cff5fed..a1107f00 100644
--- a/packages/poml-vscode/lsp/parseComments.ts
+++ b/packages/poml-vscode/lsp/parseComments.ts
@@ -1,9 +1,9 @@
-import "poml";
-import { ComponentSpec, Parameter } from "poml/base";
+import 'poml';
+import { ComponentSpec, Parameter } from 'poml/base';
-import { readFileSync, readdirSync, writeFileSync } from "fs";
-import { join } from "path";
-import { formatComponentDocumentation } from "./documentFormatter";
+import { readFileSync, readdirSync, writeFile, writeFileSync } from 'fs';
+import { join } from 'path';
+import { formatComponentDocumentation } from './documentFormatter';
const basicComponents: string[] = [];
const intentions: string[] = [];
@@ -18,13 +18,14 @@ function tsCommentToMarkdown(comment: string): ComponentSpec {
.replace(/^\/\*\*?/, '')
.replace(/\*\/$/, '')
.split('\n')
- .map((line) => line.replace(/^\s*\*( )?/, ''))
- .map((line) => line.replace(/\s+$/, ''))
+ .map(line => line.replace(/^\s*\*( )?/, ''))
+ .map(line => line.replace(/\s+$/, ''))
.join('\n');
// Recognize description, @param and @example in the comment.
const descriptionRegex = /([\s\S]*?)(?=@param|@example|@see|$)/;
- const paramRegex = /@param\s+(\{([\S'"\|]+?)\}\s+)?(\w+)\s+-\s+([\s\S]*?)(?=@param|@example|@see|$)/g;
+ const paramRegex =
+ /@param\s+(\{([\S'"\|]+?)\}\s+)?(\w+)\s+-\s+([\s\S]*?)(?=@param|@example|@see|$)/g;
const exampleRegex = /@example\s+([\s\S]*?)(?=@param|@example|@see|$)/;
const seeRegex = /@see\s+([\s\S]*?)(?=@param|@example|@see|$)/g;
@@ -50,7 +51,7 @@ function tsCommentToMarkdown(comment: string): ComponentSpec {
fallbackType = 'string';
} else if (paramMatch[2] && paramMatch[2].includes('|')) {
type = 'string';
- choices = paramMatch[2].split('|').map((choice) => choice.replace(/['"\s]/g, '').trim());
+ choices = paramMatch[2].split('|').map(choice => choice.replace(/['"\s]/g, '').trim());
} else if (paramMatch[2]) {
type = paramMatch[2];
}
@@ -80,7 +81,7 @@ function tsCommentToMarkdown(comment: string): ComponentSpec {
params,
example,
baseComponents
- }
+ };
}
function extractTsComments(text: string) {
@@ -95,7 +96,8 @@ function extractTsComments(text: string) {
function extractComponentComments(text: string) {
const comments: ComponentSpec[] = [];
- const commentRegex = /(\/\*\*([\s\S]*?)\*\/)\nexport const [\w]+ = component\(['"](\w+)['"](,[\S\s]*?)?\)/g;
+ const commentRegex =
+ /(\/\*\*([\s\S]*?)\*\/)\nexport const [\w]+ = component\(['"](\w+)['"](,[\S\s]*?)?\)/g;
let match;
while ((match = commentRegex.exec(text)) !== null) {
const doc = { name: match[3], ...tsCommentToMarkdown(match[2]) };
@@ -104,7 +106,6 @@ function extractComponentComments(text: string) {
return comments;
}
-
function* walk(folderPath: string): IterableIterator {
for (const entry of readdirSync(folderPath, { withFileTypes: true })) {
if (entry.isFile() && (entry.name.endsWith('.tsx') || entry.name.endsWith('.ts'))) {
@@ -135,7 +136,7 @@ function scanComponentDocs(folderPath: string) {
} else {
utilities.push(...names);
}
- };
+ }
return allComments;
}
@@ -159,6 +160,134 @@ function docsToMarkdown(docs: ComponentSpec[]) {
return parts.join('\n\n');
}
+function camelToSnake(str: string): string {
+ return str
+ .replace(/([A-Z]+)([A-Z][a-z])/g, '$1_$2') // Handles cases like "XMLFile" -> "XML_File"
+ .replace(/([a-z\d])([A-Z])/g, '$1_$2') // Handles "camelCase" -> "camel_Case"
+ .toLowerCase(); // Converts to lowercase: "XML_File" -> "xml_file"
+}
+
+function getPythonType(jsonType: string, paramName: string): string {
+ const lcJsonType = jsonType.toLowerCase();
+ switch (lcJsonType) {
+ case 'string':
+ return 'str';
+ case 'boolean':
+ return 'bool';
+ case 'buffer':
+ return 'bytes';
+ case 'number':
+ // Heuristic for int vs float based on common parameter names
+ if (
+ paramName.includes('max') ||
+ paramName.includes('count') ||
+ paramName.includes('depth') ||
+ paramName.endsWith('Index')
+ ) {
+ return 'int';
+ }
+ return 'float';
+ case 'object':
+ return 'Any'; // Could be Dict[str, Any]
+ case 'regexp':
+ return 'str'; // Python uses strings for regex patterns
+ default:
+ if (jsonType.endsWith('[]')) {
+ // Handles array types like TreeItemData[]
+ return 'List[Any]'; // Generic list type
+ }
+ // For unknown or complex non-array types (e.g., a specific object schema name)
+ return 'Any';
+ }
+}
+
+function generatePythonMethod(tag: ComponentSpec): string {
+ const methodName = camelToSnake(tag.name!);
+ let paramsSignatureList: string[] = [' self'];
+ let argsDocstring = '';
+ const callArgsList: string[] = [`tag_name="${tag.name}"`];
+
+ tag.params.forEach(param => {
+ const paramName = param.name; // Use original JSON name for Python parameter
+ const pythonType = getPythonType(param.type, paramName);
+ const typeHint = `Optional[${pythonType}]`;
+
+ paramsSignatureList.push(` ${paramName}: ${typeHint} = None`);
+ callArgsList.push(`${paramName}=${paramName}`);
+
+ let paramDesc = param.description.replace(/\n/g, '\n ');
+ if (param.defaultValue !== undefined) {
+ const defValStr =
+ typeof param.defaultValue === 'string' ? `"${param.defaultValue}"` : param.defaultValue;
+ paramDesc += ` Default is \`${defValStr}\`.`;
+ }
+ if (param.choices && param.choices.length > 0) {
+ paramDesc += ` Choices: ${param.choices.map(c => `\`${JSON.stringify(c)}\``).join(', ')}.`;
+ }
+ argsDocstring += ` ${paramName} (${typeHint}): ${paramDesc}\n`;
+ });
+
+ paramsSignatureList.push(' **kwargs: Any');
+
+ const paramsString = paramsSignatureList.join(',\n');
+
+ let docstring = `"""${tag.description.replace(/\n/g, '\n ')}\n\n`;
+ if (argsDocstring) {
+ docstring += ` Args:\n${argsDocstring}`;
+ }
+ if (tag.example) {
+ const exampleIndented = tag.example
+ .replace(/\\/g, '\\\\') // Escape backslashes for string literal
+ .replace(/"""/g, '\\"\\"\\"') // Escape triple quotes if any in example
+ .replace(/\n/g, '\n ');
+ docstring += `\n Example:\n ${exampleIndented}\n`;
+ }
+ docstring += ` """`;
+
+ const methodBody = `return self._tag(
+ ${callArgsList.join(',\n ')},
+ **kwargs,
+ )`;
+
+ return `
+ def ${methodName}(
+${paramsString},
+ ):
+ ${docstring}
+ ${methodBody}
+ `;
+}
+
+function generatePythonFile(jsonData: ComponentSpec[]): string {
+ let pythonCode = `# This file is auto-generated from component documentation.
+# Do not edit manually. Run \`npm run build-comment\` to regenerate.
+
+from typing import Optional, Any, Union, List, Dict
+# from numbers import Number # For more specific number types if needed
+
+class _TagLib:
+
+ def tag(self, tag_name: str, **kwargs: Any) -> Any:
+ """Helper method to create a tag with the given name and attributes.
+ Implemented by subclasses.
+ """
+ raise NotImplementedError("This method should be implemented by subclasses.")
+`;
+
+ jsonData.forEach(tag => {
+ if (!tag.name) {
+ console.warn('Skipping tag with no name:', tag);
+ return;
+ }
+ pythonCode += generatePythonMethod(tag);
+ });
+
+ return pythonCode;
+}
+
const allDocs = scanComponentDocs('packages/poml');
+const pythonCode = generatePythonFile(allDocs);
writeFileSync('packages/poml/assets/componentDocs.json', JSON.stringify(allDocs, null, 2));
writeFileSync('docs/components.md', docsToMarkdown(allDocs));
+writeFileSync('python/poml/_tags.py', pythonCode);
+console.log('Component documentation generated successfully!');
diff --git a/packages/poml/assets/componentDocs.json b/packages/poml/assets/componentDocs.json
index 7086e93b..976506af 100644
--- a/packages/poml/assets/componentDocs.json
+++ b/packages/poml/assets/componentDocs.json
@@ -18,6 +18,13 @@
"description": "Document data buffer. Recommended to use `src` instead unless you want to use a string.",
"required": false
},
+ {
+ "name": "base64",
+ "type": "string",
+ "choices": [],
+ "description": "Base64 encoded string of the document data. Mutually exclusive with `src` and `buffer`.",
+ "required": false
+ },
{
"name": "parser",
"type": "string",
@@ -1106,6 +1113,13 @@
"description": "HTML content as string or buffer.",
"required": false
},
+ {
+ "name": "base64",
+ "type": "string",
+ "choices": [],
+ "description": "Base64 encoded HTML content.",
+ "required": false
+ },
{
"name": "extractText",
"type": "boolean",
diff --git a/python/poml/_tags.py b/python/poml/_tags.py
new file mode 100644
index 00000000..19314a0b
--- /dev/null
+++ b/python/poml/_tags.py
@@ -0,0 +1,1277 @@
+# This file is auto-generated from component documentation.
+# Do not edit manually. Run `npm run build-comment` to regenerate.
+
+from typing import Optional, Any, Union, List, Dict
+# from numbers import Number # For more specific number types if needed
+
+class _TagLib:
+
+ def tag(self, tag_name: str, **kwargs: Any) -> Any:
+ """Helper method to create a tag with the given name and attributes.
+ Implemented by subclasses.
+ """
+ raise NotImplementedError("This method should be implemented by subclasses.")
+
+ def document(
+ self,
+ src: Optional[str] = None,
+ buffer: Optional[bytes] = None,
+ base64: Optional[str] = None,
+ parser: Optional[str] = None,
+ multimedia: Optional[bool] = None,
+ selectedPages: Optional[str] = None,
+ **kwargs: Any,
+ ):
+ """Displaying an external document like PDF, TXT or DOCX.
+
+ Args:
+ src (Optional[str]): The source file to read the data from. This must be provided if records is not provided.
+ buffer (Optional[bytes]): Document data buffer. Recommended to use `src` instead unless you want to use a string.
+ base64 (Optional[str]): Base64 encoded string of the document data. Mutually exclusive with `src` and `buffer`.
+ parser (Optional[str]): The parser to use for reading the data. If not provided, it will be inferred from the file extension. Choices: `"auto"`, `"pdf"`, `"docx"`, `"txt"`.
+ multimedia (Optional[bool]): If true, the multimedias will be displayed. If false, the alt strings will be displayed at best effort. Default is `true`. Default is `"true"`.
+ selectedPages (Optional[str]): The pages to be selected. This is only available **for PDF documents**. If not provided, all pages will be selected.
+ You can use a string like `2` to specify a single page, or slice like `2:4` to specify a range of pages (2 inclusive, 4 exclusive).
+ The pages selected are **0-indexed**. Negative indexes like `-1` is not supported here.
+
+ Example:
+ To display a Word document without including the real multimedia:
+ ```xml
+
+ ```
+ """
+ return self._tag(
+ tag_name="Document",
+ src=src,
+ buffer=buffer,
+ base64=base64,
+ parser=parser,
+ multimedia=multimedia,
+ selectedPages=selectedPages,
+ **kwargs,
+ )
+
+ def role(
+ self,
+ caption: Optional[str] = None,
+ captionSerialized: Optional[str] = None,
+ captionStyle: Optional[str] = None,
+ captionTextTransform: Optional[str] = None,
+ captionEnding: Optional[str] = None,
+ **kwargs: Any,
+ ):
+ """Specifies the role you want the language model to assume when responding.
+ Defining a role provides the model with a perspective or context,
+ such as a scientist, poet, child, or any other persona you choose.
+
+ Args:
+ caption (Optional[str]): The title or label for the role paragraph. Default is `Role`. Default is `"Role"`.
+ captionSerialized (Optional[str]): The serialized version of the caption when using "serializer" syntaxes. Default is `role`. Default is `"role"`.
+ captionStyle (Optional[str]): Determines the style of the caption,
+ applicable only for "markup" syntaxes. Default is `header`. Default is `"header"`. Choices: `"header"`, `"bold"`, `"plain"`, `"hidden"`.
+ captionTextTransform (Optional[str]): Specifies text transformation for the caption, applicable only for "markup" syntaxes. Default is `none`. Default is `"none"`. Choices: `"upper"`, `"level"`, `"capitalize"`, `"none"`.
+ captionEnding (Optional[str]): A caption can ends with a colon, a newline or simply nothing.
+ If not specified, it defaults to `colon` for `bold` or `plain` captionStyle, and `none` otherwise. Choices: `"colon"`, `"newline"`, `"colon-newline"`, `"none"`.
+
+ Example:
+ ```xml
+ You are a data scientist.
+ ```
+ """
+ return self._tag(
+ tag_name="Role",
+ caption=caption,
+ captionSerialized=captionSerialized,
+ captionStyle=captionStyle,
+ captionTextTransform=captionTextTransform,
+ captionEnding=captionEnding,
+ **kwargs,
+ )
+
+ def task(
+ self,
+ caption: Optional[str] = None,
+ captionSerialized: Optional[str] = None,
+ captionStyle: Optional[str] = None,
+ captionTextTransform: Optional[str] = None,
+ captionEnding: Optional[str] = None,
+ **kwargs: Any,
+ ):
+ """Task represents the action you want the language model to perform.
+ It is a directive or instruction that you want the model to follow.
+ Task is usually not long, but rather a concise and clear statement.
+ Users can also include a list of steps or instructions to complete the task.
+
+ Args:
+ caption (Optional[str]): The title or label for the task paragraph. Default is `Task`. Default is `"Task"`.
+ captionSerialized (Optional[str]): The serialized version of the caption when using "serializer" syntaxes. Default is `task`. Default is `"task"`.
+ captionStyle (Optional[str]): Determines the style of the caption,
+ applicable only for "markup" syntaxes. Default is `header`. Default is `"header"`. Choices: `"header"`, `"bold"`, `"plain"`, `"hidden"`.
+ captionTextTransform (Optional[str]): Specifies text transformation for the caption, applicable only for "markup" syntaxes. Default is `none`. Default is `"none"`. Choices: `"upper"`, `"level"`, `"capitalize"`, `"none"`.
+ captionEnding (Optional[str]): A caption can ends with a colon, a newline or simply nothing.
+ If not specified, it defaults to `colon` for `bold` or `plain` captionStyle, and `none` otherwise. Choices: `"colon"`, `"newline"`, `"colon-newline"`, `"none"`.
+
+ Example:
+ ```xml
+ Cook a recipe on how to prepare a beef dish.
+ ```
+
+ When including a list of steps:
+ ```xml
+
+ Planning a schedule for a travel.
+
+ Decide on the destination and plan the duration.
+ Find useful information about the destination.
+ Write down the schedule for each day.
+
+
+ ```
+ """
+ return self._tag(
+ tag_name="Task",
+ caption=caption,
+ captionSerialized=captionSerialized,
+ captionStyle=captionStyle,
+ captionTextTransform=captionTextTransform,
+ captionEnding=captionEnding,
+ **kwargs,
+ )
+
+ def output_format(
+ self,
+ caption: Optional[str] = None,
+ captionSerialized: Optional[str] = None,
+ captionStyle: Optional[str] = None,
+ captionTextTransform: Optional[str] = None,
+ captionEnding: Optional[str] = None,
+ **kwargs: Any,
+ ):
+ """Output format deals with the format in which the model should provide the output.
+ It can be a specific format such as JSON, XML, or CSV, or a general format such as a story,
+ a diagram or steps of instructions.
+ Please refrain from specifying too complex formats that the model may not be able to generate,
+ such as a PDF file or a video.
+
+ Args:
+ caption (Optional[str]): The title or label for the output format paragraph. Default is `Output Format`.
+ captionSerialized (Optional[str]): The serialized version of the caption when using "serializer" syntaxes. Default is `outputFormat`. Default is `"outputFormat"`.
+ captionStyle (Optional[str]): Determines the style of the caption,
+ applicable only for "markup" syntaxes. Default is `header`. Default is `"header"`. Choices: `"header"`, `"bold"`, `"plain"`, `"hidden"`.
+ captionTextTransform (Optional[str]): Specifies text transformation for the caption, applicable only for "markup" syntaxes. Default is `none`. Default is `"none"`. Choices: `"upper"`, `"level"`, `"capitalize"`, `"none"`.
+ captionEnding (Optional[str]): A caption can ends with a colon, a newline or simply nothing.
+ If not specified, it defaults to `colon` for `bold` or `plain` captionStyle, and `none` otherwise. Choices: `"colon"`, `"newline"`, `"colon-newline"`, `"none"`.
+
+ Example:
+ ```xml
+ Respond with a JSON without additional characters or punctuations.
+ ```
+ """
+ return self._tag(
+ tag_name="OutputFormat",
+ caption=caption,
+ captionSerialized=captionSerialized,
+ captionStyle=captionStyle,
+ captionTextTransform=captionTextTransform,
+ captionEnding=captionEnding,
+ **kwargs,
+ )
+
+ def stepwise_instructions(
+ self,
+ caption: Optional[str] = None,
+ captionSerialized: Optional[str] = None,
+ captionStyle: Optional[str] = None,
+ captionTextTransform: Optional[str] = None,
+ captionEnding: Optional[str] = None,
+ **kwargs: Any,
+ ):
+ """StepwiseInstructions that elaborates the task by providing a list of steps or instructions.
+ Each step should be concise and clear, and the list should be easy to follow.
+
+ Args:
+ caption (Optional[str]): The title or label for the stepwise instructions paragraph. Default is `Stepwise Instructions`.
+ captionSerialized (Optional[str]): The serialized version of the caption when using "serializer" syntaxes. Default is `stepwiseInstructions`. Default is `"stepwiseInstructions"`.
+ captionStyle (Optional[str]): Determines the style of the caption,
+ applicable only for "markup" syntaxes. Default is `header`. Default is `"header"`. Choices: `"header"`, `"bold"`, `"plain"`, `"hidden"`.
+ captionTextTransform (Optional[str]): Specifies text transformation for the caption, applicable only for "markup" syntaxes. Default is `none`. Default is `"none"`. Choices: `"upper"`, `"level"`, `"capitalize"`, `"none"`.
+ captionEnding (Optional[str]): A caption can ends with a colon, a newline or simply nothing.
+ If not specified, it defaults to `colon` for `bold` or `plain` captionStyle, and `none` otherwise. Choices: `"colon"`, `"newline"`, `"colon-newline"`, `"none"`.
+
+ Example:
+ ```xml
+
+
+ Interpret and rewrite user's query.
+ Think of a plan to solve the query.
+ Generate a response based on the plan.
+
+
+ ```
+ """
+ return self._tag(
+ tag_name="StepwiseInstructions",
+ caption=caption,
+ captionSerialized=captionSerialized,
+ captionStyle=captionStyle,
+ captionTextTransform=captionTextTransform,
+ captionEnding=captionEnding,
+ **kwargs,
+ )
+
+ def hint(
+ self,
+ caption: Optional[str] = None,
+ captionSerialized: Optional[str] = None,
+ captionStyle: Optional[str] = None,
+ captionTextTransform: Optional[str] = None,
+ captionColon: Optional[bool] = None,
+ **kwargs: Any,
+ ):
+ """Hint can be used anywhere in the prompt where you want to provide a helpful tip or explanation.
+ It is usually a short and concise statement that guides the LLM in the right direction.
+
+ Args:
+ caption (Optional[str]): The title or label for the hint paragraph. Default is `Hint`. Default is `"Hint"`.
+ captionSerialized (Optional[str]): The serialized version of the caption when using "serializer" syntaxes. Default is `hint`. Default is `"hint"`.
+ captionStyle (Optional[str]): Determines the style of the caption,
+ applicable only for "markup" syntaxes. Default is `bold`. Default is `"bold"`. Choices: `"header"`, `"bold"`, `"plain"`, `"hidden"`.
+ captionTextTransform (Optional[str]): Specifies text transformation for the caption, applicable only for "markup" syntaxes. Default is `none`. Default is `"none"`. Choices: `"upper"`, `"level"`, `"capitalize"`, `"none"`.
+ captionColon (Optional[bool]): Indicates whether to append a colon after the caption.
+ By default, this is true for `bold` or `plain` captionStyle, and false otherwise.
+
+ Example:
+ ```xml
+ Alice first purchased 4 apples and then 3 more, so she has 7 apples in total.
+ ```
+ """
+ return self._tag(
+ tag_name="Hint",
+ caption=caption,
+ captionSerialized=captionSerialized,
+ captionStyle=captionStyle,
+ captionTextTransform=captionTextTransform,
+ captionColon=captionColon,
+ **kwargs,
+ )
+
+ def introducer(
+ self,
+ caption: Optional[str] = None,
+ captionSerialized: Optional[str] = None,
+ captionStyle: Optional[str] = None,
+ captionTextTransform: Optional[str] = None,
+ captionEnding: Optional[str] = None,
+ **kwargs: Any,
+ ):
+ """Introducer is a paragraph before a long paragraph (usually a list of examples, steps, or instructions).
+ It serves as a context introducing what is expected to follow.
+
+ Args:
+ caption (Optional[str]): The title or label for the introducer paragraph. Default is `Introducer`. Default is `"Introducer"`.
+ captionSerialized (Optional[str]): The serialized version of the caption when using "serializer" syntaxes. Default is `introducer`. Default is `"introducer"`.
+ captionStyle (Optional[str]): Determines the style of the caption,
+ applicable only for "markup" syntaxes. Default is `hidden`. Default is `"hidden"`. Choices: `"header"`, `"bold"`, `"plain"`, `"hidden"`.
+ captionTextTransform (Optional[str]): Specifies text transformation for the caption, applicable only for "markup" syntaxes. Default is `none`. Default is `"none"`. Choices: `"upper"`, `"level"`, `"capitalize"`, `"none"`.
+ captionEnding (Optional[str]): A caption can ends with a colon, a newline or simply nothing.
+ If not specified, it defaults to `colon` for `bold` or `plain` captionStyle, and `none` otherwise. Choices: `"colon"`, `"newline"`, `"colon-newline"`, `"none"`.
+
+ Example:
+ ```xml
+ Here are some examples.
+ ```
+ """
+ return self._tag(
+ tag_name="Introducer",
+ caption=caption,
+ captionSerialized=captionSerialized,
+ captionStyle=captionStyle,
+ captionTextTransform=captionTextTransform,
+ captionEnding=captionEnding,
+ **kwargs,
+ )
+
+ def example_set(
+ self,
+ caption: Optional[str] = None,
+ captionSerialized: Optional[str] = None,
+ chat: Optional[bool] = None,
+ introducer: Optional[str] = None,
+ captionStyle: Optional[str] = None,
+ captionTextTransform: Optional[str] = None,
+ captionEnding: Optional[str] = None,
+ **kwargs: Any,
+ ):
+ """Example set (``) is a collection of examples that are usually presented in a list.
+ With the example set, you can manage multiple examples under a single title and optionally an introducer,
+ as well as the same `chat` format.
+ You can also choose to use `` purely without example set.
+
+ Args:
+ caption (Optional[str]): The title or label for the example set paragraph. Default is `Examples`. Default is `"Examples"`.
+ captionSerialized (Optional[str]): The serialized version of the caption when using "serializer" syntaxes. Default is `examples`. Default is `"examples"`.
+ chat (Optional[bool]): Indicates whether the examples should be rendered in chat format.
+ By default, it's `true` for "markup" syntaxes and `false` for "serializer" syntaxes.
+ introducer (Optional[str]): An optional introducer text to be displayed before the examples.
+ For example, `Here are some examples:`.
+ captionStyle (Optional[str]): Determines the style of the caption,
+ applicable only for "markup" syntaxes. Default is `header`. Default is `"header"`. Choices: `"header"`, `"bold"`, `"plain"`, `"hidden"`.
+ captionTextTransform (Optional[str]): Specifies text transformation for the caption, applicable only for "markup" syntaxes. Default is `none`. Default is `"none"`. Choices: `"upper"`, `"level"`, `"capitalize"`, `"none"`.
+ captionEnding (Optional[str]): A caption can ends with a colon, a newline or simply nothing.
+ If not specified, it defaults to `colon` for `bold` or `plain` captionStyle, and `none` otherwise. Choices: `"colon"`, `"newline"`, `"colon-newline"`, `"none"`.
+
+ Example:
+ ```xml
+
+
+ What is the capital of France?
+
+
+
+ What is the capital of Germany?
+
+
+
+ ```
+ """
+ return self._tag(
+ tag_name="ExampleSet",
+ caption=caption,
+ captionSerialized=captionSerialized,
+ chat=chat,
+ introducer=introducer,
+ captionStyle=captionStyle,
+ captionTextTransform=captionTextTransform,
+ captionEnding=captionEnding,
+ **kwargs,
+ )
+
+ def example(
+ self,
+ caption: Optional[str] = None,
+ captionSerialized: Optional[str] = None,
+ captionStyle: Optional[str] = None,
+ chat: Optional[bool] = None,
+ captionTextTransform: Optional[str] = None,
+ captionColon: Optional[bool] = None,
+ **kwargs: Any,
+ ):
+ """Example is useful for providing a context, helping the model to understand what kind of inputs and outputs are expected.
+ It can also be used to demonstrate the desired output style, clarifying the structure, tone, or level of detail in the response.
+
+ Args:
+ caption (Optional[str]): The title or label for the example paragraph. Default is `Example`. Default is `"Example"`.
+ captionSerialized (Optional[str]): The serialized version of the caption when using "serializer" syntaxes. Default is `example`. Default is `"example"`.
+ captionStyle (Optional[str]): Determines the style of the caption, applicable only for "markup" syntaxes. Default is `hidden`.
+ Options include `header`, `bold`, `plain`, or `hidden`. Default is `"hidden"`.
+ chat (Optional[bool]): Indicates whether the example should be rendered in chat format.
+ When used in a example set (``), this is inherited from the example set.
+ Otherwise, it defaults to `false` for "serializer" syntaxes and `true` for "markup" syntaxes.
+ captionTextTransform (Optional[str]): Specifies text transformation for the caption, applicable only for "markup" syntaxes.
+ Options are `upper`, `lower`, `capitalize`, or `none`. Default is `none`. Default is `"none"`.
+ captionColon (Optional[bool]): Indicates whether to append a colon after the caption.
+ By default, this is true for `bold` or `plain` captionStyle, and false otherwise.
+
+ Example:
+ ```xml
+
+ What is the capital of France?
+
+
+ ```
+
+ ```xml
+ Summarize the following passage in a single sentence.
+
+ The sun provides energy for life on Earth through processes like photosynthesis.
+
+
+ ```
+ """
+ return self._tag(
+ tag_name="Example",
+ caption=caption,
+ captionSerialized=captionSerialized,
+ captionStyle=captionStyle,
+ chat=chat,
+ captionTextTransform=captionTextTransform,
+ captionColon=captionColon,
+ **kwargs,
+ )
+
+ def example_input(
+ self,
+ caption: Optional[str] = None,
+ captionSerialized: Optional[str] = None,
+ speaker: Optional[str] = None,
+ captionStyle: Optional[str] = None,
+ captionTextTransform: Optional[str] = None,
+ captionColon: Optional[bool] = None,
+ **kwargs: Any,
+ ):
+ """ExampleInput (``) is a paragraph that represents an example input.
+ By default, it's spoken by a human speaker in a chat context, but you can manually specify the speaker.
+
+ Args:
+ caption (Optional[str]): The title or label for the example input paragraph. Default is `Input`. Default is `"Input"`.
+ captionSerialized (Optional[str]): The serialized version of the caption when using "serializer" syntaxes. Default is `input`. Default is `"input"`.
+ speaker (Optional[str]): The speaker for the example input. Default is `human` if chat context is enabled (see ``). Default is `"human"`.
+ captionStyle (Optional[str]): Determines the style of the caption,
+ applicable only for "markup" syntaxes. Default is `hidden` if chat context is enabled. Otherwise, it's `bold`. Default is `"hidden"`. Choices: `"header"`, `"bold"`, `"plain"`, `"hidden"`.
+ captionTextTransform (Optional[str]): Specifies text transformation for the caption, applicable only for "markup" syntaxes. Default is `none`. Default is `"none"`. Choices: `"upper"`, `"level"`, `"capitalize"`, `"none"`.
+ captionColon (Optional[bool]): Indicates whether to append a colon after the caption.
+ By default, this is true for `bold` or `plain` captionStyle, and false otherwise.
+
+ Example:
+ ```xml
+ What is the capital of France?
+ ```
+
+ When used with a template:
+
+ ```xml
+ What is the capital of {{country}}?
+ ```
+ """
+ return self._tag(
+ tag_name="ExampleInput",
+ caption=caption,
+ captionSerialized=captionSerialized,
+ speaker=speaker,
+ captionStyle=captionStyle,
+ captionTextTransform=captionTextTransform,
+ captionColon=captionColon,
+ **kwargs,
+ )
+
+ def example_output(
+ self,
+ caption: Optional[str] = None,
+ captionSerialized: Optional[str] = None,
+ speaker: Optional[str] = None,
+ captionStyle: Optional[str] = None,
+ captionTextTransform: Optional[str] = None,
+ captionColon: Optional[bool] = None,
+ **kwargs: Any,
+ ):
+ """ExampleOutput (`
```
"""
- return self._tag(
+ return self.tag(
tag_name="ExampleSet",
caption=caption,
captionSerialized=captionSerialized,
@@ -388,7 +388,7 @@ def example(
```
"""
- return self._tag(
+ return self.tag(
tag_name="Example",
caption=caption,
captionSerialized=captionSerialized,
@@ -433,7 +433,7 @@ def example_input(
What is the capital of {{country}}?
```
"""
- return self._tag(
+ return self.tag(
tag_name="ExampleInput",
caption=caption,
captionSerialized=captionSerialized,
@@ -478,7 +478,7 @@ def example_output(
The capital of {{country}} is {{capital}}.
```
"""
- return self._tag(
+ return self.tag(
tag_name="ExampleOutput",
caption=caption,
captionSerialized=captionSerialized,
@@ -518,7 +518,7 @@ def question(
What is the capital of France?
```
"""
- return self._tag(
+ return self.tag(
tag_name="Question",
questionCaption=questionCaption,
answerCaption=answerCaption,
@@ -541,7 +541,7 @@ def system_message(
Answer concisely.
```
"""
- return self._tag(
+ return self.tag(
tag_name="SystemMessage",
**kwargs,
)
@@ -558,7 +558,7 @@ def human_message(
What is the capital of France?
```
"""
- return self._tag(
+ return self.tag(
tag_name="HumanMessage",
**kwargs,
)
@@ -575,7 +575,7 @@ def ai_message(
Paris
```
"""
- return self._tag(
+ return self.tag(
tag_name="AiMessage",
**kwargs,
)
@@ -595,7 +595,7 @@ def message_content(
```
"""
- return self._tag(
+ return self.tag(
tag_name="MessageContent",
content=content,
**kwargs,
@@ -620,7 +620,7 @@ def conversation(
```
"""
- return self._tag(
+ return self.tag(
tag_name="Conversation",
messages=messages,
selectedMessages=selectedMessages,
@@ -670,7 +670,7 @@ def table(
") == [{"speaker": "human", "content": "Hello, World!"}]
+
+
+def test_prompt():
+ with Prompt() as p:
+ with p.task(caption="My Task"):
+ p.text("This is a task description.")
+ with p.paragraph():
+ with p.header():
+ p.text("Subheading")
+ p.text("This is a paragraph in the document.")
+
+ xml_output = p.dump_xml()
+ print(xml_output)
+ assert xml_output == (
+ """This is a task description.
+ SubheadingThis is a paragraph in the document.
+"""
+ )
+ prompt_output = p.render()
+ assert prompt_output == [
+ {
+ "speaker": "human",
+ "content": "# My Task\n\nThis is a task description.\n\n## Subheading\n\nThis is a paragraph in the document.",
+ }
+ ]