llm-behave 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. llm_behave-0.1.0/.github/workflows/publish.yml +28 -0
  2. llm_behave-0.1.0/.gitignore +54 -0
  3. llm_behave-0.1.0/CHANGELOG.md +30 -0
  4. llm_behave-0.1.0/LICENSE +21 -0
  5. llm_behave-0.1.0/PKG-INFO +307 -0
  6. llm_behave-0.1.0/README.md +260 -0
  7. llm_behave-0.1.0/llm_assert/__init__.py +17 -0
  8. llm_behave-0.1.0/llm_assert/core/__init__.py +0 -0
  9. llm_behave-0.1.0/llm_assert/core/assertions.py +223 -0
  10. llm_behave-0.1.0/llm_assert/core/conversation.py +156 -0
  11. llm_behave-0.1.0/llm_assert/core/drift.py +143 -0
  12. llm_behave-0.1.0/llm_assert/engines/__init__.py +0 -0
  13. llm_behave-0.1.0/llm_assert/engines/contradiction.py +69 -0
  14. llm_behave-0.1.0/llm_assert/engines/semantic.py +100 -0
  15. llm_behave-0.1.0/llm_assert/engines/tone.py +149 -0
  16. llm_behave-0.1.0/llm_assert/plugin/__init__.py +0 -0
  17. llm_behave-0.1.0/llm_assert/plugin/fixtures.py +5 -0
  18. llm_behave-0.1.0/llm_assert/plugin/pytest_plugin.py +48 -0
  19. llm_behave-0.1.0/llm_assert/providers/__init__.py +0 -0
  20. llm_behave-0.1.0/llm_assert/providers/anthropic_adapter.py +65 -0
  21. llm_behave-0.1.0/llm_assert/providers/base.py +79 -0
  22. llm_behave-0.1.0/llm_assert/providers/ollama_adapter.py +57 -0
  23. llm_behave-0.1.0/llm_assert/providers/openai_adapter.py +57 -0
  24. llm_behave-0.1.0/pyproject.toml +78 -0
  25. llm_behave-0.1.0/tests/__init__.py +0 -0
  26. llm_behave-0.1.0/tests/test_assertions.py +109 -0
  27. llm_behave-0.1.0/tests/test_conversation.py +80 -0
  28. llm_behave-0.1.0/tests/test_drift.py +92 -0
  29. llm_behave-0.1.0/tests/test_e2e.py +267 -0
  30. llm_behave-0.1.0/tests/test_harden.py +373 -0
  31. llm_behave-0.1.0/tests/test_intent.py +36 -0
  32. llm_behave-0.1.0/tests/test_mentions.py +72 -0
  33. llm_behave-0.1.0/tests/test_multi_turn.py +106 -0
  34. llm_behave-0.1.0/tests/test_providers.py +41 -0
  35. llm_behave-0.1.0/tests/test_semantic_engine.py +62 -0
  36. llm_behave-0.1.0/tests/test_tone.py +74 -0
@@ -0,0 +1,28 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ jobs:
8
+ publish:
9
+ runs-on: ubuntu-latest
10
+ steps:
11
+ - uses: actions/checkout@v4
12
+
13
+ - name: Set up Python
14
+ uses: actions/setup-python@v5
15
+ with:
16
+ python-version: "3.12"
17
+
18
+ - name: Install build tools
19
+ run: pip install build twine hatchling
20
+
21
+ - name: Build package
22
+ run: python -m build --no-isolation
23
+
24
+ - name: Upload to PyPI
25
+ run: twine upload dist/*
26
+ env:
27
+ TWINE_USERNAME: __token__
28
+ TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
@@ -0,0 +1,54 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.egg-info/
6
+ dist/
7
+ build/
8
+ *.egg
9
+ .eggs/
10
+
11
+ # Virtual environment
12
+ .venv/
13
+ venv/
14
+ env/
15
+
16
+ # Testing
17
+ .pytest_cache/
18
+ htmlcov/
19
+ coverage.xml
20
+ .coverage
21
+ *.cover
22
+
23
+ # llm-assert baselines (user-specific, not committed)
24
+ .llm_assert_baselines/
25
+
26
+ # ML models cache
27
+ .models_cache/
28
+
29
+ # IDE
30
+ .idea/
31
+ *.swp
32
+ *.swo
33
+
34
+ # OS
35
+ .DS_Store
36
+ Thumbs.db
37
+
38
+ # Reference docs (not part of the library)
39
+ *.docx
40
+ *.pdf
41
+ rought.txt
42
+
43
+ # Environment
44
+ .env
45
+ .env.local
46
+
47
+ # Internal / private files (not for public GitHub)
48
+ CLAUDE.md
49
+ ideas.txt
50
+ PROJECT_LOG.txt
51
+ PLAIN_ENGLISH_GUIDE.txt
52
+ launch/
53
+ .claudeignore
54
+ .vscode/
@@ -0,0 +1,30 @@
1
+ # Changelog
2
+
3
+ All notable changes to llm-assert will be documented here.
4
+
5
+ ## [0.1.0] — 2026-03-12
6
+
7
+ First public release.
8
+
9
+ ### Added
10
+ - `assert_behavior(text)` — fluent assertion interface for LLM outputs
11
+ - `.mentions(concept)` — semantic similarity using sentence-level max comparison
12
+ - `.not_mentions(concept)` — assert a topic is absent
13
+ - `.tone(label)` — detect empathetic / professional / rude / helpful / formal / casual etc.
14
+ - `.intent(description)` — match output intent against a natural language description
15
+ - `.calls_tool(name)` — assert a specific tool was called (supports OpenAI + Anthropic formats)
16
+ - `ConversationTest` — multi-turn conversation harness with full history tracking
17
+ - `.recalls(concept)` — assert response references a concept from earlier in the conversation
18
+ - `.contradicts_turn(n)` — NLI-based contradiction detection across turns
19
+ - `.consistent_tone_across_turns()` — tone consistency check across all assistant turns
20
+ - `DriftTest` — baseline save and compare for CI regression detection
21
+ - `@DriftTest.baseline()` — decorator to save test output as baseline
22
+ - `MockProvider` — built-in mock for testing without real LLM API calls
23
+ - `OpenAIProvider` — adapter for OpenAI chat completions
24
+ - `AnthropicProvider` — adapter for Anthropic messages API
25
+ - `OllamaProvider` — adapter for Ollama local models
26
+ - pytest fixtures: `assert_llm`, `mock_provider`, `conversation`
27
+ - pytest markers: `behavioral`, `drift`
28
+ - Offline ML engine: `all-MiniLM-L6-v2` (80MB, CPU, no internet needed)
29
+ - Batch encoding for tone detection (~4x faster than sequential)
30
+ - Lazy model loading — fast import, model loads on first assertion
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Swanand Potnis
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,307 @@
1
+ Metadata-Version: 2.4
2
+ Name: llm-behave
3
+ Version: 0.1.0
4
+ Summary: Behavioral testing for LLM applications. pytest plugin with semantic assertions, multi-turn conversation testing, and drift detection. No LLM judge needed.
5
+ Project-URL: Homepage, https://github.com/Swanand33/llm-behave
6
+ Project-URL: Repository, https://github.com/Swanand33/llm-behave
7
+ Project-URL: Issues, https://github.com/Swanand33/llm-behave/issues
8
+ Author-email: Swanand Potnis <swanandpotnis500@gmail.com>
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: ai,anthropic,behavioral-testing,langchain,llm,openai,pytest,testing
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Framework :: Pytest
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
21
+ Classifier: Topic :: Software Development :: Testing
22
+ Requires-Python: >=3.10
23
+ Requires-Dist: numpy>=1.24
24
+ Requires-Dist: pydantic>=2.0
25
+ Requires-Dist: pytest>=7.0
26
+ Provides-Extra: all
27
+ Requires-Dist: anthropic>=0.20; extra == 'all'
28
+ Requires-Dist: ollama>=0.1; extra == 'all'
29
+ Requires-Dist: openai>=1.0; extra == 'all'
30
+ Requires-Dist: sentence-transformers>=2.2; extra == 'all'
31
+ Requires-Dist: torch>=2.0; extra == 'all'
32
+ Provides-Extra: anthropic
33
+ Requires-Dist: anthropic>=0.20; extra == 'anthropic'
34
+ Provides-Extra: dev
35
+ Requires-Dist: mypy>=1.0; extra == 'dev'
36
+ Requires-Dist: pytest-cov>=4.0; extra == 'dev'
37
+ Requires-Dist: pytest-html>=3.0; extra == 'dev'
38
+ Requires-Dist: ruff>=0.1; extra == 'dev'
39
+ Provides-Extra: ollama
40
+ Requires-Dist: ollama>=0.1; extra == 'ollama'
41
+ Provides-Extra: openai
42
+ Requires-Dist: openai>=1.0; extra == 'openai'
43
+ Provides-Extra: semantic
44
+ Requires-Dist: sentence-transformers>=2.2; extra == 'semantic'
45
+ Requires-Dist: torch>=2.0; extra == 'semantic'
46
+ Description-Content-Type: text/markdown
47
+
48
+ # llm-behave
49
+
50
+ **Behavioral testing for LLM applications. A pytest plugin.**
51
+
52
+ No LLM judge. No API cost. Offline models. Works with any provider.
53
+
54
+ ```python
55
+ def test_support_bot():
56
+ output = my_support_bot("I want a refund for order 1234")
57
+
58
+ assert_behavior(output) \
59
+ .mentions("refund policy") \
60
+ .tone("empathetic") \
61
+ .not_mentions("competitor")
62
+ ```
63
+
64
+ ---
65
+
66
+ ## Why llm-behave?
67
+
68
+ Most LLM testing tools either:
69
+ - Use another LLM to judge the output (expensive, slow, circular)
70
+ - Only do exact string matching (misses semantic meaning)
71
+ - Don't support multi-turn conversations at all
72
+
73
+ **llm-behave** uses small offline transformer models (80MB, runs on CPU) to understand meaning — no API calls, no cost, no internet required during tests.
74
+
75
+ ---
76
+
77
+ ## Install
78
+
79
+ ```bash
80
+ # Core (tool call assertions, structure tests — no ML deps)
81
+ pip install llm-behave
82
+
83
+ # Full (semantic assertions: mentions, tone, intent, drift)
84
+ pip install llm-behave[semantic]
85
+ ```
86
+
87
+ ---
88
+
89
+ ## Features at a glance
90
+
91
+ | Feature | What it does |
92
+ |---|---|
93
+ | `mentions()` | Semantic similarity — "money back" matches "refund" |
94
+ | `not_mentions()` | Assert a topic is NOT brought up |
95
+ | `tone()` | Detect empathetic / professional / rude / helpful etc. |
96
+ | `intent()` | Does the response intend to help? refuse? apologize? |
97
+ | `calls_tool()` | Assert which tool the LLM called |
98
+ | `ConversationTest` | Multi-turn testing with memory, contradiction detection |
99
+ | `DriftTest` | Save baseline behavior, detect regressions in CI |
100
+
101
+ ---
102
+
103
+ ## Usage
104
+
105
+ ### Basic assertions
106
+
107
+ ```python
108
+ from llm_assert import assert_behavior
109
+
110
+ output = my_llm("I want a refund")
111
+
112
+ # Semantic match — not exact string
113
+ assert_behavior(output).mentions("refund policy")
114
+
115
+ # Tone detection
116
+ assert_behavior(output).tone("empathetic")
117
+ assert_behavior(output).tone("professional", threshold=0.6)
118
+
119
+ # Intent
120
+ assert_behavior(output).intent("offering to help the customer")
121
+
122
+ # Negative assertions
123
+ assert_behavior(output).not_mentions("competitor")
124
+
125
+ # Fluent chaining
126
+ assert_behavior(output) \
127
+ .mentions("refund") \
128
+ .tone("empathetic") \
129
+ .not_mentions("competitor")
130
+ ```
131
+
132
+ ### Tool call assertions
133
+
134
+ ```python
135
+ text, tool_calls = my_llm.chat_with_tools(messages, tools=my_tools)
136
+
137
+ assert_behavior(text, tool_calls) \
138
+ .calls_tool("lookup_order") \
139
+ .mentions("order")
140
+ ```
141
+
142
+ ### Multi-turn conversation testing
143
+
144
+ ```python
145
+ from llm_assert import ConversationTest, MockProvider
146
+
147
+ conv = ConversationTest(agent=my_agent)
148
+
149
+ conv.say("Hi, my name is Swanand")
150
+ conv.say("I placed order #5678 last week")
151
+ response = conv.say("When will it arrive?")
152
+
153
+ # Does it remember context from earlier turns?
154
+ assert response.recalls("order")
155
+ assert response.recalls("Swanand")
156
+
157
+ # Is tone consistent across the whole conversation?
158
+ assert response.consistent_tone_across_turns(threshold=0.6)
159
+ ```
160
+
161
+ ### Drift detection (for CI)
162
+
163
+ Catch silent regressions when you update your model or prompts.
164
+
165
+ ```python
166
+ from llm_assert import DriftTest
167
+
168
+ # First run: save baseline
169
+ @DriftTest.baseline(save_as="support_refund_flow")
170
+ def get_baseline_output():
171
+ return my_llm("I need a refund")
172
+
173
+ # Every CI run: compare against baseline
174
+ result = DriftTest.compare("support_refund_flow", current_output)
175
+ assert result.passed, f"Behavior drift detected: {result.details}"
176
+ ```
177
+
178
+ ### pytest fixtures (auto-registered)
179
+
180
+ ```python
181
+ # These fixtures are available in any test file automatically
182
+
183
+ def test_with_mock(mock_provider, assert_llm):
184
+ provider = mock_provider(responses=["I'll help with your refund right away."])
185
+ output = provider.chat([{"role": "user", "content": "refund please"}])
186
+ assert_llm(output).mentions("refund").tone("helpful")
187
+
188
+ def test_conversation(conversation):
189
+ conv = conversation(responses=["Hello!", "Sure, I can help with that."])
190
+ conv.say("Hi")
191
+ response = conv.say("I need help")
192
+ assert "help" in response.text.lower()
193
+ ```
194
+
195
+ ---
196
+
197
+ ## Providers
198
+
199
+ Built-in adapters for all major LLM providers:
200
+
201
+ ```python
202
+ from llm_assert.providers.openai_adapter import OpenAIProvider
203
+ from llm_assert.providers.anthropic_adapter import AnthropicProvider
204
+ from llm_assert.providers.ollama_adapter import OllamaProvider
205
+ from llm_assert import MockProvider # for tests, no API calls
206
+
207
+ # All providers have the same interface
208
+ provider = OpenAIProvider(model="gpt-4o-mini")
209
+ provider = AnthropicProvider(model="claude-haiku-4-5-20251001")
210
+ provider = OllamaProvider(model="llama3")
211
+
212
+ output = provider.chat([{"role": "user", "content": "Hello"}])
213
+ text, tool_calls = provider.chat_with_tools(messages, tools=my_tools)
214
+ ```
215
+
216
+ Bring your own provider by subclassing `LLMProvider`:
217
+
218
+ ```python
219
+ from llm_assert.providers.base import LLMProvider
220
+
221
+ class MyProvider(LLMProvider):
222
+ def chat(self, messages, **kwargs):
223
+ ...
224
+ def chat_with_tools(self, messages, tools, **kwargs):
225
+ ...
226
+ ```
227
+
228
+ ---
229
+
230
+ ## How it works
231
+
232
+ llm-behave uses [`all-MiniLM-L6-v2`](https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2) — an 80MB sentence-transformer model that runs fully offline on CPU.
233
+
234
+ - **`mentions()` / `not_mentions()`** — splits text into sentences, computes max cosine similarity between any sentence and your concept
235
+ - **`tone()`** — batch-encodes input text against example sentences for each tone, returns max similarity
236
+ - **`intent()`** — semantic similarity between output and your intent description
237
+ - **`contradicts_turn()`** — NLI (Natural Language Inference) model detects logical contradictions
238
+
239
+ Models load **lazily** on first use and are cached for the rest of the test session. Import time stays fast.
240
+
241
+ ---
242
+
243
+ ## Performance
244
+
245
+ Measured after model warmup (model loads once per test session):
246
+
247
+ | Assertion | Time |
248
+ |---|---|
249
+ | `mentions()` | ~32ms |
250
+ | `tone()` | ~40ms |
251
+ | `intent()` | ~32ms |
252
+ | 4-assertion chain | ~350ms |
253
+
254
+ ---
255
+
256
+ ## pytest markers
257
+
258
+ ```python
259
+ import pytest
260
+
261
+ @pytest.mark.behavioral
262
+ def test_refund_flow():
263
+ ...
264
+
265
+ @pytest.mark.drift
266
+ def test_no_regression():
267
+ ...
268
+ ```
269
+
270
+ Run only behavioral tests:
271
+ ```bash
272
+ pytest -m behavioral
273
+ pytest -m drift
274
+ ```
275
+
276
+ ---
277
+
278
+ ## Full install options
279
+
280
+ ```bash
281
+ pip install llm-behave # core only
282
+ pip install llm-behave[semantic] # + sentence-transformers + torch
283
+ pip install llm-behave[openai] # + openai SDK
284
+ pip install llm-behave[anthropic] # + anthropic SDK
285
+ pip install llm-behave[ollama] # + ollama SDK
286
+ pip install llm-behave[all] # everything
287
+ ```
288
+
289
+ ---
290
+
291
+ ## Requirements
292
+
293
+ - Python 3.10+
294
+ - pytest 7.0+
295
+ - For semantic assertions: `pip install llm-behave[semantic]`
296
+
297
+ ---
298
+
299
+ ## License
300
+
301
+ MIT — free to use in personal and commercial projects.
302
+
303
+ ---
304
+
305
+ ## Author
306
+
307
+ Built by [Swanand Potnis](https://github.com/Swanand33) — Pune, India.
@@ -0,0 +1,260 @@
1
+ # llm-behave
2
+
3
+ **Behavioral testing for LLM applications. A pytest plugin.**
4
+
5
+ No LLM judge. No API cost. Offline models. Works with any provider.
6
+
7
+ ```python
8
+ def test_support_bot():
9
+ output = my_support_bot("I want a refund for order 1234")
10
+
11
+ assert_behavior(output) \
12
+ .mentions("refund policy") \
13
+ .tone("empathetic") \
14
+ .not_mentions("competitor")
15
+ ```
16
+
17
+ ---
18
+
19
+ ## Why llm-behave?
20
+
21
+ Most LLM testing tools either:
22
+ - Use another LLM to judge the output (expensive, slow, circular)
23
+ - Only do exact string matching (misses semantic meaning)
24
+ - Don't support multi-turn conversations at all
25
+
26
+ **llm-behave** uses small offline transformer models (80MB, runs on CPU) to understand meaning — no API calls, no cost, no internet required during tests.
27
+
28
+ ---
29
+
30
+ ## Install
31
+
32
+ ```bash
33
+ # Core (tool call assertions, structure tests — no ML deps)
34
+ pip install llm-behave
35
+
36
+ # Full (semantic assertions: mentions, tone, intent, drift)
37
+ pip install llm-behave[semantic]
38
+ ```
39
+
40
+ ---
41
+
42
+ ## Features at a glance
43
+
44
+ | Feature | What it does |
45
+ |---|---|
46
+ | `mentions()` | Semantic similarity — "money back" matches "refund" |
47
+ | `not_mentions()` | Assert a topic is NOT brought up |
48
+ | `tone()` | Detect empathetic / professional / rude / helpful etc. |
49
+ | `intent()` | Does the response intend to help? refuse? apologize? |
50
+ | `calls_tool()` | Assert which tool the LLM called |
51
+ | `ConversationTest` | Multi-turn testing with memory, contradiction detection |
52
+ | `DriftTest` | Save baseline behavior, detect regressions in CI |
53
+
54
+ ---
55
+
56
+ ## Usage
57
+
58
+ ### Basic assertions
59
+
60
+ ```python
61
+ from llm_assert import assert_behavior
62
+
63
+ output = my_llm("I want a refund")
64
+
65
+ # Semantic match — not exact string
66
+ assert_behavior(output).mentions("refund policy")
67
+
68
+ # Tone detection
69
+ assert_behavior(output).tone("empathetic")
70
+ assert_behavior(output).tone("professional", threshold=0.6)
71
+
72
+ # Intent
73
+ assert_behavior(output).intent("offering to help the customer")
74
+
75
+ # Negative assertions
76
+ assert_behavior(output).not_mentions("competitor")
77
+
78
+ # Fluent chaining
79
+ assert_behavior(output) \
80
+ .mentions("refund") \
81
+ .tone("empathetic") \
82
+ .not_mentions("competitor")
83
+ ```
84
+
85
+ ### Tool call assertions
86
+
87
+ ```python
88
+ text, tool_calls = my_llm.chat_with_tools(messages, tools=my_tools)
89
+
90
+ assert_behavior(text, tool_calls) \
91
+ .calls_tool("lookup_order") \
92
+ .mentions("order")
93
+ ```
94
+
95
+ ### Multi-turn conversation testing
96
+
97
+ ```python
98
+ from llm_assert import ConversationTest, MockProvider
99
+
100
+ conv = ConversationTest(agent=my_agent)
101
+
102
+ conv.say("Hi, my name is Swanand")
103
+ conv.say("I placed order #5678 last week")
104
+ response = conv.say("When will it arrive?")
105
+
106
+ # Does it remember context from earlier turns?
107
+ assert response.recalls("order")
108
+ assert response.recalls("Swanand")
109
+
110
+ # Is tone consistent across the whole conversation?
111
+ assert response.consistent_tone_across_turns(threshold=0.6)
112
+ ```
113
+
114
+ ### Drift detection (for CI)
115
+
116
+ Catch silent regressions when you update your model or prompts.
117
+
118
+ ```python
119
+ from llm_assert import DriftTest
120
+
121
+ # First run: save baseline
122
+ @DriftTest.baseline(save_as="support_refund_flow")
123
+ def get_baseline_output():
124
+ return my_llm("I need a refund")
125
+
126
+ # Every CI run: compare against baseline
127
+ result = DriftTest.compare("support_refund_flow", current_output)
128
+ assert result.passed, f"Behavior drift detected: {result.details}"
129
+ ```
130
+
131
+ ### pytest fixtures (auto-registered)
132
+
133
+ ```python
134
+ # These fixtures are available in any test file automatically
135
+
136
+ def test_with_mock(mock_provider, assert_llm):
137
+ provider = mock_provider(responses=["I'll help with your refund right away."])
138
+ output = provider.chat([{"role": "user", "content": "refund please"}])
139
+ assert_llm(output).mentions("refund").tone("helpful")
140
+
141
+ def test_conversation(conversation):
142
+ conv = conversation(responses=["Hello!", "Sure, I can help with that."])
143
+ conv.say("Hi")
144
+ response = conv.say("I need help")
145
+ assert "help" in response.text.lower()
146
+ ```
147
+
148
+ ---
149
+
150
+ ## Providers
151
+
152
+ Built-in adapters for all major LLM providers:
153
+
154
+ ```python
155
+ from llm_assert.providers.openai_adapter import OpenAIProvider
156
+ from llm_assert.providers.anthropic_adapter import AnthropicProvider
157
+ from llm_assert.providers.ollama_adapter import OllamaProvider
158
+ from llm_assert import MockProvider # for tests, no API calls
159
+
160
+ # All providers have the same interface
161
+ provider = OpenAIProvider(model="gpt-4o-mini")
162
+ provider = AnthropicProvider(model="claude-haiku-4-5-20251001")
163
+ provider = OllamaProvider(model="llama3")
164
+
165
+ output = provider.chat([{"role": "user", "content": "Hello"}])
166
+ text, tool_calls = provider.chat_with_tools(messages, tools=my_tools)
167
+ ```
168
+
169
+ Bring your own provider by subclassing `LLMProvider`:
170
+
171
+ ```python
172
+ from llm_assert.providers.base import LLMProvider
173
+
174
+ class MyProvider(LLMProvider):
175
+ def chat(self, messages, **kwargs):
176
+ ...
177
+ def chat_with_tools(self, messages, tools, **kwargs):
178
+ ...
179
+ ```
180
+
181
+ ---
182
+
183
+ ## How it works
184
+
185
+ llm-behave uses [`all-MiniLM-L6-v2`](https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2) — an 80MB sentence-transformer model that runs fully offline on CPU.
186
+
187
+ - **`mentions()` / `not_mentions()`** — splits text into sentences, computes max cosine similarity between any sentence and your concept
188
+ - **`tone()`** — batch-encodes input text against example sentences for each tone, returns max similarity
189
+ - **`intent()`** — semantic similarity between output and your intent description
190
+ - **`contradicts_turn()`** — NLI (Natural Language Inference) model detects logical contradictions
191
+
192
+ Models load **lazily** on first use and are cached for the rest of the test session. Import time stays fast.
193
+
194
+ ---
195
+
196
+ ## Performance
197
+
198
+ Measured after model warmup (model loads once per test session):
199
+
200
+ | Assertion | Time |
201
+ |---|---|
202
+ | `mentions()` | ~32ms |
203
+ | `tone()` | ~40ms |
204
+ | `intent()` | ~32ms |
205
+ | 4-assertion chain | ~350ms |
206
+
207
+ ---
208
+
209
+ ## pytest markers
210
+
211
+ ```python
212
+ import pytest
213
+
214
+ @pytest.mark.behavioral
215
+ def test_refund_flow():
216
+ ...
217
+
218
+ @pytest.mark.drift
219
+ def test_no_regression():
220
+ ...
221
+ ```
222
+
223
+ Run only behavioral tests:
224
+ ```bash
225
+ pytest -m behavioral
226
+ pytest -m drift
227
+ ```
228
+
229
+ ---
230
+
231
+ ## Full install options
232
+
233
+ ```bash
234
+ pip install llm-behave # core only
235
+ pip install llm-behave[semantic] # + sentence-transformers + torch
236
+ pip install llm-behave[openai] # + openai SDK
237
+ pip install llm-behave[anthropic] # + anthropic SDK
238
+ pip install llm-behave[ollama] # + ollama SDK
239
+ pip install llm-behave[all] # everything
240
+ ```
241
+
242
+ ---
243
+
244
+ ## Requirements
245
+
246
+ - Python 3.10+
247
+ - pytest 7.0+
248
+ - For semantic assertions: `pip install llm-behave[semantic]`
249
+
250
+ ---
251
+
252
+ ## License
253
+
254
+ MIT — free to use in personal and commercial projects.
255
+
256
+ ---
257
+
258
+ ## Author
259
+
260
+ Built by [Swanand Potnis](https://github.com/Swanand33) — Pune, India.