Skip to content

Commit d2dc48d

Browse files
authored
FIRE-570 | Qualifire tracing (#244)
* Add qualifire tracing * Add missing files * Codestyle * Updategitignore * Disable otel metrics and logging * update poetry.lock * poetry fixes and codestyle * Move consts to separate file * Add traceloop dependency * handle lower python versions in code * Delete historic tests, create tests for utils * Delete historic tests, create tests for utils * Fix tests * Fix tests * Disable telemetry and traceloop sync
1 parent ba78b73 commit d2dc48d

13 files changed

Lines changed: 3403 additions & 842 deletions

File tree

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ Temporary Items
3434
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
3535
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
3636

37+
.idea/
38+
3739
# User-specific stuff
3840
.idea/**/workspace.xml
3941
.idea/**/tasks.xml

.pre-commit-config.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,14 @@ default_stages: [commit, push]
22

33
repos:
44
- repo: https://github.com/pre-commit/pre-commit-hooks
5-
rev: v2.5.0
5+
rev: v5.0.0
66
hooks:
77
- id: check-yaml
88
- id: end-of-file-fixer
99
exclude: LICENSE
1010

1111
- repo: https://github.com/pycqa/flake8
12-
rev: "3.9.2" # pick a git hash / tag to point to
12+
rev: "7.2.0" # pick a git hash / tag to point to
1313
hooks:
1414
- id: flake8
1515

poetry.lock

Lines changed: 3239 additions & 790 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ build-backend = "poetry.core.masonry.api"
55

66
[tool.poetry]
77
name = "qualifire"
8-
version = "0.7.0"
8+
version = "0.8.0"
99
description = "Qualifire Python SDK"
1010
readme = "README.md"
1111
authors = ["qualifire-dev <dror@qualifire.ai>"]
@@ -33,6 +33,7 @@ classifiers = [ #! Update me
3333

3434
[tool.poetry.dependencies]
3535
python = "^3.8"
36+
traceloop-sdk = { version = "0.40.4" , markers = "python_version >= '3.10'" }
3637

3738
[tool.poetry.dev-dependencies]
3839
bandit = "^1.7.1"

qualifire/__init__.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,11 @@
1-
"""Qualifire PYthon SDK"""
1+
"""Qualifire Python SDK"""
22

33
import logging
4-
import os
54

6-
import pkg_resources
7-
8-
from . import client, types
5+
from . import client, consts, tracer_init, types, utils
6+
from .tracer_init import init
97

108
logger = logging.getLogger("qualifire")
11-
QUALIFIRE_API_KEY_ENV_VAR = "QUALIFIRE_API_KEY"
12-
QUALIFIRE_BASE_URL_ENV_VAR = "QUALIFIRE_BASE_URL"
13-
_DEFAULT_BASE_URL = "https://proxy.qualifire.ai/"
149

1510

1611
from importlib import metadata as importlib_metadata
@@ -24,3 +19,10 @@ def get_version() -> str:
2419

2520

2621
version: str = get_version()
22+
23+
__all__ = [
24+
"client",
25+
"types",
26+
"init",
27+
"version",
28+
]

qualifire/client.py

Lines changed: 11 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,21 @@
77
import requests
88

99
from .types import EvaluationRequest, EvaluationResponse, LLMMessage, SyntaxCheckArgs
10+
from .utils import get_api_key, get_base_url
1011

1112
logger = logging.getLogger("qualifire")
1213

1314

1415
class Client:
1516
def __init__(
1617
self,
17-
api_key: str,
18-
base_url: str = "https://proxy.qualifire.ai",
19-
version: str = None,
18+
api_key: Optional[str],
19+
base_url: Optional[str] = None,
20+
version: Optional[str] = None,
2021
debug: bool = False,
2122
) -> None:
22-
self._base_url = base_url
23-
self._api_key = api_key
23+
self._base_url = base_url or get_base_url()
24+
self._api_key = api_key or get_api_key()
2425
self._version = version
2526
self._debug = debug
2627

@@ -47,8 +48,6 @@ def evaluate(
4748
:param input: The primary input for the evaluation.
4849
:param output: The primary output (e.g., LLM response) to evaluate.
4950
:param assertions: A list of custom assertions to check against the output.
50-
:param consistency_check: Check for consistency between
51-
input/output and context.
5251
:param dangerous_content_check: Check for dangerous content generation.
5352
:param grounding_check: Check if the output is grounded in the provided
5453
input/context.
@@ -60,8 +59,6 @@ def evaluate(
6059
:param messages: List of message objects representing conversation history.
6160
:param pii_check: Check for personally identifiable information.
6261
:param prompt_injections: Check for attempts at prompt injection.
63-
:param responseFunctionCalls: Expected function calls in the response
64-
(if applicable).
6562
:param sexual_content_check: Check for sexually explicit content.
6663
:param syntax_checks: Dictionary defining syntax checks (e.g., JSON, SQL).
6764
@@ -80,7 +77,6 @@ def evaluate(
8077
input="Translate 'hello' to French and provide the result in JSON format.",
8178
output='{"translation": "bonjour"}',
8279
assertions=["The output must contain the key 'translation'"],
83-
consistency_check=True,
8480
dangerous_content_check=True,
8581
grounding_check=True,
8682
hallucinations_check=True,
@@ -96,7 +92,6 @@ def evaluate(
9692
],
9793
pii_check=True,
9894
prompt_injections=True,
99-
# responseFunctionCalls="some_function_call", # Optional
10095
sexual_content_check=True,
10196
syntax_checks={
10297
"json": SyntaxCheckArgs(args="strict") # Example syntax check
@@ -135,16 +130,15 @@ def evaluate(
135130
if response.status_code != 200:
136131
raise Exception(f"Qualifire API error: {response.text}")
137132

138-
jsonResponse = response.json()
139-
return EvaluationResponse(**jsonResponse)
133+
json_response = response.json()
134+
return EvaluationResponse(**json_response)
140135

141136
def invoke_evaluation(
142137
self,
143138
input: str,
144139
output: str,
145140
evaluation_id: str,
146-
):
147-
141+
) -> EvaluationResponse:
148142
url = f"{self._base_url}/api/evaluation/invoke/"
149143

150144
payload = {
@@ -163,5 +157,5 @@ def invoke_evaluation(
163157
response.raise_for_status()
164158
raise Exception(f"Qualifire API error: {response.text}")
165159

166-
jsonResponse = response.json()
167-
return EvaluationResponse(**jsonResponse)
160+
json_response = response.json()
161+
return EvaluationResponse(**json_response)

qualifire/consts.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
QUALIFIRE_API_KEY_ENV_VAR = "QUALIFIRE_API_KEY"
2+
QUALIFIRE_BASE_URL_ENV_VAR = "QUALIFIRE_BASE_URL"
3+
_DEFAULT_BASE_URL = "https://proxy.qualifire.ai/"

qualifire/tracer_init.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
from typing import Any, Callable, Optional, TypeVar
2+
3+
import os
4+
import sys
5+
6+
try:
7+
from traceloop.sdk import Traceloop
8+
9+
traceloop_installed = True
10+
except ImportError:
11+
traceloop_installed = False
12+
13+
from .utils import get_api_key, get_base_url
14+
15+
R = TypeVar("R")
16+
17+
18+
def __suppress_prints(
19+
func: Callable[..., R],
20+
*args: Any,
21+
**kwargs: Any,
22+
) -> R:
23+
original_stdout = sys.stdout
24+
sys.stdout = open(os.devnull, "w")
25+
try:
26+
return func(*args, **kwargs)
27+
finally:
28+
sys.stdout.close()
29+
sys.stdout = original_stdout
30+
31+
32+
def __configure_tracer(api_key: str) -> None:
33+
# traceloop uses env for those configs
34+
os.environ["TRACELOOP_METRICS_ENABLED"] = "false"
35+
os.environ["TRACELOOP_LOGGING_ENABLED"] = "false"
36+
37+
__suppress_prints(
38+
Traceloop.init,
39+
app_name="qualifire-agent",
40+
api_endpoint=f"{get_base_url()}/telemetry", # /v1/traces is automatically added # noqa: E501
41+
headers={"X-Qualifire-API-Key": api_key},
42+
telemetry_enabled=False,
43+
traceloop_sync_enabled=False,
44+
)
45+
46+
47+
def init(api_key: Optional[str] = None) -> None:
48+
if not traceloop_installed:
49+
if sys.version_info < (3, 10):
50+
raise RuntimeError("qualifire.init requires Python 3.10 or higher")
51+
else:
52+
raise RuntimeError("Dependency error, please reinstall qualifire-sdk")
53+
54+
api_key = api_key or get_api_key()
55+
__configure_tracer(api_key)

qualifire/utils.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import os
2+
3+
from .consts import (
4+
_DEFAULT_BASE_URL,
5+
QUALIFIRE_API_KEY_ENV_VAR,
6+
QUALIFIRE_BASE_URL_ENV_VAR,
7+
)
8+
9+
10+
def get_api_key() -> str:
11+
api_key = os.getenv(QUALIFIRE_API_KEY_ENV_VAR)
12+
if not api_key:
13+
raise ValueError("Qualifire API Key not found")
14+
return api_key
15+
16+
17+
def get_base_url() -> str:
18+
base_url = os.getenv(QUALIFIRE_BASE_URL_ENV_VAR)
19+
if not base_url:
20+
return _DEFAULT_BASE_URL
21+
return base_url

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,4 @@ requests==2.31.0 ; python_version >= "3.8" and python_version < "4.0"
1212
tqdm==4.66.1 ; python_version >= "3.8" and python_version < "4.0"
1313
urllib3==2.1.0 ; python_version >= "3.8" and python_version < "4.0"
1414
yarl==1.9.4 ; python_version >= "3.8" and python_version < "4.0"
15+
traceloop-sdk==0.40.4 ; python_version >= "3.10" and python_version < "4.0"

0 commit comments

Comments
 (0)