Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 49 additions & 3 deletions docs/language/standalone.md
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,41 @@ In expression mode, the `z` variable is automatically available for constructing
- **description**: Tool description (optional but recommended)
- **lang**: Schema language, either "json" or "expr" (optional, auto-detected based on content)

### Template Expressions in Attributes

Both schemas and tools support template expressions in their attributes:

```xml
<let name="toolName">calculate</let>
<let name="toolDesc">Perform mathematical calculations</let>
<let name="schemaLang">json</let>

<tool-definition name="{{toolName}}" description="{{toolDesc}}" lang="{{schemaLang}}">
{
"type": "object",
"properties": {
"operation": { "type": "string" }
}
}
</tool-definition>
```

Similarly for output schemas:

```xml
<let name="schemaJson">
{
"type": "object",
"properties": {
"result": { "type": "string" }
}
}
</let>
<output-schema lang="json">
{{ schemaJson }}
</output-schema>
```

You can define multiple tools in a single document.

## Runtime Parameters
Expand All @@ -481,12 +516,23 @@ Runtime parameters configure the language model's behavior during execution. The

```xml
<runtime temperature="0.7"
maxOutputTokens="1000"
max-output-tokens="1000"
model="gpt-5"
topP="0.9" />
top-p="0.9" />
```

All attributes are passed as runtime parameters. Common parameters include:
All attributes are passed as runtime parameters with automatic type conversion:

### Key Conversion
- Keys are converted from kebab-case to camelCase
- Examples: `max-tokens` → `maxTokens`, `top-p` → `topP`, `frequency-penalty` → `frequencyPenalty`

### Value Conversion
- **Boolean strings**: `"true"` and `"false"` → `true` and `false`
- **Number strings**: `"1000"`, `"0.7"` → `1000`, `0.7`
- **JSON strings**: `'["END", "STOP"]'`, `'{"key": "value"}'` → parsed JSON objects/arrays

### Common Parameters

- **temperature**: Controls randomness (0-2, typically 0.3-0.7 for balanced output)
- **maxOutputTokens**: Maximum response length in tokens
Expand Down
75 changes: 69 additions & 6 deletions packages/poml/file.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -719,7 +719,11 @@ export class PomlFile {
};

private handleSchema = (element: XMLElement, context?: { [key: string]: any }): Schema | undefined => {
let lang: 'json' | 'expr' | undefined = xmlAttribute(element, 'lang')?.value as any;
const langAttr = xmlAttribute(element, 'lang');
let lang: 'json' | 'expr' | undefined = langAttr?.value
? this.handleTextAsString(langAttr.value, context || {},
this.xmlAttributeValueRange(langAttr)) as 'json' | 'expr'
: undefined;
const text = xmlElementText(element).trim();

// Get the range for the text content (if available)
Expand Down Expand Up @@ -808,16 +812,23 @@ export class PomlFile {
return false;
}

const name = xmlAttribute(element, 'name')?.value;
if (!name) {
const nameAttr = xmlAttribute(element, 'name');
if (!nameAttr?.value) {
this.reportError(
'name attribute is required for tool definition',
this.xmlElementRange(element)
);
return true;
}

const description = xmlAttribute(element, 'description')?.value;

// Process template expressions in name attribute
const name = this.handleTextAsString(nameAttr.value, context || {}, this.xmlAttributeValueRange(nameAttr));

const descriptionAttr = xmlAttribute(element, 'description');
// Process template expressions in description attribute if present
const description = descriptionAttr?.value
? this.handleTextAsString(descriptionAttr.value, context || {}, this.xmlAttributeValueRange(descriptionAttr))
: undefined;
const inputSchema = this.handleSchema(element, context);
if (inputSchema) {
if (!this.toolsSchema) {
Expand Down Expand Up @@ -846,13 +857,52 @@ export class PomlFile {
const runtimeParams: any = {};
for (const attribute of element.attributes) {
if (attribute.key && attribute.value) {
runtimeParams[attribute.key] = attribute.value;
// Process template expressions and convert to string
const stringValue = this.handleTextAsString(attribute.value, context || {}, this.xmlAttributeValueRange(attribute));

// Convert key to camelCase (kebab-case to camelCase)
const camelKey = hyphenToCamelCase(attribute.key);
// Convert value (auto-convert booleans, numbers, JSON)
const convertedValue = this.convertRuntimeValue(stringValue);
runtimeParams[camelKey] = convertedValue;
}
}
this.runtimeParameters = runtimeParams;
return true;
};

private convertRuntimeValue = (value: string): any => {
// Convert boolean-like values
if (value === 'true') {
return true;
}
if (value === 'false') {
return false;
}

// Convert number-like values
if (/^-?\d*\.?\d+$/.test(value)) {
const num = parseFloat(value);
if (!isNaN(num)) {
return num;
}
}

// Convert JSON-like values (arrays and objects)
if ((value.startsWith('[') && value.endsWith(']')) ||
(value.startsWith('{') && value.endsWith('}'))) {
try {
return JSON.parse(value);
} catch {
// If JSON parsing fails, return as string
return value;
}
}

// Return as string for everything else
return value;
};

private handleMeta = (element: XMLElement, context?: { [key: string]: any }): boolean => {
if (element.name?.toLowerCase() !== 'meta') {
return false;
Expand Down Expand Up @@ -963,6 +1013,19 @@ export class PomlFile {
return results;
};

private handleTextAsString = (text: string, context: { [key: string]: any }, position?: Range): string => {
const results = this.handleText(text, context, position);
if (results.length === 1) {
if (typeof results[0] === 'string') {
return results[0];
} else {
return results[0].toString();
}
} else {
return JSON.stringify(results);
}
};

private evaluateExpression(
expression: string,
context: { [key: string]: any },
Expand Down
Loading
Loading