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
2 changes: 1 addition & 1 deletion cragents/_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ def build_grammar(generation_sequence: Sequence[GenerationSequenceElement]) -> s
tool_names = [f'"{tool_name}"' for tool_name in element.tool_names]
custom_defs.append(f"FUNCTION_NAME: ({' | '.join(tool_names)})")

grammar = "\n".join([start_def] + custom_defs + default_defs)
grammar = "\n".join([start_def.strip()] + custom_defs + default_defs)
return grammar


Expand Down
10 changes: 5 additions & 5 deletions tests/test_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,29 +146,29 @@ async def test_mixed_output_type():
async def test_set_guide_requires_openai_model():
agent = CRAgent(TestModel())
with pytest.raises(RuntimeError, match="OpenAIChatModel required"):
await agent.set_guide([Anchor("hi ")])
await agent.set_guide([Anchor("hi")])


async def test_set_guide_creates_model_settings_when_none():
agent = CRAgent(model)
assert agent.model_settings is None
await agent.set_guide([Anchor("hi ")])
await agent.set_guide([Anchor("hi")])
assert agent.model_settings is not None
assert "extra_body" in agent.model_settings


async def test_set_guide_preserves_existing_model_settings():
agent = CRAgent(model, model_settings=OpenAIChatModelSettings(temperature=0.5))
await agent.set_guide([Anchor("hi ")])
await agent.set_guide([Anchor("hi")])
assert agent.model_settings["temperature"] == 0.5
assert "extra_body" in agent.model_settings


async def test_set_guide_overwrites_on_second_call():
agent = CRAgent(model)
await agent.set_guide([Anchor("first ")])
await agent.set_guide([Anchor("first")])
first_grammar = agent.model_settings["extra_body"]["structured_outputs"]["grammar"]
await agent.set_guide([Anchor("second ")])
await agent.set_guide([Anchor("second")])
second_grammar = agent.model_settings["extra_body"]["structured_outputs"]["grammar"]
assert first_grammar != second_grammar
assert "second" in second_grammar
Expand Down
81 changes: 54 additions & 27 deletions tests/test_grammar.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,125 +9,152 @@
def test_grammar_single_anchor():
grammar = build_grammar([Anchor("hello ")])
assert grammar == snapshot("""\
start: "hello " \nFREE: /[\\S\\s]*/
start: "hello "
FREE: /[\\S\\s]*/
NL: /\\n/""")


def test_grammar_multiple_anchors():
grammar = build_grammar([Anchor("foo "), Anchor("bar ")])
assert grammar == snapshot("""\
start: "foo " "bar " \nFREE: /[\\S\\s]*/
NL: /\\n/""")
start: "foo " "bar "
FREE: /[\\S\\s]*/
NL: /\\n/\
""")


def test_grammar_single_free():
grammar = build_grammar([Free()])
assert grammar == snapshot("""\
start: FREE \nFREE: /[\\S\\s]*/
NL: /\\n/""")
start: FREE
FREE: /[\\S\\s]*/
NL: /\\n/\
""")


def test_grammar_anchor_then_free():
grammar = build_grammar([Anchor("Result: "), Free()])
assert grammar == snapshot("""\
start: "Result: " FREE \nFREE: /[\\S\\s]*/
NL: /\\n/""")
start: "Result: " FREE
FREE: /[\\S\\s]*/
NL: /\\n/\
""")


def test_grammar_single_constrain():
grammar = build_grammar([Constrain(max_newlines=2, max_char_captures=3)])
assert grammar == snapshot("""\
start: block_1 \nblock_1: p_1{1,2}
start: block_1
block_1: p_1{1,2}
p_1: s_1{1,3} NL NL
s_1[lazy]: /[^\\.\\n]+/ ( "." )
FREE: /[\\S\\s]*/
NL: /\\n/""")
NL: /\\n/\
""")


def test_grammar_constrain_custom_chars():
grammar = build_grammar([Constrain(max_newlines=1, max_char_captures=2, chars_to_capture="!?")])
assert grammar == snapshot("""\
start: block_1 \nblock_1: p_1{1,1}
start: block_1
block_1: p_1{1,1}
p_1: s_1{1,2} NL NL
s_1[lazy]: /[^!\\?\\n]+/ ( "!" | "?" )
FREE: /[\\S\\s]*/
NL: /\\n/""")
NL: /\\n/\
""")


def test_grammar_constrain_regex_special_chars():
# "." is the default and must be escaped to "\\." in the character class
grammar = build_grammar([Constrain(max_newlines=1, max_char_captures=1, chars_to_capture=".")])
assert grammar == snapshot("""\
start: block_1 \nblock_1: p_1{1,1}
start: block_1
block_1: p_1{1,1}
p_1: s_1{1,1} NL NL
s_1[lazy]: /[^\\.\\n]+/ ( "." )
FREE: /[\\S\\s]*/
NL: /\\n/""")
NL: /\\n/\
""")


def test_grammar_multiple_constrains_uid_increments():
grammar = build_grammar([Constrain(1, 1), Constrain(2, 3)])
assert grammar == snapshot("""\
start: block_1 block_2 \nblock_1: p_1{1,1}
start: block_1 block_2
block_1: p_1{1,1}
p_1: s_1{1,1} NL NL
s_1[lazy]: /[^\\.\\n]+/ ( "." )
block_2: p_2{1,2}
p_2: s_2{1,3} NL NL
s_2[lazy]: /[^\\.\\n]+/ ( "." )
FREE: /[\\S\\s]*/
NL: /\\n/""")
NL: /\\n/\
""")


def test_grammar_think_empty_sequence():
grammar = build_grammar([Think([])])
assert grammar == snapshot("""\
start: <think> NL </think> \nFREE: /[\\S\\s]*/
NL: /\\n/""")
start: <think> NL </think>
FREE: /[\\S\\s]*/
NL: /\\n/\
""")


def test_grammar_think_with_anchor():
grammar = build_grammar([Think([Anchor("step one ")])])
assert grammar == snapshot("""\
start: <think> NL "step one " </think> \nFREE: /[\\S\\s]*/
NL: /\\n/""")
start: <think> NL "step one " </think>
FREE: /[\\S\\s]*/
NL: /\\n/\
""")


def test_grammar_think_with_constrain():
grammar = build_grammar([Think([Constrain(1, 2)])])
assert grammar == snapshot("""\
start: <think> NL block_1 </think> \nblock_1: p_1{1,1}
start: <think> NL block_1 </think>
block_1: p_1{1,1}
p_1: s_1{1,2} NL NL
s_1[lazy]: /[^\\.\\n]+/ ( "." )
FREE: /[\\S\\s]*/
NL: /\\n/""")
NL: /\\n/\
""")


def test_grammar_think_with_free():
grammar = build_grammar([Think([Free()])])
assert grammar == snapshot("""\
start: <think> NL FREE </think> \nFREE: /[\\S\\s]*/
NL: /\\n/""")
start: <think> NL FREE </think>
FREE: /[\\S\\s]*/
NL: /\\n/\
""")


def test_grammar_think_uid_shared_with_outer():
# Outer Constrain gets uid=1, inner Think's Constrain gets uid=2
grammar = build_grammar([Constrain(1, 1), Think([Constrain(2, 3)])])
assert grammar == snapshot("""\
start: block_1 <think> NL block_2 </think> \nblock_1: p_1{1,1}
start: block_1 <think> NL block_2 </think>
block_1: p_1{1,1}
p_1: s_1{1,1} NL NL
s_1[lazy]: /[^\\.\\n]+/ ( "." )
block_2: p_2{1,2}
p_2: s_2{1,3} NL NL
s_2[lazy]: /[^\\.\\n]+/ ( "." )
FREE: /[\\S\\s]*/
NL: /\\n/""")
NL: /\\n/\
""")


def test_grammar_think_custom_tokens():
grammar = build_grammar([Think([], start_token="<|think|>", stop_token="<|/think|>")])
assert grammar == snapshot("""\
start: <|think|> NL <|/think|> \nFREE: /[\\S\\s]*/
NL: /\\n/""")
start: <|think|> NL <|/think|>
FREE: /[\\S\\s]*/
NL: /\\n/\
""")


def test_grammar_use_tools_explicit_schema():
Expand Down