fusesell 1.3.2__tar.gz → 1.3.4__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.
Potentially problematic release.
This version of fusesell might be problematic. Click here for more details.
- {fusesell-1.3.2 → fusesell-1.3.4}/CHANGELOG.md +15 -5
- {fusesell-1.3.2/fusesell.egg-info → fusesell-1.3.4}/PKG-INFO +1 -2
- {fusesell-1.3.2 → fusesell-1.3.4/fusesell.egg-info}/PKG-INFO +1 -2
- {fusesell-1.3.2 → fusesell-1.3.4}/fusesell.egg-info/requires.txt +0 -1
- {fusesell-1.3.2 → fusesell-1.3.4}/fusesell_local/__init__.py +1 -1
- {fusesell-1.3.2 → fusesell-1.3.4}/fusesell_local/api.py +3 -1
- {fusesell-1.3.2 → fusesell-1.3.4}/fusesell_local/tests/test_api.py +31 -14
- {fusesell-1.3.2 → fusesell-1.3.4}/fusesell_local/utils/__init__.py +12 -11
- {fusesell-1.3.2 → fusesell-1.3.4}/fusesell_local/utils/event_scheduler.py +5 -5
- {fusesell-1.3.2 → fusesell-1.3.4}/fusesell_local/utils/llm_client.py +78 -19
- {fusesell-1.3.2 → fusesell-1.3.4}/pyproject.toml +14 -16
- {fusesell-1.3.2 → fusesell-1.3.4}/LICENSE +0 -0
- {fusesell-1.3.2 → fusesell-1.3.4}/MANIFEST.in +0 -0
- {fusesell-1.3.2 → fusesell-1.3.4}/README.md +0 -0
- {fusesell-1.3.2 → fusesell-1.3.4}/fusesell.egg-info/SOURCES.txt +0 -0
- {fusesell-1.3.2 → fusesell-1.3.4}/fusesell.egg-info/dependency_links.txt +0 -0
- {fusesell-1.3.2 → fusesell-1.3.4}/fusesell.egg-info/entry_points.txt +0 -0
- {fusesell-1.3.2 → fusesell-1.3.4}/fusesell.egg-info/top_level.txt +0 -0
- {fusesell-1.3.2 → fusesell-1.3.4}/fusesell.py +0 -0
- {fusesell-1.3.2 → fusesell-1.3.4}/fusesell_local/cli.py +0 -0
- {fusesell-1.3.2 → fusesell-1.3.4}/fusesell_local/config/__init__.py +0 -0
- {fusesell-1.3.2 → fusesell-1.3.4}/fusesell_local/config/prompts.py +0 -0
- {fusesell-1.3.2 → fusesell-1.3.4}/fusesell_local/config/settings.py +0 -0
- {fusesell-1.3.2 → fusesell-1.3.4}/fusesell_local/pipeline.py +0 -0
- {fusesell-1.3.2 → fusesell-1.3.4}/fusesell_local/stages/__init__.py +0 -0
- {fusesell-1.3.2 → fusesell-1.3.4}/fusesell_local/stages/base_stage.py +0 -0
- {fusesell-1.3.2 → fusesell-1.3.4}/fusesell_local/stages/data_acquisition.py +0 -0
- {fusesell-1.3.2 → fusesell-1.3.4}/fusesell_local/stages/data_preparation.py +0 -0
- {fusesell-1.3.2 → fusesell-1.3.4}/fusesell_local/stages/follow_up.py +0 -0
- {fusesell-1.3.2 → fusesell-1.3.4}/fusesell_local/stages/initial_outreach.py +0 -0
- {fusesell-1.3.2 → fusesell-1.3.4}/fusesell_local/stages/lead_scoring.py +0 -0
- {fusesell-1.3.2 → fusesell-1.3.4}/fusesell_local/tests/conftest.py +0 -0
- {fusesell-1.3.2 → fusesell-1.3.4}/fusesell_local/tests/test_cli.py +0 -0
- {fusesell-1.3.2 → fusesell-1.3.4}/fusesell_local/tests/test_data_manager_products.py +0 -0
- {fusesell-1.3.2 → fusesell-1.3.4}/fusesell_local/tests/test_data_manager_sales_process.py +0 -0
- {fusesell-1.3.2 → fusesell-1.3.4}/fusesell_local/tests/test_data_manager_teams.py +0 -0
- {fusesell-1.3.2 → fusesell-1.3.4}/fusesell_local/utils/birthday_email_manager.py +0 -0
- {fusesell-1.3.2 → fusesell-1.3.4}/fusesell_local/utils/data_manager.py +0 -0
- {fusesell-1.3.2 → fusesell-1.3.4}/fusesell_local/utils/logger.py +0 -0
- {fusesell-1.3.2 → fusesell-1.3.4}/fusesell_local/utils/timezone_detector.py +0 -0
- {fusesell-1.3.2 → fusesell-1.3.4}/fusesell_local/utils/validators.py +0 -0
- {fusesell-1.3.2 → fusesell-1.3.4}/requirements.txt +0 -0
- {fusesell-1.3.2 → fusesell-1.3.4}/setup.cfg +0 -0
|
@@ -1,10 +1,20 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
All notable changes to FuseSell Local will be documented in this file.
|
|
4
|
-
|
|
5
|
-
# [1.3.
|
|
6
|
-
|
|
7
|
-
### Changed
|
|
3
|
+
All notable changes to FuseSell Local will be documented in this file.
|
|
4
|
+
|
|
5
|
+
# [1.3.3] - 2025-10-25
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
- Added a shared `normalize_llm_base_url` helper so every entry point (CLI, programmatic API, birthday scheduler, etc.) consistently appends `/v1` to OpenAI-style endpoints while leaving Azure deployment URLs untouched.
|
|
9
|
+
- Exported the helper for downstream consumers, enabling RealTimeX flows to import the canonical logic instead of maintaining their own copy.
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
- Resolved repeated `APIConnectionError` retries when custom LLM base URLs were missing `/v1`, restoring successful chat completion calls across all pipeline stages.
|
|
13
|
+
- Added regression coverage to ensure Azure endpoints remain unchanged while standard endpoints are normalized, guarding against future regressions.
|
|
14
|
+
|
|
15
|
+
# [1.3.2] - 2025-10-24
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
8
18
|
- Removed the deterministic fallback template so every initial-outreach draft now originates from the LLM path; when the model call fails the stage reports an error instead of emitting canned copy.
|
|
9
19
|
|
|
10
20
|
### Fixed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fusesell
|
|
3
|
-
Version: 1.3.
|
|
3
|
+
Version: 1.3.4
|
|
4
4
|
Summary: Local implementation of FuseSell AI sales automation pipeline
|
|
5
5
|
Author-email: FuseSell Team <team@fusesell.ai>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -22,7 +22,6 @@ License-File: LICENSE
|
|
|
22
22
|
Requires-Dist: openai>=1.0.0
|
|
23
23
|
Requires-Dist: requests>=2.25.0
|
|
24
24
|
Requires-Dist: beautifulsoup4>=4.9.0
|
|
25
|
-
Requires-Dist: lxml>=4.6.0
|
|
26
25
|
Requires-Dist: python-dateutil>=2.8.0
|
|
27
26
|
Requires-Dist: pytz>=2021.1
|
|
28
27
|
Provides-Extra: ocr
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fusesell
|
|
3
|
-
Version: 1.3.
|
|
3
|
+
Version: 1.3.4
|
|
4
4
|
Summary: Local implementation of FuseSell AI sales automation pipeline
|
|
5
5
|
Author-email: FuseSell Team <team@fusesell.ai>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -22,7 +22,6 @@ License-File: LICENSE
|
|
|
22
22
|
Requires-Dist: openai>=1.0.0
|
|
23
23
|
Requires-Dist: requests>=2.25.0
|
|
24
24
|
Requires-Dist: beautifulsoup4>=4.9.0
|
|
25
|
-
Requires-Dist: lxml>=4.6.0
|
|
26
25
|
Requires-Dist: python-dateutil>=2.8.0
|
|
27
26
|
Requires-Dist: pytz>=2021.1
|
|
28
27
|
Provides-Extra: ocr
|
|
@@ -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
|
-
|
|
@@ -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
|
)
|
|
@@ -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
|
+
]
|
|
@@ -183,17 +183,17 @@ class EventScheduler:
|
|
|
183
183
|
ISO 8601 formatted string.
|
|
184
184
|
"""
|
|
185
185
|
if isinstance(value, datetime):
|
|
186
|
-
return value.isoformat()
|
|
186
|
+
return value.replace(second=0, microsecond=0).isoformat()
|
|
187
187
|
if value is None:
|
|
188
|
-
return datetime.utcnow().isoformat()
|
|
188
|
+
return datetime.utcnow().replace(second=0, microsecond=0).isoformat()
|
|
189
189
|
|
|
190
190
|
value_str = str(value).strip()
|
|
191
191
|
if not value_str:
|
|
192
|
-
return datetime.utcnow().isoformat()
|
|
192
|
+
return datetime.utcnow().replace(second=0, microsecond=0).isoformat()
|
|
193
193
|
|
|
194
194
|
try:
|
|
195
195
|
parsed = datetime.fromisoformat(value_str)
|
|
196
|
-
return parsed.isoformat()
|
|
196
|
+
return parsed.replace(second=0, microsecond=0).isoformat()
|
|
197
197
|
except ValueError:
|
|
198
198
|
return value_str
|
|
199
199
|
|
|
@@ -283,7 +283,7 @@ class EventScheduler:
|
|
|
283
283
|
customextra.setdefault('recipient_name', recipient_name)
|
|
284
284
|
customextra.setdefault('draft_id', draft_id)
|
|
285
285
|
customextra.setdefault('customer_timezone', customer_timezone)
|
|
286
|
-
customextra.setdefault('scheduled_time_utc',
|
|
286
|
+
customextra.setdefault('scheduled_time_utc', self._format_datetime(send_time))
|
|
287
287
|
|
|
288
288
|
if team_id and 'team_id' not in customextra:
|
|
289
289
|
customextra['team_id'] = team_id
|
|
@@ -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
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "fusesell"
|
|
7
|
-
version = "1.3.
|
|
7
|
+
version = "1.3.4"
|
|
8
8
|
description = "Local implementation of FuseSell AI sales automation pipeline"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
@@ -23,14 +23,13 @@ classifiers = [
|
|
|
23
23
|
"Programming Language :: Python :: 3.10",
|
|
24
24
|
"Programming Language :: Python :: 3.11",
|
|
25
25
|
]
|
|
26
|
-
dependencies = [
|
|
27
|
-
"openai>=1.0.0",
|
|
28
|
-
"requests>=2.25.0",
|
|
29
|
-
"beautifulsoup4>=4.9.0",
|
|
30
|
-
"
|
|
31
|
-
"
|
|
32
|
-
|
|
33
|
-
]
|
|
26
|
+
dependencies = [
|
|
27
|
+
"openai>=1.0.0",
|
|
28
|
+
"requests>=2.25.0",
|
|
29
|
+
"beautifulsoup4>=4.9.0",
|
|
30
|
+
"python-dateutil>=2.8.0",
|
|
31
|
+
"pytz>=2021.1",
|
|
32
|
+
]
|
|
34
33
|
|
|
35
34
|
[project.optional-dependencies]
|
|
36
35
|
ocr = [
|
|
@@ -61,13 +60,12 @@ fusesell = "fusesell_local.cli:main"
|
|
|
61
60
|
|
|
62
61
|
[tool.setuptools]
|
|
63
62
|
include-package-data = true
|
|
64
|
-
packages = [
|
|
65
|
-
"fusesell_local",
|
|
66
|
-
"fusesell_local.config",
|
|
67
|
-
"fusesell_local.stages",
|
|
68
|
-
"fusesell_local.
|
|
69
|
-
|
|
70
|
-
]
|
|
63
|
+
packages = [
|
|
64
|
+
"fusesell_local",
|
|
65
|
+
"fusesell_local.config",
|
|
66
|
+
"fusesell_local.stages",
|
|
67
|
+
"fusesell_local.utils",
|
|
68
|
+
]
|
|
71
69
|
py-modules = ["fusesell"]
|
|
72
70
|
|
|
73
71
|
[tool.setuptools.package-data]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|