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
11 changes: 10 additions & 1 deletion ballerina-interpreter/parser.bal
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,18 @@ import ballerina/os;
function parseAfm(string content) returns AFMRecord|error {
string resolvedContent = check resolveVariables(content);

string[] resolvedLines = splitLines(resolvedContent);
int firstNonBlank = 0;
while firstNonBlank < resolvedLines.length() && resolvedLines[firstNonBlank].trim() == "" {
firstNonBlank += 1;
}
if firstNonBlank > 0 {
resolvedContent = string:'join("\n", ...resolvedLines.slice(firstNonBlank));
resolvedLines = splitLines(resolvedContent);
}

AgentMetadata? metadata = ();
string body;
string[] resolvedLines = splitLines(resolvedContent);
if resolvedLines.length() > 0 && resolvedLines[0].trim() == FRONTMATTER_DELIMITER {
map<json> frontmatterMap;
[frontmatterMap, body] = check extractFrontMatter(resolvedContent);
Expand Down
23 changes: 23 additions & 0 deletions ballerina-interpreter/tests/agent_test.bal
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,29 @@ function testGetModelUnsupportedProvider() returns error? {
test:assertTrue((<error>result).message().includes("not yet supported"));
}

@test:Config
function testParseAfmIgnoresLeadingBlankLines() returns error? {
string content = "\n\n---\n" +
"model:\n" +
" provider: \"ollama\"\n" +
" name: \"llama3\"\n" +
"---\n\n" +
"# Role\n" +
"You are a helpful assistant.\n\n" +
"# Instructions\n" +
"Be concise.\n";

AFMRecord afm = check parseAfm(content);

AgentMetadata? metadata = afm.metadata;
test:assertTrue(metadata is AgentMetadata);
Model? model = (<AgentMetadata>metadata).model;
test:assertTrue(model is Model);
test:assertEquals((<Model>model).provider, "ollama");
test:assertEquals((<Model>model).name, "llama3");
test:assertEquals(afm.role, "You are a helpful assistant.");
}

function extractJsonFromCodeBlockDataProvider() returns [string, string, string][] {
return [
["json marker", "Here is the result:\n```json\n{\"name\": \"Alice\"}\n```\nDone.", "{\"name\": \"Alice\"}"],
Expand Down
10 changes: 7 additions & 3 deletions python-interpreter/packages/afm-core/src/afm/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,19 +36,23 @@ def extract_raw_frontmatter(content: str) -> tuple[dict | None, str]:
"""
lines = content.splitlines()

if not lines or lines[0].strip() != FRONTMATTER_DELIMITER:
start = 0
while start < len(lines) and not lines[start].strip():
start += 1

if start >= len(lines) or lines[start].strip() != FRONTMATTER_DELIMITER:
return None, content

end_index = None
for i in range(1, len(lines)):
for i in range(start + 1, len(lines)):
if lines[i].strip() == FRONTMATTER_DELIMITER:
end_index = i
break

if end_index is None:
raise ValueError("Unclosed frontmatter - missing closing '---'")

yaml_content = "\n".join(lines[1:end_index])
yaml_content = "\n".join(lines[start + 1 : end_index])
body = "\n".join(lines[end_index + 1 :])

if not yaml_content.strip():
Expand Down
25 changes: 25 additions & 0 deletions python-interpreter/packages/afm-core/tests/test_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,31 @@ def test_parse_no_frontmatter(self, sample_no_frontmatter_path: Path) -> None:
assert result.role == "This is the role without frontmatter."
assert result.instructions == "These are instructions without frontmatter."

def test_parse_frontmatter_with_leading_blank_lines(self) -> None:
content = """

---
spec_version: "0.3.0"
model:
provider: "ollama"
name: "llama3"
---

# Role
The role.

# Instructions
The instructions.
"""
result = parse_afm(content)

assert result.metadata.spec_version == "0.3.0"
assert result.metadata.model is not None
assert result.metadata.model.provider == "ollama"
assert result.metadata.model.name == "llama3"
assert result.role == "The role."
assert result.instructions == "The instructions."

def test_parse_unclosed_frontmatter(self) -> None:
content = """---
spec_version: "0.3.0"
Expand Down