Skip to content
Open
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
30 changes: 30 additions & 0 deletions agentschema-emitter/lib/model/agent.tsp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,24 @@ namespace AgentSchema;

alias AgentKind = "prompt" | "hosted" | "workflow";

/**
* A single safety or governance policy attached to an agent.
* The `type` field discriminates the policy kind; the remaining fields are
* interpreted based on `type`. For `rai_policy`, `raiPolicyName` associates the
* agent with a Responsible AI policy that governs content filtering and safety
* guardrails for prompts and completions.
*/
model Policy {
@doc("The policy type. 'rai_policy' attaches a Responsible AI (content safety) guardrail.")
type: "rai_policy";

@doc("For 'rai_policy', the full ARM resource ID of the Responsible AI policy on the Cognitive Services account (e.g., '/subscriptions/<sub>/resourceGroups/<rg>/providers/Microsoft.CognitiveServices/accounts/<account>/raiPolicies/<policyName>').")
@sample(#{
raiPolicyName: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/my-rg/providers/Microsoft.CognitiveServices/accounts/my-account/raiPolicies/Microsoft.DefaultV2",
})
raiPolicyName?: string;
}

/**
* The following is a specification for defining AI agents with structured metadata, inputs, outputs, tools, and templates.
* It provides a way to create reusable and composable AI agents that can be executed with specific configurations.
Expand Down Expand Up @@ -95,6 +113,18 @@ model AgentDefinition {
},
})
outputSchema?: PropertySchema;

// Policies
@doc("Safety and governance policies for the agent, such as Responsible AI content-safety configuration.")
@sample(#{
policies: #[
#{
type: "rai_policy",
raiPolicyName: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/my-rg/providers/Microsoft.CognitiveServices/accounts/my-account/raiPolicies/Microsoft.DefaultV2",
}
],
})
policies?: Policy[];
}

/**
Expand Down
26 changes: 26 additions & 0 deletions agentschema-emitter/src/template-engine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,32 @@ export class TemplateEngine {
.replace(/^_/, '');
});

// rustFieldIdent: snake_case the name, then escape Rust reserved keywords
// so the generated identifier is valid (e.g. "type" -> "r#type"). The wire
// key (serde/JSON name) is rendered separately from the original property
// name, so escaping here only affects the Rust identifier.
this.env.addFilter('rustFieldIdent', (str: string): string => {
if (!str) return str;
const snake = str
.replace(/([A-Z])/g, '_$1')
.toLowerCase()
.replace(/^_/, '');
// Strict and reserved Rust keywords that are valid raw identifiers.
const rawKeywords = new Set([
'as', 'break', 'const', 'continue', 'dyn', 'else', 'enum', 'extern',
'false', 'fn', 'for', 'if', 'impl', 'in', 'let', 'loop', 'match', 'mod',
'move', 'mut', 'pub', 'ref', 'return', 'static', 'struct', 'trait',
'true', 'type', 'unsafe', 'use', 'where', 'while', 'async', 'await',
'abstract', 'become', 'box', 'do', 'final', 'macro', 'override', 'priv',
'typeof', 'unsized', 'virtual', 'yield', 'try', 'union',
]);
// Keywords that cannot be raw identifiers; escape with a trailing underscore.
const nonRawKeywords = new Set(['crate', 'self', 'Self', 'super']);
if (nonRawKeywords.has(snake)) return `${snake}_`;
if (rawKeywords.has(snake)) return `r#${snake}`;
return snake;
});

// kebab-case: "helloWorld" -> "hello-world"
this.env.addFilter('kebabCase', (str: string): string => {
if (!str) return str;
Expand Down
11 changes: 6 additions & 5 deletions agentschema-emitter/src/templates/rust/_macros.njk
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ Default::default()
Optional non-polymorphic non-scalar fields are wrapped in Some().
#}
{% macro loadProperty(prop, varName, typeMapper, polymorphicTypeNames) -%}
{%- set propNameRust = prop.name | snakeCase -%}
{%- set propNameRust = prop.name | rustFieldIdent -%}
{%- if prop.isCollection and not prop.isDict and not prop.isScalar %}
if let Some(val) = {{ varName }}.get("{{ prop.name }}") {
result.{{ propNameRust }} = val.clone();
Expand Down Expand Up @@ -212,7 +212,7 @@ Default::default()
Render save code for a single property.
#}
{% macro saveProperty(prop, polymorphicTypeNames) -%}
{%- set propNameRust = prop.name | snakeCase -%}
{%- set propNameRust = prop.name | rustFieldIdent -%}
{%- if prop.isCollection and not prop.isDict and not prop.isScalar %}
if !self.{{ propNameRust }}.is_null() {
result.insert("{{ prop.name }}".to_string(), self.{{ propNameRust }}.clone());
Expand Down Expand Up @@ -294,12 +294,13 @@ Default::default()
#}
{% macro typedAccessor(prop) -%}
{%- set propNameRust = prop.name | snakeCase -%}
{%- set propNameField = prop.name | rustFieldIdent -%}
{%- if prop.isCollection and not prop.isScalar and not prop.isDict %}
/// Returns typed `Vec<{{ prop.typeName.name }}>` by parsing the stored JSON value.
/// Handles both array format `[{...}]` and dict format `{"name": {...}}`.
/// Returns `None` if the field is null or cannot be parsed.
pub fn as_{{ propNameRust }}(&self) -> Option<Vec<{{ prop.typeName.name }}>> {
match &self.{{ propNameRust }} {
match &self.{{ propNameField }} {
serde_json::Value::Array(arr) => {
Some(arr.iter().map({{ prop.typeName.name }}::load_from_value).collect())
}
Expand Down Expand Up @@ -327,7 +328,7 @@ Default::default()
/// Returns typed reference to the map if the field is an object.
/// Returns `None` if the field is null or not an object.
pub fn as_{{ propNameRust }}_dict(&self) -> Option<&serde_json::Map<String, serde_json::Value>> {
self.{{ propNameRust }}.as_object()
self.{{ propNameField }}.as_object()
}
{%- endif -%}
{%- endmacro %}
Expand All @@ -338,7 +339,7 @@ Default::default()
Used for struct initialization syntax to avoid clippy::field_reassign_with_default.
#}
{% macro fieldInitializer(prop, varName, typeMapper, polymorphicTypeNames) -%}
{%- set propNameRust = prop.name | snakeCase -%}
{%- set propNameRust = prop.name | rustFieldIdent -%}
{%- if prop.isCollection and not prop.isDict and not prop.isScalar %}
{{ varName }}.get("{{ prop.name }}").cloned().unwrap_or(serde_json::Value::Null)
{%- elif prop.isCollection and not prop.isDict and prop.isScalar %}
Expand Down
4 changes: 2 additions & 2 deletions agentschema-emitter/src/templates/rust/file.rs.njk
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ pub struct {{ class.node.typeName.name }} {
{%- if prop.description %}
/// {{ prop.description | replace("\r\n", " ") | replace("\n", " ") | trim }}
{%- endif %}
pub {{ prop.name | snakeCase }}: {{ rust.rustType(prop, typeMapper, polymorphicTypeNames) | trim }},
pub {{ prop.name | rustFieldIdent }}: {{ rust.rustType(prop, typeMapper, polymorphicTypeNames) | trim }},
{%- endfor %}
}

Expand Down Expand Up @@ -81,7 +81,7 @@ impl {{ class.node.typeName.name }} {
{%- endif %}
Self {
{%- for prop in class.node.properties %}
{{ prop.name | snakeCase }}: {{ rust.fieldInitializer(prop, "value", typeMapper, polymorphicTypeNames) | trim }},
{{ prop.name | rustFieldIdent }}: {{ rust.fieldInitializer(prop, "value", typeMapper, polymorphicTypeNames) | trim }},
{%- endfor %}
}
}
Expand Down
16 changes: 16 additions & 0 deletions docs/src/content/docs/reference/AgentDefinition.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ classDiagram
+dictionary metadata
+PropertySchema inputSchema
+PropertySchema outputSchema
+Policy[] policies
}
class PromptAgent {
+string kind
Expand All @@ -55,6 +56,11 @@ classDiagram
+CodeConfiguration codeConfiguration
}
AgentDefinition <|-- ContainerAgent
class Policy {
+string type
+string raiPolicyName
}
AgentDefinition *-- Policy
```

## Yaml Example
Expand Down Expand Up @@ -87,6 +93,9 @@ outputSchema:
answer:
kind: string
description: The answer to the user's question.
policies:
- type: rai_policy
raiPolicyName: /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/my-rg/providers/Microsoft.CognitiveServices/accounts/my-account/raiPolicies/Microsoft.DefaultV2
```

## Properties
Expand All @@ -100,6 +109,7 @@ outputSchema:
| metadata | dictionary | Additional metadata including authors, tags, and other arbitrary properties |
| inputSchema | [PropertySchema](../propertyschema/) | Input parameters that participate in template rendering |
| outputSchema | [PropertySchema](../propertyschema/) | Expected output format and structure from the agent |
| policies | [Policy[]](../policy/) | Safety and governance policies for the agent, such as Responsible AI content-safety configuration. |

## Child Types

Expand All @@ -108,3 +118,9 @@ The following types extend `AgentDefinition`:
- [PromptAgent](../promptagent/)
- [Workflow](../workflow/)
- [ContainerAgent](../containeragent/)

## Composed Types

The following types are composed within `AgentDefinition`:

- [Policy](../policy/)
1 change: 1 addition & 0 deletions docs/src/content/docs/reference/AgentManifest.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ classDiagram
+dictionary metadata
+PropertySchema inputSchema
+PropertySchema outputSchema
+Policy[] policies
}
AgentManifest *-- AgentDefinition
class Resource {
Expand Down
1 change: 1 addition & 0 deletions docs/src/content/docs/reference/ContainerAgent.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ classDiagram
+dictionary metadata
+PropertySchema inputSchema
+PropertySchema outputSchema
+Policy[] policies
}
AgentDefinition <|-- ContainerAgent
class ContainerAgent {
Expand Down
43 changes: 43 additions & 0 deletions docs/src/content/docs/reference/Policy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
---
title: "Policy"
description: "Documentation for the Policy type."
slug: "reference/policy"
---

A single safety or governance policy attached to an agent.
The `type` field discriminates the policy kind; the remaining fields are
interpreted based on `type`. For `rai_policy`, `raiPolicyName` associates the
agent with a Responsible AI policy that governs content filtering and safety
guardrails for prompts and completions.

## Class Diagram

```mermaid
---
title: Policy
config:
look: handDrawn
theme: colorful
class:
hideEmptyMembersBox: true
---
classDiagram
class Policy {

+string type
+string raiPolicyName
}
```

## Yaml Example

```yaml
raiPolicyName: /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/my-rg/providers/Microsoft.CognitiveServices/accounts/my-account/raiPolicies/Microsoft.DefaultV2
```

## Properties

| Name | Type | Description |
| ---- | ---- | ----------- |
| type | string | The policy type. &#39;rai_policy&#39; attaches a Responsible AI (content safety) guardrail. |
| raiPolicyName | string | For &#39;rai_policy&#39;, the full ARM resource ID of the Responsible AI policy on the Cognitive Services account (e.g., &#39;/subscriptions/&lt;sub&gt;/resourceGroups/&lt;rg&gt;/providers/Microsoft.CognitiveServices/accounts/&lt;account&gt;/raiPolicies/&lt;policyName&gt;&#39;). |
1 change: 1 addition & 0 deletions docs/src/content/docs/reference/PromptAgent.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ classDiagram
+dictionary metadata
+PropertySchema inputSchema
+PropertySchema outputSchema
+Policy[] policies
}
AgentDefinition <|-- PromptAgent
class PromptAgent {
Expand Down
7 changes: 7 additions & 0 deletions docs/src/content/docs/reference/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ config:
hideEmptyMembersBox: true
---
classDiagram
class Policy {

+string type
+string raiPolicyName
}
class AgentDefinition {
<<abstract>>
+string kind
Expand All @@ -28,6 +33,7 @@ classDiagram
+dictionary metadata
+PropertySchema inputSchema
+PropertySchema outputSchema
+Policy[] policies
}
class Connection {
<<abstract>>
Expand Down Expand Up @@ -311,6 +317,7 @@ classDiagram
Resource <|-- ToolResource
AgentDefinition *-- PropertySchema
AgentDefinition *-- PropertySchema
AgentDefinition *-- Policy
Model *-- Connection
Model *-- ModelOptions
Tool *-- Binding
Expand Down
1 change: 1 addition & 0 deletions docs/src/content/docs/reference/Workflow.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ classDiagram
+dictionary metadata
+PropertySchema inputSchema
+PropertySchema outputSchema
+Policy[] policies
}
AgentDefinition <|-- Workflow
class Workflow {
Expand Down
Loading
Loading