Skip to content

Commit ec642e5

Browse files
committed
feat: add AI utils methods to compare texts
1 parent 605cb29 commit ec642e5

10 files changed

Lines changed: 412 additions & 1 deletion

File tree

.github/workflows/ci.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ on: [push, pull_request]
44

55
jobs:
66
test:
7-
87
runs-on: ubuntu-latest
98
strategy:
109
matrix:
@@ -22,10 +21,15 @@ jobs:
2221
python -m pip install --upgrade pip
2322
pip install -r requirements.txt
2423
pip install -r requirements_dev.txt
24+
python -m spacy download en_core_web_sm
2525
- name: Lint with flake8
2626
run: |
2727
flake8 . --count --max-complexity=10 --max-line-length=121 --show-source --statistics
2828
- name: Test with pytest and coverage
29+
env:
30+
AZURE_OPENAI_API_KEY: ${{ secrets.AZURE_OPENAI_API_KEY }}
31+
AZURE_OPENAI_ENDPOINT: ${{ secrets.AZURE_OPENAI_ENDPOINT }}
32+
OPENAI_API_VERSION: ${{ secrets.OPENAI_API_VERSION }}
2933
run: |
3034
coverage run --source=toolium -m pytest toolium/test
3135
- name: Publish on coveralls.io

CHANGELOG.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@ v3.6.0
66

77
*Release date: In development*
88

9+
- Add text comparison methods based on AI utils. To use them, install the `ai` extra dependency:
10+
11+
.. code:: console
12+
13+
$ pip install toolium[ai]
914
1015
v3.5.0
1116
------

docs/ai_utils.rst

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
.. _tests_result_analysis:
2+
3+
AI Utils
4+
========
5+
6+
Text Similarity
7+
---------------
8+
9+
Toolium provides several methods to compare text similarity using different techniques:
10+
11+
1. **SpaCy**: Uses the SpaCy library to compute text similarity with SLM models.
12+
2. **Sentence Transformers**: Leverages Sentence Transformers for semantic textual similarity.
13+
3. **OpenAI LLM**: Utilizes OpenAI's language model for advanced semantic text comparison.
14+
15+
You can use the function `check_text_similarity` from `toolium.utils.ai_utils` module to compare two texts using any of these
16+
methods. You can specify the method to use with the `similarity_method` parameter and set a threshold for similarity with the
17+
`threshold` parameter.
18+
19+
.. code-block:: python
20+
21+
from toolium.utils.ai_utils import check_text_similarity
22+
23+
input_text = "Your input text here"
24+
expected_text = "Your expected text here"
25+
threshold = 0.8 # Similarity threshold between 0 and 1
26+
similarity_method = 'spacy' # Options: 'spacy', 'sentence_transformers', 'openai_llm'
27+
28+
check_text_similarity(input_text, expected_text, threshold=threshold, similarity_method=similarity_method)
29+
30+
31+
Default similarity method can be set in the properties.cfg file with the property
32+
`text_similarity_method <https://toolium.readthedocs.io/en/latest/ai_utils.html#configuration>`_ in *[AI]* section ::
33+
34+
[AI]
35+
text_similarity_method: spacy
36+
37+
Make sure to install the required libraries for the chosen method. For SpaCy, Sentence Transformers or OpenAI LLM, you
38+
can install them with the following command:
39+
40+
.. code-block:: bash
41+
42+
pip install toolium[ai]
43+
44+
For SpaCy, you also need to download the language model:
45+
46+
.. code-block:: bash
47+
48+
python -m spacy download en_core_web_sm

docs/index.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ Main Features
1212
- :ref:`Choosing driver through a configuration file <driver_configuration>`
1313
- :ref:`Page Object pattern <page_objects>`
1414
- :ref:`BDD integration <bdd_integration>`
15+
- :ref:`AI utils <ai_utils>`
1516
- :ref:`Visual testing solution <visual_testing>`
1617
- :ref:`Tests result analysis <tests_result_analysis>`
1718

@@ -24,6 +25,7 @@ Library Reference
2425
driver_configuration.rst
2526
page_objects.rst
2627
bdd_integration.rst
28+
ai_utils.rst
2729
visual_testing.rst
2830
tests_result_analysis.rst
2931
Changelog <changelog.rst>

docs/toolium.utils.rst

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,16 @@
33
utils
44
=====
55

6+
.. _ai_utils:
7+
8+
ai_utils
9+
--------
10+
11+
.. automodule:: toolium.utils.ai_utils
12+
:members:
13+
:undoc-members:
14+
:show-inheritance:
15+
616
.. _dataset:
717

818
dataset

requirements_dev.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,7 @@ wheel~=0.40
1010
twine~=4.0
1111
behave==1.2.6 # behave tests
1212
importlib_metadata==7.2.1
13+
spacy~=3.8
14+
numpy~=1.26 # fix spaCy dependency error
15+
sentence-transformers~=5.1
16+
openai~=1.108

setup.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,12 @@ def get_long_description():
6363
tests_require=read_file('requirements_dev.txt').splitlines(),
6464
extras_require={
6565
"playwright": ["playwright~=1.43"],
66+
"ai": [
67+
"spacy~=3.8",
68+
"numpy~=1.26", # fix spaCy dependency error
69+
"sentence-transformers~=5.1",
70+
"openai~=1.108"
71+
]
6672
},
6773
test_suite='toolium.test',
6874
author='Rubén González Alonso, Telefónica I+D',

toolium/test/conf/properties.cfg

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,3 +58,6 @@ fixversion:
5858
labels:
5959
comments:
6060
build:
61+
62+
[AI]
63+
text_similarity_method: spacy
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
# -*- coding: utf-8 -*-
2+
"""
3+
Copyright 2025 Telefónica Innovación Digital, S.L.
4+
This file is part of Toolium.
5+
6+
Licensed under the Apache License, Version 2.0 (the "License");
7+
you may not use this file except in compliance with the License.
8+
You may obtain a copy of the License at
9+
10+
http://www.apache.org/licenses/LICENSE-2.0
11+
12+
Unless required by applicable law or agreed to in writing, software
13+
distributed under the License is distributed on an "AS IS" BASIS,
14+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
See the License for the specific language governing permissions and
16+
limitations under the License.
17+
"""
18+
19+
import mock
20+
import pytest
21+
22+
from toolium.driver_wrappers_pool import DriverWrappersPool
23+
from toolium.utils.ai_utils import (get_text_similarity_with_spacy, get_text_similarity_with_sentence_transformers,
24+
get_text_similarity_with_openai_llm, check_text_similarity)
25+
26+
27+
get_similarity_examples = (
28+
('Today it will be sunny', 'Today it will be sunny', 0.9, 1),
29+
('Today is sunny', 'Today it will be sunny', 0.6, 0.9),
30+
('It is sunny', 'Today it will be sunny', 0.5, 0.7),
31+
('Nothing related', 'Today it will be sunny', 0, 0.6),
32+
)
33+
34+
35+
@pytest.mark.parametrize('input_text, expected_text, expected_low, expected_high', get_similarity_examples)
36+
def test_get_text_similarity_with_spacy(input_text, expected_text, expected_low, expected_high):
37+
similarity = get_text_similarity_with_spacy(input_text, expected_text)
38+
assert expected_low <= similarity <= expected_high
39+
40+
41+
@pytest.mark.parametrize('input_text, expected_text, expected_low, expected_high', get_similarity_examples)
42+
def test_get_text_similarity_with_sentence_transformers(input_text, expected_text, expected_low, expected_high):
43+
similarity = get_text_similarity_with_sentence_transformers(input_text, expected_text)
44+
assert expected_low <= similarity <= expected_high
45+
46+
47+
get_openai_similarity_examples = (
48+
('Today it will be sunny', 'Today it will be sunny', 0.9, 1),
49+
('Today is sunny', 'Today it will be sunny', 0.7, 0.9),
50+
('It is sunny', 'Today it will be sunny', 0.7, 0.9),
51+
('Nothing related', 'Today it will be sunny', 0, 0.1),
52+
('Today it will be cloudy', 'Today it will be sunny', 0, 0.1),
53+
('A splendid and rainless day is expected', 'Today it will be sunny', 0.8, 1),
54+
)
55+
56+
57+
@pytest.mark.parametrize('input_text, expected_text, expected_low, expected_high', get_openai_similarity_examples)
58+
def test_get_text_similarity_with_openai_llm(input_text, expected_text, expected_low, expected_high):
59+
similarity = get_text_similarity_with_openai_llm(input_text, expected_text)
60+
assert expected_low <= similarity <= expected_high
61+
62+
63+
check_similarity_passed_examples = (
64+
('Today it will be sunny', 'Today it will be sunny', 0.9),
65+
('Today it will be sunny', ['Nothing related', 'Today it will be sunny'], 0.9),
66+
('Today is sunny', ['It will be nice', 'Today it will be sunny'], 0.6),
67+
)
68+
69+
70+
@pytest.mark.parametrize('input_text, expected_text, threshold', check_similarity_passed_examples)
71+
def test_check_text_similarity_with_spacy_passed(input_text, expected_text, threshold):
72+
check_text_similarity(input_text, expected_text, threshold=threshold, similarity_method='spacy')
73+
74+
75+
@pytest.mark.parametrize('input_text, expected_text, threshold', check_similarity_passed_examples)
76+
def test_check_text_similarity_with_sentence_transformers_passed(input_text, expected_text, threshold):
77+
check_text_similarity(input_text, expected_text, threshold=threshold, similarity_method='sentence_transformers')
78+
79+
80+
@pytest.mark.parametrize('input_text, expected_text, threshold', check_similarity_passed_examples)
81+
def test_check_text_similarity_with_openai_llm_passed(input_text, expected_text, threshold):
82+
check_text_similarity(input_text, expected_text, threshold=threshold, similarity_method='openai_llm')
83+
84+
85+
check_similarity_failed_examples = (
86+
('Today it will be sunny', 'Nothing related', 0.9),
87+
('Today it will be sunny', ['Nothing related', 'It is sunny'], 0.9),
88+
)
89+
90+
91+
@pytest.mark.parametrize('input_text, expected_text, threshold', check_similarity_failed_examples)
92+
def test_check_text_similarity_with_spacy_failed(input_text, expected_text, threshold):
93+
with pytest.raises(Exception) as excinfo:
94+
check_text_similarity(input_text, expected_text, threshold=threshold, similarity_method='spacy')
95+
assert str(excinfo.value).startswith('Similarity between received and expected texts is below threshold')
96+
97+
98+
@pytest.mark.parametrize('input_text, expected_text, threshold', check_similarity_failed_examples)
99+
def test_check_text_similarity_with_sentence_transformers_failed(input_text, expected_text, threshold):
100+
with pytest.raises(Exception) as excinfo:
101+
check_text_similarity(input_text, expected_text, threshold=threshold, similarity_method='sentence_transformers')
102+
assert str(excinfo.value).startswith('Similarity between received and expected texts is below threshold')
103+
104+
105+
check_openai_similarity_failed_examples = (
106+
('Today it will be sunny', 'Nothing related', 0.9),
107+
('Today it will be sunny', ['Nothing related', 'Today it is cold'], 0.9),
108+
)
109+
110+
111+
@pytest.mark.parametrize('input_text, expected_text, threshold', check_openai_similarity_failed_examples)
112+
def test_check_text_similarity_with_openai_llm_failed(input_text, expected_text, threshold):
113+
with pytest.raises(Exception) as excinfo:
114+
check_text_similarity(input_text, expected_text, threshold=threshold, similarity_method='openai_llm')
115+
assert str(excinfo.value).startswith('Similarity between received and expected texts is below threshold')
116+
117+
118+
@mock.patch('toolium.utils.ai_utils.get_text_similarity_with_spacy')
119+
def test_check_text_similarity_with_default_method(similarity_mock):
120+
similarity_mock.return_value = 0.9
121+
input_text = 'Today it will be sunny'
122+
expected_text = 'Today is sunny'
123+
check_text_similarity(input_text, expected_text, threshold=0.8)
124+
similarity_mock.assert_called_once_with(input_text, expected_text)
125+
126+
127+
@mock.patch('toolium.utils.ai_utils.get_text_similarity_with_sentence_transformers')
128+
def test_check_text_similarity_with_configured_method(similarity_mock):
129+
config = DriverWrappersPool.get_default_wrapper().config
130+
try:
131+
config.add_section('AI')
132+
except Exception:
133+
pass
134+
config.set('AI', 'text_similarity_method', 'sentence_transformers')
135+
similarity_mock.return_value = 0.9
136+
137+
input_text = 'Today it will be sunny'
138+
expected_text = 'Today is sunny'
139+
check_text_similarity(input_text, expected_text, threshold=0.8)
140+
similarity_mock.assert_called_once_with(input_text, expected_text)
141+
142+
143+
@mock.patch('toolium.utils.ai_utils.get_text_similarity_with_spacy')
144+
def test_check_text_similarity_with_configured_and_explicit_method(similarity_mock):
145+
config = DriverWrappersPool.get_default_wrapper().config
146+
try:
147+
config.add_section('AI')
148+
except Exception:
149+
pass
150+
config.set('AI', 'text_similarity_method', 'sentence_transformers')
151+
similarity_mock.return_value = 0.9
152+
153+
input_text = 'Today it will be sunny'
154+
expected_text = 'Today is sunny'
155+
check_text_similarity(input_text, expected_text, threshold=0.8, similarity_method='spacy')
156+
similarity_mock.assert_called_once_with(input_text, expected_text)

0 commit comments

Comments
 (0)