fusesell 1.3.2__py3-none-any.whl → 1.3.3__py3-none-any.whl
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.
Potentially problematic release.
This version of fusesell might be problematic. Click here for more details.
- {fusesell-1.3.2.dist-info → fusesell-1.3.3.dist-info}/METADATA +1 -1
- {fusesell-1.3.2.dist-info → fusesell-1.3.3.dist-info}/RECORD +11 -11
- fusesell_local/__init__.py +1 -1
- fusesell_local/api.py +3 -1
- fusesell_local/tests/test_api.py +31 -14
- fusesell_local/utils/__init__.py +12 -11
- fusesell_local/utils/llm_client.py +78 -19
- {fusesell-1.3.2.dist-info → fusesell-1.3.3.dist-info}/WHEEL +0 -0
- {fusesell-1.3.2.dist-info → fusesell-1.3.3.dist-info}/entry_points.txt +0 -0
- {fusesell-1.3.2.dist-info → fusesell-1.3.3.dist-info}/licenses/LICENSE +0 -0
- {fusesell-1.3.2.dist-info → fusesell-1.3.3.dist-info}/top_level.txt +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
fusesell.py,sha256=t5PjkhWEJGINp4k517u0EX0ge7lzuHOUHHro-BE1mGk,596
|
|
2
|
-
fusesell-1.3.
|
|
3
|
-
fusesell_local/__init__.py,sha256=
|
|
4
|
-
fusesell_local/api.py,sha256=
|
|
2
|
+
fusesell-1.3.3.dist-info/licenses/LICENSE,sha256=GDz1ZoC4lB0kwjERpzqc_OdA_awYVso2aBnUH-ErW_w,1070
|
|
3
|
+
fusesell_local/__init__.py,sha256=Q6pGa5zMldeiz9JEKEbO3rcIJphGt8qmZBl03G7wMMM,966
|
|
4
|
+
fusesell_local/api.py,sha256=SABxRr0TsB1Xfhl_OlL2IiMKYLG0jxbsaBKVNsMh-Qo,10972
|
|
5
5
|
fusesell_local/cli.py,sha256=MYnVxuEf5KTR4VcO3sc-VtP9NkWlSixJsYfOWST2Ds0,65859
|
|
6
6
|
fusesell_local/pipeline.py,sha256=RMF_kgwNEc1ka8-CDJyzIOTSo8PGtR_zPKAgRevhlNo,39913
|
|
7
7
|
fusesell_local/config/__init__.py,sha256=0ErO7QiSDqKn-LHcjIRdLZzh5QaRTkRsIlwfgpkkDz8,209
|
|
@@ -15,21 +15,21 @@ fusesell_local/stages/follow_up.py,sha256=H9Xek6EoIbHrerQvGTRswXDNFH6zq71DcRxxj0
|
|
|
15
15
|
fusesell_local/stages/initial_outreach.py,sha256=98KMaGP_aFkCV4K8j8HgURmNEgbVTYZSvXfLOlXX3Mc,127216
|
|
16
16
|
fusesell_local/stages/lead_scoring.py,sha256=ir3l849eMGrGLf0OYUcmA1F3FwyYhAplS4niU3R2GRY,60658
|
|
17
17
|
fusesell_local/tests/conftest.py,sha256=TWUtlP6cNPVOYkTPz-j9BzS_KnXdPWy8D-ObPLHvXYs,366
|
|
18
|
-
fusesell_local/tests/test_api.py,sha256=
|
|
18
|
+
fusesell_local/tests/test_api.py,sha256=vXlNaIDxqTTIFHRHs5zdUAPrxEleyoNxVOeyGuNgoQo,2304
|
|
19
19
|
fusesell_local/tests/test_cli.py,sha256=iNgU8nDlVrcQM5MpBUTIJ5q3oh2-jgX77hJeaqBxToM,1007
|
|
20
20
|
fusesell_local/tests/test_data_manager_products.py,sha256=g8EUSxTqdg18VifzhuOtDDywiMYzwOWFADny5Vntc28,4691
|
|
21
21
|
fusesell_local/tests/test_data_manager_sales_process.py,sha256=NbwxQ9oBKCZfrkRQYxzHHQ08F7epqPUsyeGz_vm3kf8,4447
|
|
22
22
|
fusesell_local/tests/test_data_manager_teams.py,sha256=kjk4V4r9ja4EVREIiQMxkuZd470SSwRHJAvpHln9KO4,4578
|
|
23
|
-
fusesell_local/utils/__init__.py,sha256=
|
|
23
|
+
fusesell_local/utils/__init__.py,sha256=onCrMaFAr_RepjhilcATvo2VBsSwglbaDSS7M5UVNQ0,374
|
|
24
24
|
fusesell_local/utils/birthday_email_manager.py,sha256=NKLoUyzPedyhewZPma21SOoU8p9wPquehloer7TRA9U,20478
|
|
25
25
|
fusesell_local/utils/data_manager.py,sha256=FHW9nvLXDgf-HYNFwxZlegZp0OgB3altszW6INIgyLM,188910
|
|
26
26
|
fusesell_local/utils/event_scheduler.py,sha256=TDk1v19cNgLhn2aJriQfpvZnwBcRpOWyHLDvkefW110,39834
|
|
27
|
-
fusesell_local/utils/llm_client.py,sha256=
|
|
27
|
+
fusesell_local/utils/llm_client.py,sha256=eNfbZBcyawBvZGLDPuyeectwBUVi5fjeD5GeLkEqWXI,12271
|
|
28
28
|
fusesell_local/utils/logger.py,sha256=sWlV8Tjyz_Z8J4zXKOnNalh8_iD6ytfrwPZpD-wcEOs,6259
|
|
29
29
|
fusesell_local/utils/timezone_detector.py,sha256=0cAE4c8ZXqCA8AvxRKm6PrFKmAmsbq3HOHR6w-mW3KQ,39997
|
|
30
30
|
fusesell_local/utils/validators.py,sha256=Z1VzeoxFsnuzlIA_ZaMWoy-0Cgyqupd47kIdljlMDbs,15438
|
|
31
|
-
fusesell-1.3.
|
|
32
|
-
fusesell-1.3.
|
|
33
|
-
fusesell-1.3.
|
|
34
|
-
fusesell-1.3.
|
|
35
|
-
fusesell-1.3.
|
|
31
|
+
fusesell-1.3.3.dist-info/METADATA,sha256=KtGJ5IyUrdyBtlS2QwMsspFdzf61sJMSxzmZY-TO2t0,35074
|
|
32
|
+
fusesell-1.3.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
33
|
+
fusesell-1.3.3.dist-info/entry_points.txt,sha256=Vqek7tbiX7iF4rQkCRBZvT5WrB0HUduqKTsI2ZjhsXo,53
|
|
34
|
+
fusesell-1.3.3.dist-info/top_level.txt,sha256=VP9y1K6DEq6gNq2UgLd7ChujxViF6OzeAVCK7IUBXPA,24
|
|
35
|
+
fusesell-1.3.3.dist-info/RECORD,,
|
fusesell_local/__init__.py
CHANGED
fusesell_local/api.py
CHANGED
|
@@ -16,6 +16,7 @@ from typing import Any, Callable, Dict, Mapping, MutableMapping, Optional, Seque
|
|
|
16
16
|
from .pipeline import FuseSellPipeline
|
|
17
17
|
from .utils.logger import setup_logging as _setup_logging
|
|
18
18
|
from .utils.validators import InputValidator
|
|
19
|
+
from .utils.llm_client import normalize_llm_base_url
|
|
19
20
|
|
|
20
21
|
|
|
21
22
|
class ConfigValidationError(ValueError):
|
|
@@ -138,6 +139,8 @@ def build_config(options: OptionsType) -> Dict[str, Any]:
|
|
|
138
139
|
"dry_run": _coerce_bool(_get("dry_run")),
|
|
139
140
|
}
|
|
140
141
|
|
|
142
|
+
config["llm_base_url"] = normalize_llm_base_url(config.get("llm_base_url"))
|
|
143
|
+
|
|
141
144
|
return config
|
|
142
145
|
|
|
143
146
|
|
|
@@ -338,4 +341,3 @@ def execute_pipeline(
|
|
|
338
341
|
}
|
|
339
342
|
|
|
340
343
|
return run_pipeline(config)
|
|
341
|
-
|
fusesell_local/tests/test_api.py
CHANGED
|
@@ -24,20 +24,37 @@ def base_options(**overrides):
|
|
|
24
24
|
return options
|
|
25
25
|
|
|
26
26
|
|
|
27
|
-
def test_build_config_generates_defaults():
|
|
28
|
-
config = build_config(base_options())
|
|
29
|
-
|
|
30
|
-
assert config["execution_id"].startswith("fusesell_")
|
|
31
|
-
assert config["output_format"] == "json"
|
|
32
|
-
assert config["skip_stages"] == []
|
|
33
|
-
assert config["send_immediately"] is False
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
def
|
|
37
|
-
config = build_config(
|
|
38
|
-
base_options(
|
|
39
|
-
|
|
40
|
-
|
|
27
|
+
def test_build_config_generates_defaults():
|
|
28
|
+
config = build_config(base_options())
|
|
29
|
+
|
|
30
|
+
assert config["execution_id"].startswith("fusesell_")
|
|
31
|
+
assert config["output_format"] == "json"
|
|
32
|
+
assert config["skip_stages"] == []
|
|
33
|
+
assert config["send_immediately"] is False
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def test_build_config_normalizes_llm_base_url():
|
|
37
|
+
config = build_config(
|
|
38
|
+
base_options(
|
|
39
|
+
llm_base_url="https://custom-llm.example.com",
|
|
40
|
+
)
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
assert config["llm_base_url"] == "https://custom-llm.example.com/v1"
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def test_build_config_preserves_azure_base_url():
|
|
47
|
+
azure_url = "https://rtx-openai.openai.azure.com/openai/deployments/gpt4"
|
|
48
|
+
config = build_config(base_options(llm_base_url=azure_url))
|
|
49
|
+
|
|
50
|
+
assert config["llm_base_url"] == azure_url
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def test_validate_config_detects_missing_sources():
|
|
54
|
+
config = build_config(
|
|
55
|
+
base_options(
|
|
56
|
+
input_description="",
|
|
57
|
+
input_website="",
|
|
41
58
|
input_freetext="",
|
|
42
59
|
)
|
|
43
60
|
)
|
fusesell_local/utils/__init__.py
CHANGED
|
@@ -2,14 +2,15 @@
|
|
|
2
2
|
FuseSell Utilities - Common utilities and helper functions
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
-
from .data_manager import LocalDataManager
|
|
6
|
-
from .llm_client import LLMClient
|
|
7
|
-
from .validators import InputValidator
|
|
8
|
-
from .logger import setup_logging
|
|
9
|
-
|
|
10
|
-
__all__ = [
|
|
11
|
-
'LocalDataManager',
|
|
12
|
-
'LLMClient',
|
|
13
|
-
'
|
|
14
|
-
'
|
|
15
|
-
|
|
5
|
+
from .data_manager import LocalDataManager
|
|
6
|
+
from .llm_client import LLMClient, normalize_llm_base_url
|
|
7
|
+
from .validators import InputValidator
|
|
8
|
+
from .logger import setup_logging
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
'LocalDataManager',
|
|
12
|
+
'LLMClient',
|
|
13
|
+
'normalize_llm_base_url',
|
|
14
|
+
'InputValidator',
|
|
15
|
+
'setup_logging'
|
|
16
|
+
]
|
|
@@ -2,17 +2,74 @@
|
|
|
2
2
|
LLM Client for OpenAI API integration
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
-
try:
|
|
6
|
-
import openai
|
|
7
|
-
OPENAI_AVAILABLE = True
|
|
8
|
-
except ImportError:
|
|
9
|
-
OPENAI_AVAILABLE = False
|
|
10
|
-
openai = None
|
|
11
|
-
|
|
12
|
-
from typing import Dict, Any, List, Optional
|
|
13
|
-
import logging
|
|
14
|
-
import time
|
|
15
|
-
import json
|
|
5
|
+
try:
|
|
6
|
+
import openai
|
|
7
|
+
OPENAI_AVAILABLE = True
|
|
8
|
+
except ImportError:
|
|
9
|
+
OPENAI_AVAILABLE = False
|
|
10
|
+
openai = None
|
|
11
|
+
|
|
12
|
+
from typing import Dict, Any, List, Optional
|
|
13
|
+
import logging
|
|
14
|
+
import time
|
|
15
|
+
import json
|
|
16
|
+
from urllib.parse import urlsplit, urlunsplit
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def normalize_llm_base_url(base_url: Optional[str], provider: Optional[str] = None) -> Optional[str]:
|
|
20
|
+
"""
|
|
21
|
+
Ensure LLM base URLs point to the OpenAI-compatible /v1 endpoint unless they already target
|
|
22
|
+
Azure deployment paths that do not expect the suffix.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
base_url: User-provided base URL.
|
|
26
|
+
provider: Optional provider hint (e.g., 'azure-openai').
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
Normalized base URL with `/v1` appended when needed, or ``None`` if input is empty.
|
|
30
|
+
"""
|
|
31
|
+
if not base_url:
|
|
32
|
+
return None
|
|
33
|
+
|
|
34
|
+
normalized = base_url.strip()
|
|
35
|
+
if not normalized:
|
|
36
|
+
return None
|
|
37
|
+
|
|
38
|
+
provider_hint = (provider or "").lower()
|
|
39
|
+
if provider_hint.startswith("azure") or "openai.azure.com" in normalized.lower():
|
|
40
|
+
return normalized.rstrip("/")
|
|
41
|
+
|
|
42
|
+
try:
|
|
43
|
+
parsed = urlsplit(normalized)
|
|
44
|
+
except ValueError:
|
|
45
|
+
parsed = None
|
|
46
|
+
|
|
47
|
+
if parsed and parsed.scheme and parsed.netloc:
|
|
48
|
+
path = parsed.path.rstrip("/")
|
|
49
|
+
segments = [segment for segment in path.split("/") if segment]
|
|
50
|
+
|
|
51
|
+
if not segments:
|
|
52
|
+
new_path = "/v1"
|
|
53
|
+
elif segments[-1] in {"v1", "v1beta"} or "v1" in segments or "deployments" in segments:
|
|
54
|
+
new_path = "/" + "/".join(segments)
|
|
55
|
+
else:
|
|
56
|
+
new_path = f"{path}/v1" if path else "/v1"
|
|
57
|
+
|
|
58
|
+
rebuilt = urlunsplit(
|
|
59
|
+
(
|
|
60
|
+
parsed.scheme,
|
|
61
|
+
parsed.netloc,
|
|
62
|
+
new_path,
|
|
63
|
+
parsed.query,
|
|
64
|
+
parsed.fragment,
|
|
65
|
+
)
|
|
66
|
+
)
|
|
67
|
+
return rebuilt.rstrip("/")
|
|
68
|
+
|
|
69
|
+
stripped = normalized.rstrip("/")
|
|
70
|
+
if stripped.endswith("/v1") or "/v1/" in stripped:
|
|
71
|
+
return stripped
|
|
72
|
+
return f"{stripped}/v1"
|
|
16
73
|
|
|
17
74
|
|
|
18
75
|
class LLMClient:
|
|
@@ -35,13 +92,15 @@ class LLMClient:
|
|
|
35
92
|
|
|
36
93
|
self.api_key = api_key
|
|
37
94
|
self.model = model
|
|
38
|
-
self.logger = logging.getLogger("fusesell.llm_client")
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
self.client = openai.OpenAI(api_key=api_key)
|
|
95
|
+
self.logger = logging.getLogger("fusesell.llm_client")
|
|
96
|
+
|
|
97
|
+
normalized_base_url = normalize_llm_base_url(base_url)
|
|
98
|
+
|
|
99
|
+
# Initialize OpenAI client
|
|
100
|
+
if normalized_base_url:
|
|
101
|
+
self.client = openai.OpenAI(api_key=api_key, base_url=normalized_base_url)
|
|
102
|
+
else:
|
|
103
|
+
self.client = openai.OpenAI(api_key=api_key)
|
|
45
104
|
|
|
46
105
|
def chat_completion(
|
|
47
106
|
self,
|
|
@@ -280,4 +339,4 @@ Response:"""
|
|
|
280
339
|
return len(response) > 0
|
|
281
340
|
except Exception as e:
|
|
282
341
|
self.logger.error(f"API key validation failed: {str(e)}")
|
|
283
|
-
return False
|
|
342
|
+
return False
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|