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.
@@ -0,0 +1,3 @@
1
+ .env
2
+ venv/
3
+ dist/
@@ -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"]