confamnode 0.1.0__tar.gz → 0.1.1__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.
@@ -18,3 +18,11 @@ __pycache__/
18
18
  dist/
19
19
  *.egg-info/
20
20
  .pytest_cache/
21
+
22
+ # Test files
23
+ test_chat.py
24
+ test_stream.py
25
+ exchange_rate_calc.py
26
+
27
+ # Other files
28
+ main.py
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: confamnode
3
- Version: 0.1.0
4
- Summary: The Nigerian AI inference SDK — access powerful AI models and pay in Naira
3
+ Version: 0.1.1
4
+ Summary: The Nigerian AI inference gateway — access powerful AI models
5
5
  Project-URL: Repository, https://github.com/joteqthefirst/confamnode-sdk
6
6
  Project-URL: Bug Tracker, https://github.com/joteqthefirst/confamnode-sdk/issues
7
7
  Author-email: JoTeq the First <joteqthefirst@gmail.com>
@@ -24,7 +24,7 @@ Description-Content-Type: text/markdown
24
24
 
25
25
  # ConfamNode
26
26
 
27
- The Nigerian AI inference SDK — access powerful AI models.
27
+ The Nigerian AI inference gateway — access powerful AI models.
28
28
 
29
29
  Built by **JoTeq the First**
30
30
 
@@ -32,10 +32,44 @@ Built by **JoTeq the First**
32
32
 
33
33
  ## Installation
34
34
 
35
+ ### Using pip
35
36
  ```bash
36
37
  pip install confamnode
37
38
  ```
38
39
 
40
+ ### Using uv
41
+ ```bash
42
+ uv add confamnode
43
+ ```
44
+
45
+ ### Using virtualenv
46
+ ```bash
47
+ # Create virtual environment
48
+ python -m venv .venv
49
+
50
+ # Activate — Linux/Mac
51
+ source .venv/bin/activate
52
+
53
+ # Activate — Windows
54
+ .venv\Scripts\activate
55
+
56
+ # Install
57
+ pip install confamnode
58
+ ```
59
+
60
+ ### Using uv with virtual environment
61
+ ```bash
62
+ # Create project with uv
63
+ uv init my-project
64
+ cd my-project
65
+
66
+ # Add confamnode
67
+ uv add confamnode
68
+
69
+ # Run your script
70
+ uv run python main.py
71
+ ```
72
+
39
73
  ---
40
74
 
41
75
  ## Quick Start
@@ -45,12 +79,12 @@ from confamnode import ConfamNode
45
79
 
46
80
  client = ConfamNode(api_key="confam-sk-xxx")
47
81
 
48
- response = client.gist(
82
+ ansa = client.gist(
49
83
  model="confam-speed",
50
84
  messages="How far?"
51
85
  )
52
86
 
53
- print(response.choices[0].message.content)
87
+ print(ansa.choices[0].message.content)
54
88
  ```
55
89
 
56
90
  ---
@@ -58,12 +92,12 @@ print(response.choices[0].message.content)
58
92
  ## Streaming
59
93
 
60
94
  ```python
61
- for chunk in client.gist(
95
+ for yarn in client.gist(
62
96
  model="confam-speed",
63
97
  messages="How far?",
64
98
  stream=True
65
99
  ):
66
- print(chunk.choices[0].delta.content, end="")
100
+ print(yarn.choices[0].delta.content, end="")
67
101
  ```
68
102
 
69
103
  ---
@@ -98,23 +132,23 @@ client = ConfamNode()
98
132
  from confamnode import ConfamAuthError, ConfamRateLimitError, ConfamModelError
99
133
 
100
134
  try:
101
- response = client.gist(
135
+ ansa = client.gist(
102
136
  model="confam-speed",
103
137
  messages="How far?"
104
138
  )
105
139
  except ConfamAuthError:
106
- print("Check your API key")
140
+ print("You sure say na the correct API Key be that")
107
141
  except ConfamRateLimitError:
108
- print("You don reach your limit. To upgrade your plan, contact us at confamnode@gmail.com")
142
+ print("You don reach your limit. Contact us at confamnode@gmail.com make we upgrade your plan")
109
143
  except ConfamModelError:
110
- print("Invalid model name")
144
+ print("This model name no dey valid")
111
145
  ```
112
146
 
113
147
  ---
114
148
 
115
149
  ## Private Deployment
116
150
 
117
- Need NDPA-compliant private AI on your own infrastructure? JoTeq the First offers on-premise deployment on Jetson devices and GPUs, RAG pipelines, fine-tuning, and dedicated hosted models.
151
+ Need NDPA-compliant private AI on your own infrastructure? JoTeq the First offers on-premise deployment on Jetson devices and GPUs, RAG pipelines, fine-tuning, eevaluation, monitoring, and dedicated hosted models.
118
152
 
119
153
  Contact us at **joteqthefirst@gmail.com**
120
154
 
@@ -1,6 +1,6 @@
1
1
  # ConfamNode
2
2
 
3
- The Nigerian AI inference SDK — access powerful AI models.
3
+ The Nigerian AI inference gateway — access powerful AI models.
4
4
 
5
5
  Built by **JoTeq the First**
6
6
 
@@ -8,10 +8,44 @@ Built by **JoTeq the First**
8
8
 
9
9
  ## Installation
10
10
 
11
+ ### Using pip
11
12
  ```bash
12
13
  pip install confamnode
13
14
  ```
14
15
 
16
+ ### Using uv
17
+ ```bash
18
+ uv add confamnode
19
+ ```
20
+
21
+ ### Using virtualenv
22
+ ```bash
23
+ # Create virtual environment
24
+ python -m venv .venv
25
+
26
+ # Activate — Linux/Mac
27
+ source .venv/bin/activate
28
+
29
+ # Activate — Windows
30
+ .venv\Scripts\activate
31
+
32
+ # Install
33
+ pip install confamnode
34
+ ```
35
+
36
+ ### Using uv with virtual environment
37
+ ```bash
38
+ # Create project with uv
39
+ uv init my-project
40
+ cd my-project
41
+
42
+ # Add confamnode
43
+ uv add confamnode
44
+
45
+ # Run your script
46
+ uv run python main.py
47
+ ```
48
+
15
49
  ---
16
50
 
17
51
  ## Quick Start
@@ -21,12 +55,12 @@ from confamnode import ConfamNode
21
55
 
22
56
  client = ConfamNode(api_key="confam-sk-xxx")
23
57
 
24
- response = client.gist(
58
+ ansa = client.gist(
25
59
  model="confam-speed",
26
60
  messages="How far?"
27
61
  )
28
62
 
29
- print(response.choices[0].message.content)
63
+ print(ansa.choices[0].message.content)
30
64
  ```
31
65
 
32
66
  ---
@@ -34,12 +68,12 @@ print(response.choices[0].message.content)
34
68
  ## Streaming
35
69
 
36
70
  ```python
37
- for chunk in client.gist(
71
+ for yarn in client.gist(
38
72
  model="confam-speed",
39
73
  messages="How far?",
40
74
  stream=True
41
75
  ):
42
- print(chunk.choices[0].delta.content, end="")
76
+ print(yarn.choices[0].delta.content, end="")
43
77
  ```
44
78
 
45
79
  ---
@@ -74,23 +108,23 @@ client = ConfamNode()
74
108
  from confamnode import ConfamAuthError, ConfamRateLimitError, ConfamModelError
75
109
 
76
110
  try:
77
- response = client.gist(
111
+ ansa = client.gist(
78
112
  model="confam-speed",
79
113
  messages="How far?"
80
114
  )
81
115
  except ConfamAuthError:
82
- print("Check your API key")
116
+ print("You sure say na the correct API Key be that")
83
117
  except ConfamRateLimitError:
84
- print("You don reach your limit. To upgrade your plan, contact us at confamnode@gmail.com")
118
+ print("You don reach your limit. Contact us at confamnode@gmail.com make we upgrade your plan")
85
119
  except ConfamModelError:
86
- print("Invalid model name")
120
+ print("This model name no dey valid")
87
121
  ```
88
122
 
89
123
  ---
90
124
 
91
125
  ## Private Deployment
92
126
 
93
- Need NDPA-compliant private AI on your own infrastructure? JoTeq the First offers on-premise deployment on Jetson devices and GPUs, RAG pipelines, fine-tuning, and dedicated hosted models.
127
+ Need NDPA-compliant private AI on your own infrastructure? JoTeq the First offers on-premise deployment on Jetson devices and GPUs, RAG pipelines, fine-tuning, eevaluation, monitoring, and dedicated hosted models.
94
128
 
95
129
  Contact us at **joteqthefirst@gmail.com**
96
130
 
@@ -1,20 +1,25 @@
1
- from confamnode.client import ConfamNode
1
+ from confamnode.client import ConfamNode, ConfamStream
2
2
  from confamnode.exceptions import (
3
3
  ConfamNodeError,
4
4
  ConfamAuthError,
5
5
  ConfamRateLimitError,
6
6
  ConfamModelError,
7
7
  )
8
+ from confamnode.ansa import Ansa, Usage, Cost
8
9
  from confamnode import models
9
10
 
10
- __version__ = "0.1.0"
11
+ __version__ = "0.1.1"
11
12
 
12
13
  __all__ = [
13
14
  "ConfamNode",
15
+ "ConfamStream",
14
16
  "ConfamNodeError",
15
17
  "ConfamAuthError",
16
18
  "ConfamRateLimitError",
17
19
  "ConfamModelError",
20
+ "Ansa",
21
+ "Usage",
22
+ "Cost",
18
23
  "models",
19
24
  "__version__"
20
25
  ]
@@ -0,0 +1,31 @@
1
+ import uuid
2
+ from dataclasses import dataclass, field
3
+
4
+
5
+ @dataclass
6
+ class Usage:
7
+ prompt_tokens: int
8
+ completion_tokens: int
9
+ total_tokens: int
10
+
11
+
12
+ @dataclass
13
+ class Cost:
14
+ naira: float
15
+ naira_input: float = 0.0
16
+ naira_output: float = 0.0
17
+ dollars: float | None = None
18
+
19
+
20
+ @dataclass
21
+ class Ansa:
22
+ text: str
23
+ model: str
24
+ usage: Usage
25
+ cost: Cost
26
+ finish_reason: str
27
+ raw: object
28
+ reasoning: str | None = None
29
+ tools: list = field(default_factory=list)
30
+ citations: list = field(default_factory=list)
31
+ id: str = field(default_factory=lambda: f"confam-{uuid.uuid4()}")
@@ -0,0 +1,187 @@
1
+ import os
2
+ import litellm
3
+
4
+ from typing import Union, List, Dict
5
+
6
+ from confamnode import models
7
+ from confamnode.ansa import Ansa, Usage, Cost
8
+ from confamnode.exceptions import ConfamAuthError, ConfamModelError
9
+
10
+ VALID_MODELS = [
11
+ models.LITE,
12
+ models.SPEED,
13
+ models.REASONING,
14
+
15
+ models.INTELLIGENCE,
16
+ models.DEEP_REASONING,
17
+ models.CODE,
18
+ models.CODE_PRO,
19
+ models.VISION,
20
+ models.AUDIO,
21
+ models.TTS,
22
+
23
+ models.NANO,
24
+
25
+ models.EMBED_TEXT,
26
+ models.EMBED_MULTIMODAL,
27
+ models.EMBED_MULTIMODAL_2,
28
+
29
+ # Paid embeddings
30
+ models.EMBED_TEXT_PRO,
31
+ models.EMBED_MULTIMODAL_PRO,
32
+ models.EMBED_MULTILINGUAL,
33
+ models.EMBED_SMALL,
34
+ models.EMBED_0_6B,
35
+ models.EMBED_TEXT_LOCAL,
36
+
37
+ # Rerank
38
+ models.RERANK,
39
+ models.RERANK_FAST,
40
+ ]
41
+
42
+ DEFAULT_BASE_URL = "https://api.confamnode.com/v1"
43
+ DEFAULT_USD_TO_NAIRA = 1400.0
44
+
45
+ class ConfamNode:
46
+ def __init__(
47
+ self,
48
+ api_key: str = None,
49
+ base_url: str = None
50
+ ):
51
+ # Pick up from environment if not provided
52
+ api_key = api_key or os.environ.get("CONFAMNODE_API_KEY")
53
+
54
+ if not api_key:
55
+ raise ValueError("api_key is required")
56
+
57
+ if not api_key.startswith("confam-sk-"):
58
+ raise ConfamAuthError()
59
+
60
+ self.api_key = api_key
61
+ self.litellm_key = api_key.removeprefix("confam-")
62
+ self.base_url = base_url or DEFAULT_BASE_URL
63
+
64
+ def gist(
65
+ self,
66
+ model: str,
67
+ messages: Union[str, List[Dict[str, str]]],
68
+ **kwargs
69
+ ) -> object:
70
+ if model not in VALID_MODELS:
71
+ raise ConfamModelError(model)
72
+
73
+ if isinstance(messages, str):
74
+ messages = [{"role": "user", "content": messages}]
75
+ elif not isinstance(messages, list):
76
+ raise ValueError("messages must be a string or list")
77
+
78
+ raw = litellm.completion(
79
+ model=f"openai/{model}",
80
+ messages=messages,
81
+ api_key=self.litellm_key,
82
+ base_url=self.base_url,
83
+ **kwargs
84
+ )
85
+
86
+ if kwargs.get("stream", False):
87
+ return ConfamStream(raw, model)
88
+
89
+ hidden = getattr(raw, "_hidden_params", {})
90
+ naira_cost = float(hidden.get("x_confam_naira_cost", 0.0))
91
+ naira_input = float(hidden.get("x_confam_naira_input", 0.0))
92
+ naira_output = float(hidden.get("x_confam_naira_output", 0.0))
93
+ usd_cost = hidden.get("x_confam_usd_cost", None)
94
+ if usd_cost is not None:
95
+ usd_cost = float(usd_cost)
96
+
97
+ # Extract text
98
+ text = raw.choices[0].message.content or ""
99
+
100
+ # Extract reasoning if present
101
+ reasoning = getattr(raw.choices[0].message, "reasoning_content", None)
102
+
103
+ # Extract tool calls if present
104
+ tools = []
105
+ if hasattr(raw.choices[0].message, "tool_calls") and raw.choices[0].message.tool_calls:
106
+ tools = raw.choices[0].message.tool_calls
107
+
108
+ # Extract citations
109
+ citations = []
110
+ if hasattr(raw.choices[0].message, "citations") and raw.choices[0].message.citations:
111
+ citations = raw.choices[0].message.citations
112
+
113
+ # Extract usage
114
+ usage = Usage(
115
+ prompt_tokens=raw.usage.prompt_tokens,
116
+ completion_tokens=raw.usage.completion_tokens,
117
+ total_tokens=raw.usage.total_tokens,
118
+ )
119
+
120
+ cost = Cost(
121
+ naira=naira_cost,
122
+ naira_input=naira_input,
123
+ naira_output=naira_output,
124
+ dollars=usd_cost
125
+ )
126
+
127
+ return Ansa(
128
+ text=text,
129
+ model=model,
130
+ reasoning=reasoning,
131
+ tools=tools,
132
+ citations=citations,
133
+ usage=usage,
134
+ cost=cost,
135
+ finish_reason=raw.choices[0].finish_reason,
136
+ raw=raw,
137
+ )
138
+
139
+
140
+ class ConfamStream:
141
+ def __init__(self, raw_stream, model: str):
142
+ self._raw_stream = raw_stream
143
+ self._model = model
144
+ self._chunks = []
145
+ self._ansa = None
146
+
147
+ def __iter__(self):
148
+ for yarn in self._raw_stream:
149
+ self._chunks.append(yarn)
150
+ yield yarn
151
+ # Build Ansa after stream completes
152
+ self._ansa = self._build_ansa()
153
+
154
+ def get_ansa(self) -> Ansa:
155
+ if self._ansa is None:
156
+ raise RuntimeError("Stream not complete yet. Iterate through all chunks first.")
157
+ return self._ansa
158
+
159
+ def _build_ansa(self) -> Ansa:
160
+ # Collect text from all chunks
161
+ text = "".join([
162
+ c.choices[0].delta.content or ""
163
+ for c in self._chunks
164
+ if c.choices[0].delta.content
165
+ ])
166
+
167
+ # Get finish reason from last chunk
168
+ finish_reason = self._chunks[-1].choices[0].finish_reason if self._chunks else "stop"
169
+
170
+ # Usage — only available in last chunk for some providers
171
+ last_chunk = self._chunks[-1] if self._chunks else None
172
+ usage = Usage(
173
+ prompt_tokens=getattr(getattr(last_chunk, "usage", None), "prompt_tokens", 0),
174
+ completion_tokens=getattr(getattr(last_chunk, "usage", None), "completion_tokens", 0),
175
+ total_tokens=getattr(getattr(last_chunk, "usage", None), "total_tokens", 0),
176
+ )
177
+
178
+ cost = Cost(naira=0.0)
179
+
180
+ return Ansa(
181
+ text=text,
182
+ model=self._model,
183
+ usage=usage,
184
+ cost=cost,
185
+ finish_reason=finish_reason,
186
+ raw=self._chunks,
187
+ )
@@ -0,0 +1,45 @@
1
+ # =========================================================================
2
+ # 🆓 FREE TIER — CHAT
3
+ # =========================================================================
4
+ LITE = "confam-lite"
5
+ SPEED = "confam-speed"
6
+ REASONING = "confam-reasoning"
7
+
8
+ # =========================================================================
9
+ # 💎 PAID TIER — CHAT
10
+ # =========================================================================
11
+ INTELLIGENCE = "confam-intelligence"
12
+ DEEP_REASONING = "confam-deep-reasoning"
13
+ CODE = "confam-code"
14
+ CODE_PRO = "confam-code-pro"
15
+ VISION = "confam-vision"
16
+ AUDIO = "confam-audio"
17
+ TTS = "confam-tts"
18
+
19
+ # =========================================================================
20
+ # 🛠️ LOCAL PAID (NDPA Compliant)
21
+ # =========================================================================
22
+ NANO = "confam-nano"
23
+
24
+ # =========================================================================
25
+ # 📦 FREE EMBEDDINGS
26
+ # =========================================================================
27
+ EMBED_TEXT = "confam-embed-text-1"
28
+ EMBED_MULTIMODAL = "confam-embed-multimodal-1"
29
+ EMBED_MULTIMODAL_2 = "confam-embed-multimodal-2"
30
+
31
+ # =========================================================================
32
+ # 💎 PAID EMBEDDINGS
33
+ # =========================================================================
34
+ EMBED_TEXT_PRO = "confam-embed-text-pro"
35
+ EMBED_MULTIMODAL_PRO = "confam-embed-multimodal-pro"
36
+ EMBED_MULTILINGUAL = "confam-embed-multilingual"
37
+ EMBED_SMALL = "confam-embed-small"
38
+ EMBED_0_6B = "confam-embed-0.6b"
39
+ EMBED_TEXT_LOCAL = "confam-embed-text-local"
40
+
41
+ # =========================================================================
42
+ # 🔍 RERANK
43
+ # =========================================================================
44
+ RERANK = "confam-rerank"
45
+ RERANK_FAST = "confam-rerank-fast"
@@ -1,7 +1,7 @@
1
1
  [project]
2
2
  name = "confamnode"
3
- version = "0.1.0"
4
- description = "The Nigerian AI inference SDK — access powerful AI models and pay in Naira"
3
+ version = "0.1.1"
4
+ description = "The Nigerian AI inference gateway — access powerful AI models"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
7
7
  license = { text = "MIT" }
@@ -34,6 +34,7 @@ dev = [
34
34
  "build>=1.5.0",
35
35
  "pytest>=9.0.3",
36
36
  "pytest-dotenv>=0.5.2",
37
+ "setuptools>=82.0.1",
37
38
  "twine>=6.2.0",
38
39
  ]
39
40
 
@@ -0,0 +1,231 @@
1
+ import pytest
2
+ from confamnode.ansa import Ansa, Usage, Cost
3
+
4
+
5
+ def test_usage_has_prompt_tokens():
6
+ usage = Usage(prompt_tokens=10, completion_tokens=20, total_tokens=30)
7
+ assert usage.prompt_tokens == 10
8
+
9
+
10
+ def test_usage_has_completion_tokens():
11
+ usage = Usage(prompt_tokens=10, completion_tokens=20, total_tokens=30)
12
+ assert usage.completion_tokens == 20
13
+
14
+
15
+ def test_usage_has_total_tokens():
16
+ usage = Usage(prompt_tokens=10, completion_tokens=20, total_tokens=30)
17
+ assert usage.total_tokens == 30
18
+
19
+
20
+ def test_cost_has_naira():
21
+ cost = Cost(naira=1.23)
22
+ assert cost.naira == 1.23
23
+
24
+
25
+ def test_cost_dollars_is_none_by_default():
26
+ cost = Cost(naira=1.23)
27
+ assert cost.dollars is None
28
+
29
+
30
+ def test_cost_accepts_dollars():
31
+ cost = Cost(naira=1.23, dollars=0.0008)
32
+ assert cost.dollars == 0.0008
33
+
34
+
35
+ def test_ansa_has_text():
36
+ usage = Usage(prompt_tokens=10, completion_tokens=20, total_tokens=30)
37
+ cost = Cost(naira=1.23)
38
+ ansa = Ansa(
39
+ text="How far!",
40
+ model="confam-speed",
41
+ usage=usage,
42
+ cost=cost,
43
+ finish_reason="stop",
44
+ raw={}
45
+ )
46
+ assert ansa.text == "How far!"
47
+
48
+
49
+ def test_ansa_has_model():
50
+ usage = Usage(prompt_tokens=10, completion_tokens=20, total_tokens=30)
51
+ cost = Cost(naira=1.23)
52
+ ansa = Ansa(
53
+ text="How far!",
54
+ model="confam-speed",
55
+ usage=usage,
56
+ cost=cost,
57
+ finish_reason="stop",
58
+ raw={}
59
+ )
60
+ assert ansa.model == "confam-speed"
61
+
62
+
63
+ def test_ansa_reasoning_is_none_by_default():
64
+ usage = Usage(prompt_tokens=10, completion_tokens=20, total_tokens=30)
65
+ cost = Cost(naira=1.23)
66
+ ansa = Ansa(
67
+ text="How far!",
68
+ model="confam-speed",
69
+ usage=usage,
70
+ cost=cost,
71
+ finish_reason="stop",
72
+ raw={}
73
+ )
74
+ assert ansa.reasoning is None
75
+
76
+
77
+ def test_ansa_tools_is_empty_list_by_default():
78
+ usage = Usage(prompt_tokens=10, completion_tokens=20, total_tokens=30)
79
+ cost = Cost(naira=1.23)
80
+ ansa = Ansa(
81
+ text="How far!",
82
+ model="confam-speed",
83
+ usage=usage,
84
+ cost=cost,
85
+ finish_reason="stop",
86
+ raw={}
87
+ )
88
+ assert ansa.tools == []
89
+
90
+
91
+ def test_ansa_has_usage():
92
+ usage = Usage(prompt_tokens=10, completion_tokens=20, total_tokens=30)
93
+ cost = Cost(naira=1.23)
94
+ ansa = Ansa(
95
+ text="How far!",
96
+ model="confam-speed",
97
+ usage=usage,
98
+ cost=cost,
99
+ finish_reason="stop",
100
+ raw={}
101
+ )
102
+ assert ansa.usage.total_tokens == 30
103
+
104
+
105
+ def test_ansa_has_finish_reason():
106
+ usage = Usage(prompt_tokens=10, completion_tokens=20, total_tokens=30)
107
+ cost = Cost(naira=1.23)
108
+ ansa = Ansa(
109
+ text="How far!",
110
+ model="confam-speed",
111
+ usage=usage,
112
+ cost=cost,
113
+ finish_reason="stop",
114
+ raw={}
115
+ )
116
+ assert ansa.finish_reason == "stop"
117
+
118
+
119
+ def test_ansa_has_raw():
120
+ usage = Usage(prompt_tokens=10, completion_tokens=20, total_tokens=30)
121
+ cost = Cost(naira=1.23)
122
+ raw = {"id": "abc123"}
123
+ ansa = Ansa(
124
+ text="How far!",
125
+ model="confam-speed",
126
+ usage=usage,
127
+ cost=cost,
128
+ finish_reason="stop",
129
+ raw=raw
130
+ )
131
+ assert ansa.raw == {"id": "abc123"}
132
+
133
+
134
+ def test_ansa_accepts_reasoning():
135
+ usage = Usage(prompt_tokens=10, completion_tokens=20, total_tokens=30)
136
+ cost = Cost(naira=1.23)
137
+ ansa = Ansa(
138
+ text="The answer is 42",
139
+ model="confam-reasoning",
140
+ usage=usage,
141
+ cost=cost,
142
+ finish_reason="stop",
143
+ raw={},
144
+ reasoning="First I thought about it..."
145
+ )
146
+ assert ansa.reasoning == "First I thought about it..."
147
+
148
+
149
+ def test_ansa_accepts_tools():
150
+ usage = Usage(prompt_tokens=10, completion_tokens=20, total_tokens=30)
151
+ cost = Cost(naira=1.23)
152
+ ansa = Ansa(
153
+ text="",
154
+ model="confam-agents",
155
+ usage=usage,
156
+ cost=cost,
157
+ finish_reason="tool_calls",
158
+ raw={},
159
+ tools=[{"name": "search", "arguments": {"query": "Lagos weather"}}]
160
+ )
161
+ assert len(ansa.tools) == 1
162
+ assert ansa.tools[0]["name"] == "search"
163
+
164
+
165
+ def test_ansa_has_id():
166
+ usage = Usage(prompt_tokens=10, completion_tokens=20, total_tokens=30)
167
+ cost = Cost(naira=1.23)
168
+ ansa = Ansa(
169
+ text="How far!",
170
+ model="confam-speed",
171
+ usage=usage,
172
+ cost=cost,
173
+ finish_reason="stop",
174
+ raw={},
175
+ )
176
+ assert ansa.id.startswith("confam-")
177
+
178
+
179
+ def test_ansa_id_is_unique():
180
+ usage = Usage(prompt_tokens=10, completion_tokens=20, total_tokens=30)
181
+ cost = Cost(naira=1.23)
182
+ ansa1 = Ansa(
183
+ text="How far!",
184
+ model="confam-speed",
185
+ usage=usage,
186
+ cost=cost,
187
+ finish_reason="stop",
188
+ raw={},
189
+ )
190
+ ansa2 = Ansa(
191
+ text="How far!",
192
+ model="confam-speed",
193
+ usage=usage,
194
+ cost=cost,
195
+ finish_reason="stop",
196
+ raw={},
197
+ )
198
+ assert ansa1.id != ansa2.id
199
+
200
+
201
+ def test_ansa_citations_is_empty_list_by_default():
202
+ usage = Usage(prompt_tokens=10, completion_tokens=20, total_tokens=30)
203
+ cost = Cost(naira=1.23)
204
+ ansa = Ansa(
205
+ text="How far!",
206
+ model="confam-speed",
207
+ usage=usage,
208
+ cost=cost,
209
+ finish_reason="stop",
210
+ raw={}
211
+ )
212
+ assert ansa.citations == []
213
+
214
+
215
+ def test_ansa_accepts_citations():
216
+ usage = Usage(prompt_tokens=10, completion_tokens=20, total_tokens=30)
217
+ cost = Cost(naira=1.23)
218
+ ansa = Ansa(
219
+ text="Nigerian GDP grew by 3.4%",
220
+ model="confam-speed",
221
+ usage=usage,
222
+ cost=cost,
223
+ finish_reason="stop",
224
+ raw={},
225
+ citations=[
226
+ {"source": "World Bank", "url": "https://worldbank.org/"},
227
+ {"source": "NBS Nigeria", "url": "https://nigerianstat.gov.ng/"}
228
+ ]
229
+ )
230
+ assert len(ansa.citations) == 2
231
+ assert ansa.citations[0]["source"] == "World Bank"
@@ -1,6 +1,7 @@
1
1
  import os
2
2
  import pytest
3
3
  from unittest.mock import patch
4
+
4
5
  from confamnode.client import ConfamNode
5
6
  from confamnode.exceptions import ConfamAuthError
6
7
 
@@ -1,10 +1,13 @@
1
1
  import pytest
2
2
  from unittest.mock import patch, MagicMock
3
- from confamnode.client import ConfamNode
3
+
4
4
  from confamnode import models
5
+ from confamnode.client import ConfamNode
6
+ from confamnode.ansa import Ansa, Usage, Cost
5
7
  from confamnode.exceptions import ConfamModelError
6
8
 
7
9
 
10
+
8
11
  @pytest.fixture
9
12
  def client():
10
13
  return ConfamNode(api_key="confam-sk-test-abc123")
@@ -41,11 +44,11 @@ def test_gist_rejects_invalid_model(client):
41
44
 
42
45
  def test_gist_returns_content(client, mock_litellm_response):
43
46
  with patch("confamnode.client.litellm.completion", return_value=mock_litellm_response):
44
- response = client.gist(
47
+ ansa = client.gist(
45
48
  model=models.SPEED,
46
49
  messages=[{"role": "user", "content": "How far?"}]
47
50
  )
48
- assert response.choices[0].message.content == "How far! I dey fine."
51
+ assert ansa.text == "How far! I dey fine."
49
52
 
50
53
 
51
54
  def test_gist_accepts_all_valid_models(client, mock_litellm_response):
@@ -57,11 +60,11 @@ def test_gist_accepts_all_valid_models(client, mock_litellm_response):
57
60
  ]
58
61
  with patch("confamnode.client.litellm.completion", return_value=mock_litellm_response):
59
62
  for model in valid_models:
60
- response = client.gist(
63
+ ansa = client.gist(
61
64
  model=model,
62
65
  messages=[{"role": "user", "content": "How far?"}]
63
66
  )
64
- assert response is not None
67
+ assert ansa is not None
65
68
 
66
69
 
67
70
  def test_gist_sends_correct_model_to_litellm(client, mock_litellm_response):
@@ -77,11 +80,11 @@ def test_gist_sends_correct_model_to_litellm(client, mock_litellm_response):
77
80
 
78
81
  def test_gist_accepts_string_messages(client, mock_litellm_response):
79
82
  with patch("confamnode.client.litellm.completion", return_value=mock_litellm_response):
80
- response = client.gist(
83
+ ansa = client.gist(
81
84
  model=models.SPEED,
82
85
  messages="How far?"
83
86
  )
84
- assert response is not None
87
+ assert ansa is not None
85
88
 
86
89
 
87
90
  def test_gist_converts_string_to_message_list(client, mock_litellm_response):
@@ -96,11 +99,11 @@ def test_gist_converts_string_to_message_list(client, mock_litellm_response):
96
99
 
97
100
  def test_gist_accepts_lists_messages(client, mock_litellm_response):
98
101
  with patch("confamnode.client.litellm.completion", return_value=mock_litellm_response):
99
- response = client.gist(
102
+ ansa = client.gist(
100
103
  model=models.SPEED,
101
104
  messages=[{"role": "user", "content": "How far?"}]
102
105
  )
103
- assert response is not None
106
+ assert ansa is not None
104
107
 
105
108
 
106
109
  def test_gist_rejects_invalid_messages_type(client):
@@ -122,3 +125,91 @@ def test_gist_passes_kwargs_to_litellm(client, mock_litellm_response):
122
125
  call_args = mock_call.call_args
123
126
  assert call_args.kwargs["temperature"] == 0.7
124
127
  assert call_args.kwargs["max_tokens"] == 500
128
+
129
+
130
+ def test_gist_prepends_openai_prefix_to_model(client, mock_litellm_response):
131
+ with patch("confamnode.client.litellm.completion", return_value=mock_litellm_response) as mock_call:
132
+ client.gist(
133
+ model="confam-speed",
134
+ messages="How far?"
135
+ )
136
+ call_args = mock_call.call_args
137
+ assert call_args.kwargs["model"] == "openai/confam-speed"
138
+
139
+
140
+ def test_gist_returns_ansa_object(client, mock_litellm_response):
141
+ with patch("confamnode.client.litellm.completion", return_value=mock_litellm_response):
142
+ ansa = client.gist(
143
+ model="confam-speed",
144
+ messages="How far?"
145
+ )
146
+ assert isinstance(ansa, Ansa)
147
+
148
+
149
+ def test_gist_has_test(client, mock_litellm_response):
150
+ with patch("confamnode.client.litellm.completion", return_value=mock_litellm_response):
151
+ ansa = client.gist(
152
+ model="confam-speed",
153
+ messages="How far?"
154
+ )
155
+ assert ansa.text == "How far! I dey fine."
156
+
157
+
158
+ def test_gist_has_model(client, mock_litellm_response):
159
+ with patch("confamnode.client.litellm.completion", return_value=mock_litellm_response):
160
+ ansa = client.gist(
161
+ model="confam-speed",
162
+ messages="How far?"
163
+ )
164
+ assert ansa.model == "confam-speed"
165
+
166
+
167
+ def test_gist_has_usage(client, mock_litellm_response):
168
+ with patch("confamnode.client.litellm.completion", return_value=mock_litellm_response):
169
+ ansa = client.gist(
170
+ model="confam-speed",
171
+ messages="How far?"
172
+ )
173
+ assert isinstance(ansa.usage, Usage)
174
+ assert ansa.usage.prompt_tokens == 10
175
+ assert ansa.usage.completion_tokens == 20
176
+ assert ansa.usage.total_tokens == 30
177
+
178
+
179
+ def test_gist_has_cost(client, mock_litellm_response):
180
+ with patch("confamnode.client.litellm.completion", return_value=mock_litellm_response):
181
+ ansa = client.gist(
182
+ model="confam-speed",
183
+ messages="How far?"
184
+ )
185
+ assert isinstance(ansa.cost, Cost)
186
+ assert isinstance(ansa.cost.naira, float)
187
+
188
+
189
+ def test_gist_has_finish_reason(client, mock_litellm_response):
190
+ with patch("confamnode.client.litellm.completion", return_value=mock_litellm_response):
191
+ ansa = client.gist(
192
+ model="confam-speed",
193
+ messages="How far?"
194
+ )
195
+ assert ansa.finish_reason is not None
196
+
197
+
198
+ def test_gist_has_id(client, mock_litellm_response):
199
+ with patch("confamnode.client.litellm.completion", return_value=mock_litellm_response):
200
+ ansa = client.gist(
201
+ model="confam-speed",
202
+ messages="How far?"
203
+ )
204
+ assert ansa.id.startswith("confam-")
205
+
206
+
207
+ def test_gist_has_raw(client, mock_litellm_response):
208
+ with patch("confamnode.client.litellm.completion", return_value=mock_litellm_response):
209
+ ansa = client.gist(
210
+ model="confam-speed",
211
+ messages="How far?"
212
+ )
213
+ assert ansa.raw is not None
214
+
215
+
@@ -19,4 +19,9 @@ def test_exceptions_importable_from_package():
19
19
  def test_version_is_defined():
20
20
  import confamnode
21
21
  assert hasattr(confamnode, "__version__")
22
- assert isinstance(confamnode.__version__, str)
22
+ assert isinstance(confamnode.__version__, str)
23
+
24
+
25
+ def test_confamstream_importable_from_package():
26
+ from confamnode import ConfamStream
27
+ assert ConfamStream is not None
@@ -0,0 +1,84 @@
1
+ from confamnode.models import (
2
+ LITE, SPEED, REASONING,
3
+ INTELLIGENCE, DEEP_REASONING, CODE, CODE_PRO, VISION, AUDIO, TTS,
4
+ NANO,
5
+ EMBED_TEXT, EMBED_MULTIMODAL, EMBED_MULTIMODAL_2, EMBED_TEXT_LOCAL,
6
+ EMBED_TEXT_PRO, EMBED_MULTIMODAL_PRO, EMBED_MULTILINGUAL,
7
+ EMBED_SMALL, EMBED_0_6B,
8
+ RERANK, RERANK_FAST,
9
+ )
10
+
11
+
12
+ def test_model_constants_are_strings():
13
+ assert isinstance(LITE, str)
14
+ assert isinstance(SPEED, str)
15
+ assert isinstance(REASONING, str)
16
+ assert isinstance(INTELLIGENCE, str)
17
+ assert isinstance(DEEP_REASONING, str)
18
+ assert isinstance(CODE, str)
19
+ assert isinstance(CODE_PRO, str)
20
+ assert isinstance(VISION, str)
21
+ assert isinstance(AUDIO, str)
22
+ assert isinstance(TTS, str)
23
+ assert isinstance(NANO, str)
24
+ assert isinstance(EMBED_TEXT, str)
25
+ assert isinstance(EMBED_MULTIMODAL, str)
26
+ assert isinstance(EMBED_MULTIMODAL_2, str)
27
+ assert isinstance(EMBED_TEXT_LOCAL, str)
28
+ assert isinstance(EMBED_TEXT_PRO, str)
29
+ assert isinstance(EMBED_MULTIMODAL_PRO, str)
30
+ assert isinstance(EMBED_MULTILINGUAL, str)
31
+ assert isinstance(EMBED_SMALL, str)
32
+ assert isinstance(EMBED_0_6B, str)
33
+ assert isinstance(RERANK, str)
34
+ assert isinstance(RERANK_FAST, str)
35
+
36
+
37
+ def test_model_constants_start_with_confam():
38
+ assert LITE.startswith("confam-")
39
+ assert SPEED.startswith("confam-")
40
+ assert REASONING.startswith("confam-")
41
+ assert INTELLIGENCE.startswith("confam-")
42
+ assert DEEP_REASONING.startswith("confam-")
43
+ assert CODE.startswith("confam-")
44
+ assert CODE_PRO.startswith("confam-")
45
+ assert VISION.startswith("confam-")
46
+ assert AUDIO.startswith("confam-")
47
+ assert TTS.startswith("confam-")
48
+ assert NANO.startswith("confam-")
49
+ assert EMBED_TEXT.startswith("confam-")
50
+ assert EMBED_MULTIMODAL.startswith("confam-")
51
+ assert EMBED_MULTIMODAL_2.startswith("confam-")
52
+ assert EMBED_TEXT_LOCAL.startswith("confam-")
53
+ assert EMBED_TEXT_PRO.startswith("confam-")
54
+ assert EMBED_MULTIMODAL_PRO.startswith("confam-")
55
+ assert EMBED_MULTILINGUAL.startswith("confam-")
56
+ assert EMBED_SMALL.startswith("confam-")
57
+ assert EMBED_0_6B.startswith("confam-")
58
+ assert RERANK.startswith("confam-")
59
+ assert RERANK_FAST.startswith("confam-")
60
+
61
+
62
+ def test_model_constants_have_correct_values():
63
+ assert LITE == "confam-lite"
64
+ assert SPEED == "confam-speed"
65
+ assert REASONING == "confam-reasoning"
66
+ assert INTELLIGENCE == "confam-intelligence"
67
+ assert DEEP_REASONING == "confam-deep-reasoning"
68
+ assert CODE == "confam-code"
69
+ assert CODE_PRO == "confam-code-pro"
70
+ assert VISION == "confam-vision"
71
+ assert AUDIO == "confam-audio"
72
+ assert TTS == "confam-tts"
73
+ assert NANO == "confam-nano"
74
+ assert EMBED_TEXT == "confam-embed-text-1"
75
+ assert EMBED_MULTIMODAL == "confam-embed-multimodal-1"
76
+ assert EMBED_MULTIMODAL_2 == "confam-embed-multimodal-2"
77
+ assert EMBED_TEXT_LOCAL == "confam-embed-text-local"
78
+ assert EMBED_TEXT_PRO == "confam-embed-text-pro"
79
+ assert EMBED_MULTIMODAL_PRO == "confam-embed-multimodal-pro"
80
+ assert EMBED_MULTILINGUAL == "confam-embed-multilingual"
81
+ assert EMBED_SMALL == "confam-embed-small"
82
+ assert EMBED_0_6B == "confam-embed-0.6b"
83
+ assert RERANK == "confam-rerank"
84
+ assert RERANK_FAST == "confam-rerank-fast"
@@ -418,7 +418,7 @@ wheels = [
418
418
 
419
419
  [[package]]
420
420
  name = "confamnode"
421
- version = "0.1.0"
421
+ version = "0.1.1"
422
422
  source = { editable = "." }
423
423
  dependencies = [
424
424
  { name = "litellm" },
@@ -430,6 +430,7 @@ dev = [
430
430
  { name = "build" },
431
431
  { name = "pytest" },
432
432
  { name = "pytest-dotenv" },
433
+ { name = "setuptools" },
433
434
  { name = "twine" },
434
435
  ]
435
436
 
@@ -444,6 +445,7 @@ dev = [
444
445
  { name = "build", specifier = ">=1.5.0" },
445
446
  { name = "pytest", specifier = ">=9.0.3" },
446
447
  { name = "pytest-dotenv", specifier = ">=0.5.2" },
448
+ { name = "setuptools", specifier = ">=82.0.1" },
447
449
  { name = "twine", specifier = ">=6.2.0" },
448
450
  ]
449
451
 
@@ -2283,6 +2285,15 @@ wheels = [
2283
2285
  { url = "https://files.pythonhosted.org/packages/b7/46/f5af3402b579fd5e11573ce652019a67074317e18c1935cc0b4ba9b35552/secretstorage-3.5.0-py3-none-any.whl", hash = "sha256:0ce65888c0725fcb2c5bc0fdb8e5438eece02c523557ea40ce0703c266248137", size = 15554, upload-time = "2025-11-23T19:02:51.545Z" },
2284
2286
  ]
2285
2287
 
2288
+ [[package]]
2289
+ name = "setuptools"
2290
+ version = "82.0.1"
2291
+ source = { registry = "https://pypi.org/simple" }
2292
+ sdist = { url = "https://files.pythonhosted.org/packages/4f/db/cfac1baf10650ab4d1c111714410d2fbb77ac5a616db26775db562c8fab2/setuptools-82.0.1.tar.gz", hash = "sha256:7d872682c5d01cfde07da7bccc7b65469d3dca203318515ada1de5eda35efbf9", size = 1152316, upload-time = "2026-03-09T12:47:17.221Z" }
2293
+ wheels = [
2294
+ { url = "https://files.pythonhosted.org/packages/9d/76/f789f7a86709c6b087c5a2f52f911838cad707cc613162401badc665acfe/setuptools-82.0.1-py3-none-any.whl", hash = "sha256:a59e362652f08dcd477c78bb6e7bd9d80a7995bc73ce773050228a348ce2e5bb", size = 1006223, upload-time = "2026-03-09T12:47:15.026Z" },
2295
+ ]
2296
+
2286
2297
  [[package]]
2287
2298
  name = "shellingham"
2288
2299
  version = "1.5.4"
@@ -1,63 +0,0 @@
1
- import os
2
- import litellm
3
-
4
- from typing import Union, List, Dict
5
-
6
- from confamnode import models
7
- from confamnode.exceptions import ConfamAuthError, ConfamModelError
8
-
9
- VALID_MODELS = [
10
- models.LITE,
11
- models.SPEED,
12
- models.REASONING,
13
- models.VISION,
14
- models.AUDIO,
15
- models.TTS,
16
- models.NANO
17
- ]
18
-
19
- DEFAULT_BASE_URL = "https://api.confamnode.com/v1"
20
-
21
-
22
- class ConfamNode:
23
- def __init__(
24
- self,
25
- api_key: str = None,
26
- base_url: str = None
27
- ):
28
- # Pick up from environment if not provided
29
- api_key = api_key or os.environ.get("CONFAMNODE_API_KEY")
30
-
31
- if not api_key:
32
- raise ValueError("api_key is required")
33
-
34
- if not api_key.startswith("confam-sk-"):
35
- raise ConfamAuthError()
36
-
37
- self.api_key = api_key
38
- self.litellm_key = api_key.removeprefix("confam-")
39
- self.base_url = base_url or DEFAULT_BASE_URL
40
-
41
- def gist(
42
- self,
43
- model: str,
44
- messages: Union[str, List[Dict[str, str]]],
45
- **kwargs
46
- ) -> object:
47
- if model not in VALID_MODELS:
48
- raise ConfamModelError(model)
49
-
50
- if isinstance(messages, str):
51
- messages = [{"role": "user", "content": messages}]
52
- elif not isinstance(messages, list):
53
- raise ValueError("messages must be a string or list")
54
-
55
- response = litellm.completion(
56
- model=model,
57
- messages=messages,
58
- api_key=self.litellm_key,
59
- base_url=self.base_url,
60
- **kwargs
61
- )
62
-
63
- return response
@@ -1,22 +0,0 @@
1
- # Chat models
2
- LITE = "confam-lite"
3
- SPEED = "confam-speed"
4
- REASONING = "confam-reasoning"
5
- VISION = "confam-vision"
6
- AUDIO = "confam-audio"
7
- TTS = "confam-tts"
8
-
9
-
10
- # Embedding models
11
- EMBED_TEXT = "confam-embed-text-1"
12
- EMBED_MULTIMODAL = "confam-embed-multimodal-1"
13
- EMBED_MULTIMODAL_2 = "confam-embed-multimodal-2"
14
-
15
-
16
- # Local models
17
- NANO = "confam-nano"
18
-
19
-
20
- # Local Embedding models
21
- EMBED_TEXT_LOCAL = "confam-embed-text-local"
22
-
confamnode-0.1.0/main.py DELETED
@@ -1,6 +0,0 @@
1
- def main():
2
- print("Hello from confamnode-sdk!")
3
-
4
-
5
- if __name__ == "__main__":
6
- main()
@@ -1,55 +0,0 @@
1
- from confamnode.models import (
2
- LITE,
3
- SPEED,
4
- REASONING,
5
- VISION,
6
- AUDIO,
7
- TTS,
8
- NANO,
9
- EMBED_TEXT,
10
- EMBED_MULTIMODAL,
11
- EMBED_MULTIMODAL_2,
12
- EMBED_TEXT_LOCAL
13
- )
14
-
15
- def test_model_constants_are_strings():
16
- assert isinstance(LITE, str)
17
- assert isinstance(SPEED, str)
18
- assert isinstance(REASONING, str)
19
- assert isinstance(VISION, str)
20
- assert isinstance(AUDIO, str)
21
- assert isinstance(TTS, str)
22
- assert isinstance(NANO, str)
23
- assert isinstance(EMBED_TEXT, str)
24
- assert isinstance(EMBED_MULTIMODAL, str)
25
- assert isinstance(EMBED_MULTIMODAL_2, str)
26
- assert isinstance(EMBED_TEXT_LOCAL, str)
27
-
28
-
29
- def test_model_constants_start_with_confam():
30
- assert LITE.startswith("confam-")
31
- assert SPEED.startswith("confam-")
32
- assert REASONING.startswith("confam-")
33
- assert VISION.startswith("confam-")
34
- assert AUDIO.startswith("confam-")
35
- assert TTS.startswith("confam-")
36
- assert NANO.startswith("confam-")
37
- assert EMBED_TEXT.startswith("confam-")
38
- assert EMBED_MULTIMODAL.startswith("confam-")
39
- assert EMBED_MULTIMODAL_2.startswith("confam-")
40
- assert EMBED_TEXT_LOCAL.startswith("confam-")
41
-
42
-
43
- def test_model_constants_have_correct_values():
44
- assert LITE == "confam-lite"
45
- assert SPEED == "confam-speed"
46
- assert REASONING == "confam-reasoning"
47
- assert VISION == "confam-vision"
48
- assert AUDIO == "confam-audio"
49
- assert TTS == "confam-tts"
50
- assert NANO == "confam-nano"
51
- assert EMBED_TEXT == "confam-embed-text-1"
52
- assert EMBED_MULTIMODAL == "confam-embed-multimodal-1"
53
- assert EMBED_MULTIMODAL_2 == "confam-embed-multimodal-2"
54
- assert EMBED_TEXT_LOCAL == "confam-embed-text-local"
55
-
@@ -1,63 +0,0 @@
1
- import pytest
2
- from unittest.mock import patch, MagicMock
3
- from confamnode.client import ConfamNode
4
- from confamnode import models
5
-
6
-
7
- @pytest.fixture
8
- def client():
9
- return ConfamNode(api_key="confam-sk-abc123")
10
-
11
-
12
- @pytest.fixture
13
- def mock_stream_response():
14
- chunk1 = MagicMock()
15
- chunk1.choices[0].delta.content = "How"
16
- chunk2 = MagicMock()
17
- chunk2.choices[0].delta.content = " far!"
18
- chunk3 = MagicMock()
19
- chunk3.choices[0].delta.content = None
20
- return iter([chunk1, chunk2, chunk3])
21
-
22
-
23
- def test_gist_accepts_stream_true(client, mock_stream_response):
24
- with patch("confamnode.client.litellm.completion", return_value=mock_stream_response):
25
- response = client.gist(
26
- model=models.SPEED,
27
- messages="How far?",
28
- stream=True
29
- )
30
- assert response is not None
31
-
32
-
33
- def test_gist_stream_returns_chunks(client, mock_stream_response):
34
- with patch("confamnode.client.litellm.completion", return_value=mock_stream_response):
35
- chunks = list(client.gist(
36
- model=models.SPEED,
37
- messages="How far?",
38
- stream=True
39
- ))
40
- assert len(chunks) == 3
41
-
42
-
43
- def test_gist_stream_chunks_have_content(client, mock_stream_response):
44
- with patch("confamnode.client.litellm.completion", return_value=mock_stream_response):
45
- chunks = list(client.gist(
46
- model=models.SPEED,
47
- messages="How far?",
48
- stream=True
49
- ))
50
- assert chunks[0].choices[0].delta.content == "How"
51
- assert chunks[1].choices[0].delta.content == " far!"
52
-
53
-
54
- def test_gist_stream_passes_stream_true_to_litellm(client, mock_stream_response):
55
- with patch("confamnode.client.litellm.completion", return_value=mock_stream_response) as mock_call:
56
- client.gist(
57
- model=models.SPEED,
58
- messages="How far?",
59
- stream=True
60
- )
61
- call_args = mock_call.call_args
62
- assert call_args.kwargs.get("stream") == True
63
-
File without changes
File without changes
File without changes