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
1 change: 0 additions & 1 deletion invariant/analyzer/runtime/function_cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,5 +74,4 @@ async def call_either_way(
if inspect.iscoroutinefunction(func):
return await func(*args, **kwargs)
else:
print([func, args, kwargs], flush=True)
return func(*args, **kwargs) # type: ignore
17 changes: 14 additions & 3 deletions invariant/analyzer/runtime/nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,9 +135,11 @@ class Contents(RootModel[list[Chunk]]):
def __contains__(self, item: object) -> bool:
for chunk in self.root:
if isinstance(chunk, TextChunk):
return item in chunk.text
if item in chunk.text:
return True
elif isinstance(chunk, Image):
return item in chunk.image_url["url"]
if item in chunk.image_url["url"]:
return True
return False

def __invariant_attribute__(self, name: str):
Expand Down Expand Up @@ -324,7 +326,16 @@ class ToolParameter(BaseModel):
enum: Optional[List[str]] = None

def __invariant_attribute__(self, name: str):
if name in ["type", "name", "description", "required", "properties", "additionalProperties", "items", "enum"]:
if name in [
"type",
"name",
"description",
"required",
"properties",
"additionalProperties",
"items",
"enum",
]:
return getattr(self, name)
raise InvariantAttributeError(
f"Attribute {name} not found in ToolParameter. Available attributes are: type, name, description, required, properties, additionalProperties, items, enum"
Expand Down
55 changes: 54 additions & 1 deletion invariant/tests/analyzer/test_chunked.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import unittest

from invariant.analyzer import Monitor
from invariant.analyzer import Monitor, Policy
from invariant.analyzer.traces import chunked


Expand Down Expand Up @@ -86,3 +86,56 @@ def test_simple_but_multiple_text_chunks(self):
input.extend(pending_input)

assert len(errors) == 0, "Expected no errors, but got: " + str(errors)

def test_contains_in_text_chunk(self):
# tests that 'abc' in message.content works both when 'abc' is in chunk 0 or chunk 1
policy = Policy.from_string(
"""

raise "pattern found" if:
(msg: Message)
msg.role == "assistant"
"abc" in msg.content
"""
)

# in second chunk
input = [
{
"role": "assistant",
"content": [
{"type": "text", "text": "Hello, "},
{"type": "text", "text": "aa abc aa"},
],
}
]

result = policy.analyze(input, [])
assert len(result.errors) == 1, "Expected one error, but got: " + str(result)

# in no chunk
input = [
{
"role": "assistant",
"content": [
{"type": "text", "text": "Hello, "},
{"type": "text", "text": "adc"},
],
}
]

result = policy.analyze(input, [])
assert len(result.errors) == 0, "Expected no errors, but got: " + str(result)

# in first chunk
input = [
{
"role": "assistant",
"content": [
{"type": "text", "text": "aa abc aa"},
{"type": "text", "text": "Hello, "},
],
}
]
result = policy.analyze(input, [])
assert len(result.errors) == 1, "Expected one error, but got: " + str(result)
2 changes: 1 addition & 1 deletion uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.