abyss-ai-sdk 0.1.0__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.
- abyss_ai_sdk-0.1.0/.gitignore +3 -0
- abyss_ai_sdk-0.1.0/PKG-INFO +11 -0
- abyss_ai_sdk-0.1.0/README.md +0 -0
- abyss_ai_sdk-0.1.0/abyss/__init__.py +7 -0
- abyss_ai_sdk-0.1.0/abyss/client.py +80 -0
- abyss_ai_sdk-0.1.0/abyss/providers/__init__.py +0 -0
- abyss_ai_sdk-0.1.0/abyss/providers/anthropic.py +37 -0
- abyss_ai_sdk-0.1.0/abyss/providers/groq.py +43 -0
- abyss_ai_sdk-0.1.0/abyss/providers/openai.py +56 -0
- abyss_ai_sdk-0.1.0/abyss/types.py +33 -0
- abyss_ai_sdk-0.1.0/pyproject.toml +23 -0
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: abyss-ai-sdk
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Multi-provider AI utility with structured responses and streaming
|
|
5
|
+
Project-URL: Homepage, https://github.com/Abyss-Systems/Abyss-SDK-Python
|
|
6
|
+
License: MIT
|
|
7
|
+
Requires-Python: >=3.9
|
|
8
|
+
Requires-Dist: anthropic>=0.25.0
|
|
9
|
+
Requires-Dist: groq>=0.9.0
|
|
10
|
+
Requires-Dist: openai>=1.0.0
|
|
11
|
+
Requires-Dist: pydantic>=2.0.0
|
|
File without changes
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
from .client import ask, stream, embed, create_client
|
|
2
|
+
from .types import AbyssConfig, AskOptions, AskResult, EmbedOptions, EmbedResult
|
|
3
|
+
|
|
4
|
+
__all__ = [
|
|
5
|
+
"ask", "stream", "embed", "create_client",
|
|
6
|
+
"AbyssConfig", "AskOptions", "AskResult", "EmbedOptions", "EmbedResult",
|
|
7
|
+
]
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from .types import AbyssConfig, AskOptions, AskResult, EmbedOptions, EmbedResult
|
|
3
|
+
from .providers.groq import groq_ask, groq_stream
|
|
4
|
+
from .providers.openai import openai_ask, openai_stream, openai_embed
|
|
5
|
+
from .providers.anthropic import anthropic_ask, anthropic_stream
|
|
6
|
+
|
|
7
|
+
DEFAULT_MODELS = {
|
|
8
|
+
"groq": "llama-3.1-8b-instant",
|
|
9
|
+
"openai": "gpt-4o-mini",
|
|
10
|
+
"anthropic": "claude-sonnet-4-6",
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
def _build_system(schema_cls, base: str | None) -> str | None:
|
|
14
|
+
parts = []
|
|
15
|
+
if base:
|
|
16
|
+
parts.append(base)
|
|
17
|
+
if schema_cls:
|
|
18
|
+
parts.append("Respond ONLY with a valid JSON object. No markdown, no explanation, just raw JSON.")
|
|
19
|
+
import json
|
|
20
|
+
parts.append(f"Schema fields: {list(schema_cls.model_fields.keys())}")
|
|
21
|
+
return "\n\n".join(parts) if parts else None
|
|
22
|
+
|
|
23
|
+
def _parse_structured(raw: str, schema_cls):
|
|
24
|
+
cleaned = raw.replace("```json", "").replace("```", "").strip()
|
|
25
|
+
return schema_cls.model_validate_json(cleaned)
|
|
26
|
+
|
|
27
|
+
def ask(config: AbyssConfig, options: AskOptions) -> AskResult:
|
|
28
|
+
model = config.model or DEFAULT_MODELS[config.provider]
|
|
29
|
+
system = _build_system(options.schema, options.system)
|
|
30
|
+
opts = options.model_copy(update={"system": system})
|
|
31
|
+
|
|
32
|
+
def parse(raw: str):
|
|
33
|
+
if options.schema:
|
|
34
|
+
return _parse_structured(raw, options.schema)
|
|
35
|
+
return raw
|
|
36
|
+
|
|
37
|
+
match config.provider:
|
|
38
|
+
case "groq":
|
|
39
|
+
return groq_ask(config.api_key, model, opts, parse)
|
|
40
|
+
case "openai":
|
|
41
|
+
return openai_ask(config.api_key, model, opts, parse)
|
|
42
|
+
case "anthropic":
|
|
43
|
+
return anthropic_ask(config.api_key, model, opts, parse)
|
|
44
|
+
case _:
|
|
45
|
+
raise ValueError(f"Unknown provider: {config.provider}")
|
|
46
|
+
|
|
47
|
+
def stream(config: AbyssConfig, options: AskOptions):
|
|
48
|
+
model = config.model or DEFAULT_MODELS[config.provider]
|
|
49
|
+
|
|
50
|
+
match config.provider:
|
|
51
|
+
case "groq":
|
|
52
|
+
yield from groq_stream(config.api_key, model, options)
|
|
53
|
+
case "openai":
|
|
54
|
+
yield from openai_stream(config.api_key, model, options)
|
|
55
|
+
case "anthropic":
|
|
56
|
+
yield from anthropic_stream(config.api_key, model, options)
|
|
57
|
+
case _:
|
|
58
|
+
raise ValueError(f"Unknown provider: {config.provider}")
|
|
59
|
+
|
|
60
|
+
def embed(config: AbyssConfig, options: EmbedOptions) -> EmbedResult:
|
|
61
|
+
if config.provider != "openai":
|
|
62
|
+
raise ValueError("Embeddings only supported with provider 'openai'.")
|
|
63
|
+
return openai_embed(config.api_key, options)
|
|
64
|
+
|
|
65
|
+
class AbyssClient:
|
|
66
|
+
def __init__(self, config: AbyssConfig):
|
|
67
|
+
self.config = config
|
|
68
|
+
|
|
69
|
+
def ask(self, options: AskOptions) -> AskResult:
|
|
70
|
+
return ask(self.config, options)
|
|
71
|
+
|
|
72
|
+
def stream(self, options: AskOptions):
|
|
73
|
+
yield from stream(self.config, options)
|
|
74
|
+
|
|
75
|
+
def embed(self, options: EmbedOptions) -> EmbedResult:
|
|
76
|
+
return embed(self.config, options)
|
|
77
|
+
|
|
78
|
+
def create_client(provider: str, api_key: str, model: str | None = None) -> AbyssClient:
|
|
79
|
+
config = AbyssConfig(provider=provider, api_key=api_key, model=model)
|
|
80
|
+
return AbyssClient(config)
|
|
File without changes
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
from anthropic import Anthropic
|
|
2
|
+
from ..types import AskOptions, AskResult
|
|
3
|
+
from typing import Callable
|
|
4
|
+
|
|
5
|
+
def anthropic_ask(api_key: str, model: str, options: AskOptions, parse: Callable) -> AskResult:
|
|
6
|
+
client = Anthropic(api_key=api_key)
|
|
7
|
+
|
|
8
|
+
kwargs = dict(
|
|
9
|
+
model=model,
|
|
10
|
+
max_tokens=options.max_tokens,
|
|
11
|
+
messages=[{"role": "user", "content": options.prompt}],
|
|
12
|
+
)
|
|
13
|
+
if options.system:
|
|
14
|
+
kwargs["system"] = options.system
|
|
15
|
+
if options.temperature is not None:
|
|
16
|
+
kwargs["temperature"] = options.temperature
|
|
17
|
+
|
|
18
|
+
res = client.messages.create(**kwargs)
|
|
19
|
+
raw = next((b.text for b in res.content if b.type == "text"), "")
|
|
20
|
+
|
|
21
|
+
return AskResult(data=parse(raw), raw=raw, provider="anthropic", model=model)
|
|
22
|
+
|
|
23
|
+
def anthropic_stream(api_key: str, model: str, options: AskOptions):
|
|
24
|
+
client = Anthropic(api_key=api_key)
|
|
25
|
+
|
|
26
|
+
kwargs = dict(
|
|
27
|
+
model=model,
|
|
28
|
+
max_tokens=options.max_tokens,
|
|
29
|
+
messages=[{"role": "user", "content": options.prompt}],
|
|
30
|
+
stream=True,
|
|
31
|
+
)
|
|
32
|
+
if options.system:
|
|
33
|
+
kwargs["system"] = options.system
|
|
34
|
+
|
|
35
|
+
with client.messages.stream(**kwargs) as s:
|
|
36
|
+
for text in s.text_stream:
|
|
37
|
+
yield text
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
from groq import Groq
|
|
2
|
+
from ..types import AskOptions, AskResult
|
|
3
|
+
from typing import Any, Callable, AsyncIterator
|
|
4
|
+
|
|
5
|
+
def groq_ask(api_key: str, model: str, options: AskOptions, parse: Callable) -> AskResult:
|
|
6
|
+
client = Groq(api_key=api_key)
|
|
7
|
+
|
|
8
|
+
messages = []
|
|
9
|
+
if options.system:
|
|
10
|
+
messages.append({"role": "system", "content": options.system})
|
|
11
|
+
messages.append({"role": "user", "content": options.prompt})
|
|
12
|
+
|
|
13
|
+
res = client.chat.completions.create(
|
|
14
|
+
model=model,
|
|
15
|
+
messages=messages,
|
|
16
|
+
temperature=options.temperature,
|
|
17
|
+
max_tokens=options.max_tokens,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
raw = res.choices[0].message.content or ""
|
|
21
|
+
|
|
22
|
+
return AskResult(data=parse(raw), raw=raw, provider="groq", model=model)
|
|
23
|
+
|
|
24
|
+
def groq_stream(api_key: str, model: str, options: AskOptions):
|
|
25
|
+
client = Groq(api_key=api_key)
|
|
26
|
+
|
|
27
|
+
messages = []
|
|
28
|
+
if options.system:
|
|
29
|
+
messages.append({"role": "system", "content": options.system})
|
|
30
|
+
messages.append({"role": "user", "content": options.prompt})
|
|
31
|
+
|
|
32
|
+
res = client.chat.completions.create(
|
|
33
|
+
model=model,
|
|
34
|
+
messages=messages,
|
|
35
|
+
temperature=options.temperature,
|
|
36
|
+
max_tokens=options.max_tokens,
|
|
37
|
+
stream=True,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
for chunk in res:
|
|
41
|
+
text = chunk.choices[0].delta.content
|
|
42
|
+
if text:
|
|
43
|
+
yield text
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
from openai import OpenAI
|
|
2
|
+
from ..types import AskOptions, AskResult, EmbedOptions, EmbedResult
|
|
3
|
+
from typing import Callable
|
|
4
|
+
|
|
5
|
+
def openai_ask(api_key: str, model: str, options: AskOptions, parse: Callable) -> AskResult:
|
|
6
|
+
client = OpenAI(api_key=api_key)
|
|
7
|
+
|
|
8
|
+
messages = []
|
|
9
|
+
if options.system:
|
|
10
|
+
messages.append({"role": "system", "content": options.system})
|
|
11
|
+
messages.append({"role": "user", "content": options.prompt})
|
|
12
|
+
|
|
13
|
+
res = client.chat.completions.create(
|
|
14
|
+
model=model,
|
|
15
|
+
messages=messages,
|
|
16
|
+
temperature=options.temperature,
|
|
17
|
+
max_tokens=options.max_tokens,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
raw = res.choices[0].message.content or ""
|
|
21
|
+
return AskResult(data=parse(raw), raw=raw, provider="openai", model=model)
|
|
22
|
+
|
|
23
|
+
def openai_stream(api_key: str, model: str, options: AskOptions):
|
|
24
|
+
client = OpenAI(api_key=api_key)
|
|
25
|
+
|
|
26
|
+
messages = []
|
|
27
|
+
if options.system:
|
|
28
|
+
messages.append({"role": "system", "content": options.system})
|
|
29
|
+
messages.append({"role": "user", "content": options.prompt})
|
|
30
|
+
|
|
31
|
+
res = client.chat.completions.create(
|
|
32
|
+
model=model,
|
|
33
|
+
messages=messages,
|
|
34
|
+
temperature=options.temperature,
|
|
35
|
+
max_tokens=options.max_tokens,
|
|
36
|
+
stream=True,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
for chunk in res:
|
|
40
|
+
text = chunk.choices[0].delta.content
|
|
41
|
+
if text:
|
|
42
|
+
yield text
|
|
43
|
+
|
|
44
|
+
def openai_embed(api_key: str, options: EmbedOptions) -> EmbedResult:
|
|
45
|
+
client = OpenAI(api_key=api_key)
|
|
46
|
+
model = options.model or "text-embedding-3-small"
|
|
47
|
+
|
|
48
|
+
input_list = options.input if isinstance(options.input, list) else [options.input]
|
|
49
|
+
|
|
50
|
+
res = client.embeddings.create(model=model, input=input_list)
|
|
51
|
+
|
|
52
|
+
return EmbedResult(
|
|
53
|
+
embeddings=[d.embedding for d in res.data],
|
|
54
|
+
model=model,
|
|
55
|
+
provider="openai",
|
|
56
|
+
)
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from typing import Any, Generic, Literal, TypeVar
|
|
2
|
+
from pydantic import BaseModel
|
|
3
|
+
|
|
4
|
+
Provider = Literal["groq", "openai", "anthropic"]
|
|
5
|
+
|
|
6
|
+
T = TypeVar("T")
|
|
7
|
+
|
|
8
|
+
class AbyssConfig(BaseModel):
|
|
9
|
+
provider: Provider
|
|
10
|
+
api_key: str
|
|
11
|
+
model: str | None = None
|
|
12
|
+
|
|
13
|
+
class AskOptions(BaseModel, Generic[T]):
|
|
14
|
+
prompt: str
|
|
15
|
+
system: str | None = None
|
|
16
|
+
schema: type[T] | None = None
|
|
17
|
+
temperature: float = 0.7
|
|
18
|
+
max_tokens: int = 1024
|
|
19
|
+
|
|
20
|
+
class AskResult(BaseModel, Generic[T]):
|
|
21
|
+
data: T
|
|
22
|
+
raw: str
|
|
23
|
+
provider: Provider
|
|
24
|
+
model: str
|
|
25
|
+
|
|
26
|
+
class EmbedOptions(BaseModel):
|
|
27
|
+
input: str | list[str]
|
|
28
|
+
model: str | None = None
|
|
29
|
+
|
|
30
|
+
class EmbedResult(BaseModel):
|
|
31
|
+
embeddings: list[list[float]]
|
|
32
|
+
model: str
|
|
33
|
+
provider: Provider
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "abyss-ai-sdk"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Multi-provider AI utility with structured responses and streaming"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
dependencies = [
|
|
13
|
+
"groq>=0.9.0",
|
|
14
|
+
"openai>=1.0.0",
|
|
15
|
+
"anthropic>=0.25.0",
|
|
16
|
+
"pydantic>=2.0.0",
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
[project.urls]
|
|
20
|
+
Homepage = "https://github.com/Abyss-Systems/Abyss-SDK-Python"
|
|
21
|
+
|
|
22
|
+
[tool.hatch.build.targets.wheel]
|
|
23
|
+
packages = ["abyss"]
|