amsdal_ml 0.1.0__py3-none-any.whl → 0.1.1__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.
amsdal_ml/__about__.py CHANGED
@@ -1 +1 @@
1
- __version__ = '0.1.0'
1
+ __version__ = '0.1.1'
amsdal_ml/agents/agent.py CHANGED
@@ -6,10 +6,13 @@ from collections.abc import AsyncIterator
6
6
  from collections.abc import Iterator
7
7
  from typing import Any
8
8
  from typing import Literal
9
+ from typing import Optional
9
10
 
10
11
  from pydantic import BaseModel
11
12
  from pydantic import Field
12
13
 
14
+ from amsdal_ml.fileio.base_loader import FileAttachment
15
+
13
16
 
14
17
  class AgentMessage(BaseModel):
15
18
  role: Literal["SYSTEM", "USER", "ASSISTANT"]
@@ -24,16 +27,37 @@ class AgentOutput(BaseModel):
24
27
 
25
28
  class Agent(ABC):
26
29
  @abstractmethod
27
- async def arun(self, user_query: str) -> AgentOutput: ...
28
- @abstractmethod
29
- async def astream(self, user_query: str) -> AsyncIterator[str]:
30
- """Yield streamed chunks for the given query."""
31
- raise NotImplementedError
30
+ async def arun(
31
+ self,
32
+ user_query: str,
33
+ *,
34
+ attachments: Optional[list[FileAttachment]] = None,
35
+ ) -> AgentOutput:
36
+ ...
32
37
 
33
- def run(self, user_query: str) -> AgentOutput:
38
+ @abstractmethod
39
+ async def astream(
40
+ self,
41
+ user_query: str,
42
+ *,
43
+ attachments: Optional[list[FileAttachment]] = None,
44
+ ) -> AsyncIterator[str]:
45
+ ...
46
+
47
+ def run(
48
+ self,
49
+ user_query: str,
50
+ *,
51
+ attachments: Optional[list[FileAttachment]] = None,
52
+ ) -> AgentOutput:
34
53
  msg = "This agent is async-only. Use arun()."
35
54
  raise NotImplementedError(msg)
36
55
 
37
- def stream(self, user_query: str) -> Iterator[str]:
56
+ def stream(
57
+ self,
58
+ user_query: str,
59
+ *,
60
+ attachments: Optional[list[FileAttachment]] = None,
61
+ ) -> Iterator[str]:
38
62
  msg = "This agent is async-only. Use astream()."
39
63
  raise NotImplementedError(msg)
@@ -13,6 +13,7 @@ from typing import no_type_check
13
13
  from amsdal_ml.agents.agent import Agent
14
14
  from amsdal_ml.agents.agent import AgentOutput
15
15
  from amsdal_ml.agents.promts import get_prompt
16
+ from amsdal_ml.fileio.base_loader import FileAttachment
16
17
  from amsdal_ml.mcp_client.base import ToolClient
17
18
  from amsdal_ml.mcp_client.base import ToolInfo
18
19
  from amsdal_ml.ml_models.models import MLModel
@@ -30,6 +31,9 @@ _FINAL_RE = re.compile(
30
31
  r"Final Answer:\s*(?P<answer>.+)",
31
32
  re.DOTALL | re.IGNORECASE,
32
33
  )
34
+ # ---------- constants ----------
35
+
36
+ _MAX_PARSE_RETRIES = 5
33
37
 
34
38
 
35
39
 
@@ -186,11 +190,11 @@ class DefaultQAAgent(Agent):
186
190
  return str(content)
187
191
 
188
192
  # ---------- core run ----------
189
- def run(self, _user_query: str) -> AgentOutput:
193
+ def run(self, user_query: str, *, attachments: list[FileAttachment] | None = None) -> AgentOutput:
190
194
  msg = "DefaultQAAgent is async-only for now. Use arun()."
191
195
  raise NotImplementedError(msg)
192
196
 
193
- async def _run_async(self, user_query: str) -> AgentOutput:
197
+ async def _run_async(self, user_query: str, *, attachments: list[FileAttachment] | None = None) -> AgentOutput:
194
198
  if self._tool_clients and not self._tools_index_built:
195
199
  await self._build_clients_index()
196
200
 
@@ -198,10 +202,12 @@ class DefaultQAAgent(Agent):
198
202
  used_tools: list[str] = []
199
203
  parse_retries = 0
200
204
 
205
+
201
206
  for _ in range(self.max_steps):
202
207
  prompt = self._react_text(user_query, scratch)
203
- out = await self.model.ainvoke(prompt)
204
-
208
+ out = await self.model.ainvoke(prompt, attachments=attachments)
209
+ print("Model output:", out) # noqa: T201
210
+ print('promt:', prompt) # noqa: T201
205
211
  m_final = _FINAL_RE.search(out or "")
206
212
  if m_final:
207
213
  return AgentOutput(
@@ -213,7 +219,7 @@ class DefaultQAAgent(Agent):
213
219
  m_tool = _TOOL_CALL_RE.search(out or "")
214
220
  if not m_tool:
215
221
  parse_retries += 1
216
- if self.on_parse_error == ParseErrorMode.RAISE or parse_retries >= 1:
222
+ if self.on_parse_error == ParseErrorMode.RAISE or parse_retries >= _MAX_PARSE_RETRIES:
217
223
  msg = (
218
224
  "Invalid ReAct output. Expected EXACT format (Final or Tool-call). "
219
225
  f"Got:\n{out}"
@@ -221,6 +227,7 @@ class DefaultQAAgent(Agent):
221
227
  raise ValueError(
222
228
  msg
223
229
  )
230
+
224
231
  scratch += (
225
232
  "\nThought: Previous output violated the strict format. "
226
233
  "Reply again using EXACTLY one of the two specified formats.\n"
@@ -237,7 +244,7 @@ class DefaultQAAgent(Agent):
237
244
  raise ValueError(msg)
238
245
  except Exception as e:
239
246
  parse_retries += 1
240
- if self.on_parse_error == ParseErrorMode.RAISE or parse_retries >= 1:
247
+ if self.on_parse_error == ParseErrorMode.RAISE or parse_retries >= _MAX_PARSE_RETRIES:
241
248
  msg = f"Invalid Action Input JSON: {raw_input!r} ({e})"
242
249
  raise ValueError(msg) from e
243
250
  scratch += (
@@ -250,6 +257,7 @@ class DefaultQAAgent(Agent):
250
257
 
251
258
  try:
252
259
  result = await tool.run(args, context=None, convert_result=True)
260
+ print("Similarity search result:", result) # noqa: T201
253
261
  except Exception as e:
254
262
  # unified error payload
255
263
  err = {
@@ -276,12 +284,12 @@ class DefaultQAAgent(Agent):
276
284
  return self._stopped_response(used_tools)
277
285
 
278
286
  # ---------- public APIs ----------
279
- async def arun(self, user_query: str) -> AgentOutput:
280
- return await self._run_async(user_query)
287
+ async def arun(self, user_query: str, *, attachments: list[FileAttachment] | None = None) -> AgentOutput:
288
+ return await self._run_async(user_query, attachments=attachments)
281
289
 
282
290
  # ---------- streaming ----------
283
291
  @no_type_check
284
- async def astream(self, user_query: str) -> AsyncIterator[str]:
292
+ async def astream(self, user_query: str, *, attachments: list[FileAttachment] | None = None) -> AsyncIterator[str]:
285
293
  if self._tool_clients and not self._tools_index_built:
286
294
  await self._build_clients_index()
287
295
 
@@ -296,7 +304,7 @@ class DefaultQAAgent(Agent):
296
304
 
297
305
  # Normalize model.astream: it might be an async iterator already,
298
306
  # or a coroutine (or nested coroutines) that resolves to one.
299
- _val = self.model.astream(prompt)
307
+ _val = self.model.astream(prompt, attachments=attachments)
300
308
  while inspect.iscoroutine(_val):
301
309
  _val = await _val
302
310
 
@@ -320,7 +328,7 @@ class DefaultQAAgent(Agent):
320
328
  m_tool = _TOOL_CALL_RE.search(buffer or "")
321
329
  if not m_tool:
322
330
  parse_retries += 1
323
- if self.on_parse_error == ParseErrorMode.RAISE or parse_retries >= 1:
331
+ if self.on_parse_error == ParseErrorMode.RAISE or parse_retries >= _MAX_PARSE_RETRIES:
324
332
  msg = f"Invalid ReAct output (stream). Expected EXACT format. Got:\n{buffer}"
325
333
  raise ValueError(msg)
326
334
  scratch += (
@@ -339,7 +347,7 @@ class DefaultQAAgent(Agent):
339
347
  raise ValueError(msg)
340
348
  except Exception as e:
341
349
  parse_retries += 1
342
- if self.on_parse_error == ParseErrorMode.RAISE or parse_retries >= 1:
350
+ if self.on_parse_error == ParseErrorMode.RAISE or parse_retries >= _MAX_PARSE_RETRIES:
343
351
  msg = f"Invalid Action Input JSON: {raw_input!r} ({e})"
344
352
  raise ValueError(msg) from e
345
353
  scratch += (
@@ -21,7 +21,16 @@ RULES
21
21
  - `Action Input` MUST be a valid ONE-LINE JSON object (e.g. {{"a": 1, "b": 2}}).
22
22
  - Do NOT add anything before/after the block.
23
23
  - Do NOT print "Observation". The system will add it after tool execution.
24
-
24
+ - You CAN read and use any attached files provided by the platform (including PDFs). Do not claim inability to view files.
25
+ - Prefer not to use tools when the answer is fully supported by the user’s message and/or attached files. Use a tool only if it materially improves accuracy or retrieves missing facts.
26
+ - If information is insufficient, answer concisely with: “I don’t have enough data to answer.”
27
+ - Be deterministic and concise. No preambles, no meta-commentary, no Markdown.
28
+ - Numbers: preserve exact figures, units, and percentages; do not round unless asked.
29
+ - If you choose a tool:
30
+ - Use exactly one tool per step.
31
+ - The Action Input MUST be a single-line valid JSON object that matches the tool’s schema.
32
+ - If a tool errors or returns nothing useful, switch to the other format and produce a Final Answer.
33
+
25
34
  PREVIOUS CONVERSATION
26
35
  --------------------
27
36
  {chat_history}
@@ -1,5 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import logging
4
+ import sys
3
5
  from typing import Any
4
6
  from typing import Optional
5
7
 
@@ -9,6 +11,14 @@ from pydantic import Field
9
11
 
10
12
  from amsdal_ml.ml_retrievers.openai_retriever import OpenAIRetriever
11
13
 
14
+ logging.basicConfig(
15
+ level=logging.INFO,
16
+ format='%(asctime)s [%(levelname)s] %(message)s',
17
+ handlers=[
18
+ logging.FileHandler("server2.log"),
19
+ logging.StreamHandler(sys.stdout)
20
+ ]
21
+ )
12
22
 
13
23
  class RetrieverArgs(BaseModel):
14
24
  query: str = Field(..., description='User search query')
@@ -20,13 +30,22 @@ class RetrieverArgs(BaseModel):
20
30
  _retriever = OpenAIRetriever()
21
31
 
22
32
 
23
- async def retriever_search(args: RetrieverArgs) -> list[dict[str, Any]]:
33
+ async def retriever_search(
34
+ query: str = Field(..., description='User search query'),
35
+ k: int = 5,
36
+ include_tags: Optional[list[str]] = None,
37
+ exclude_tags: Optional[list[str]] = None,
38
+ ) -> list[dict[str, Any]]:
39
+ logging.info(
40
+ f"retriever_search called with query={query}, k={k}, include_tags={include_tags}, exclude_tags={exclude_tags}"
41
+ )
24
42
  chunks = await _retriever.asimilarity_search(
25
- query=args.query,
26
- k=args.k,
27
- include_tags=args.include_tags,
28
- exclude_tags=args.exclude_tags,
43
+ query=query,
44
+ k=k,
45
+ include_tags=include_tags,
46
+ exclude_tags=exclude_tags,
29
47
  )
48
+ logging.info(f"retriever_search found {len(chunks)} chunks: {chunks}")
30
49
  out: list[dict[str, Any]] = []
31
50
  for c in chunks:
32
51
  if hasattr(c, 'model_dump'):
@@ -42,7 +61,7 @@ async def retriever_search(args: RetrieverArgs) -> list[dict[str, Any]]:
42
61
 
43
62
  retriever_tool = Tool.from_function(
44
63
  retriever_search,
45
- name='retriever.search',
64
+ name='search',
46
65
  description='Semantic search in knowledge base (OpenAI embeddings)',
47
66
  structured_output=True,
48
67
  )
File without changes
@@ -0,0 +1,63 @@
1
+ from __future__ import annotations
2
+
3
+ from abc import ABC
4
+ from abc import abstractmethod
5
+ from dataclasses import dataclass
6
+ from typing import Any
7
+ from typing import BinaryIO
8
+ from typing import Optional
9
+
10
+ from pydantic import BaseModel
11
+
12
+ PLAIN_TEXT = "plain_text"
13
+ FILE_ID = "file_id"
14
+
15
+
16
+ class FileData(BaseModel):
17
+ name: str
18
+ size: int | None = None
19
+ mime: Optional[str] = None
20
+
21
+
22
+ @dataclass
23
+ class FileAttachment:
24
+ type: str # one of: PLAIN_TEXT, FILE_ID
25
+ content: Any
26
+ metadata: dict[str, Any] | None = None
27
+
28
+ def __post_init__(self) -> None:
29
+ if self.metadata is None:
30
+ self.metadata = {}
31
+
32
+
33
+ @dataclass
34
+ class FileItem:
35
+ file: BinaryIO
36
+ filename: str | None = None
37
+ filedata: FileData | None = None
38
+
39
+ @staticmethod
40
+ def from_path(path: str, *, filedata: FileData | None = None) -> FileItem:
41
+ # Caller is responsible for lifecycle; loaders may close after upload.
42
+ f = open(path, "rb")
43
+ return FileItem(file=f, filename=path.split("/")[-1], filedata=filedata)
44
+
45
+ @staticmethod
46
+ def from_bytes(data: bytes, *, filename: str | None = None, filedata: FileData | None = None) -> FileItem:
47
+ import io
48
+ return FileItem(file=io.BytesIO(data), filename=filename, filedata=filedata)
49
+
50
+ @staticmethod
51
+ def from_str(text: str, *, filename: str | None = None, filedata: FileData | None = None) -> FileItem:
52
+ import io
53
+ return FileItem(file=io.BytesIO(text.encode("utf-8")), filename=filename, filedata=filedata)
54
+
55
+
56
+ class BaseFileLoader(ABC):
57
+ @abstractmethod
58
+ async def load(self, item: FileItem) -> FileAttachment:
59
+ """Upload a single file and return an attachment reference."""
60
+
61
+ @abstractmethod
62
+ async def load_batch(self, items: list[FileItem]) -> list[FileAttachment]:
63
+ """Upload multiple files; input and output are lists for simplicity."""
@@ -0,0 +1,69 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import io
5
+ import logging
6
+ from collections.abc import Sequence
7
+ from typing import Any
8
+ from typing import BinaryIO
9
+ from typing import Literal
10
+
11
+ from openai import AsyncOpenAI
12
+
13
+ from amsdal_ml.fileio.base_loader import FILE_ID
14
+ from amsdal_ml.fileio.base_loader import BaseFileLoader
15
+ from amsdal_ml.fileio.base_loader import FileAttachment
16
+ from amsdal_ml.fileio.base_loader import FileData
17
+ from amsdal_ml.fileio.base_loader import FileItem
18
+
19
+ logger = logging.getLogger(__name__)
20
+
21
+ AllowedPurpose = Literal["assistants", "batch", "fine-tune", "vision", "user_data", "evals"]
22
+
23
+
24
+ class OpenAIFileLoader(BaseFileLoader):
25
+ """
26
+ Loader which uploads files into OpenAI Files API and returns openai_file_id.
27
+ """
28
+
29
+ def __init__(self, client: AsyncOpenAI, *, purpose: AllowedPurpose = "assistants") -> None:
30
+ self.client = client
31
+ self.purpose: AllowedPurpose = purpose # mypy: Literal union, matches SDK
32
+
33
+ async def _upload_one(self, file: BinaryIO, *, filename: str | None, filedata: FileData | None) -> FileAttachment:
34
+ try:
35
+ if hasattr(file, "seek"):
36
+ file.seek(0)
37
+ except Exception as exc: # pragma: no cover
38
+ logger.debug("seek(0) failed for %r: %s", filename or file, exc)
39
+
40
+ buf = file if isinstance(file, io.BytesIO) else io.BytesIO(file.read())
41
+
42
+ up = await self.client.files.create(file=(filename or "upload.bin", buf), purpose=self.purpose)
43
+
44
+ meta: dict[str, Any] = {
45
+ "filename": filename,
46
+ "provider": "openai",
47
+ "file": {
48
+ "id": up.id,
49
+ "bytes": getattr(up, "bytes", None),
50
+ "purpose": getattr(up, "purpose", self.purpose),
51
+ "created_at": getattr(up, "created_at", None),
52
+ "status": getattr(up, "status", None),
53
+ "status_details": getattr(up, "status_details", None),
54
+ },
55
+ }
56
+ if filedata is not None:
57
+ meta["filedata"] = filedata.model_dump()
58
+
59
+ return FileAttachment(type=FILE_ID, content=up.id, metadata=meta)
60
+
61
+ async def load(self, item: FileItem) -> FileAttachment:
62
+ return await self._upload_one(item.file, filename=item.filename, filedata=item.filedata)
63
+
64
+ async def load_batch(self, items: Sequence[FileItem]) -> list[FileAttachment]:
65
+ tasks = [
66
+ asyncio.create_task(self._upload_one(it.file, filename=it.filename, filedata=it.filedata))
67
+ for it in items
68
+ ]
69
+ return await asyncio.gather(*tasks)
@@ -1,10 +1,12 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import asyncio
4
+ import base64
4
5
  from collections.abc import Iterable
5
6
  from contextlib import AsyncExitStack
6
7
  from typing import Any
7
8
 
9
+ from amsdal_utils.config.manager import AmsdalConfigManager
8
10
  from mcp import ClientSession
9
11
  from mcp import StdioServerParameters
10
12
  from mcp.client.stdio import stdio_client
@@ -19,7 +21,14 @@ class StdioClient(ToolClient):
19
21
  MCP over STDIO client.
20
22
  """
21
23
 
22
- def __init__(self, alias: str, module_or_cmd: str, *args: str, persist_session: bool = True):
24
+ def __init__(
25
+ self,
26
+ alias: str,
27
+ module_or_cmd: str,
28
+ *args: str,
29
+ persist_session: bool = True,
30
+ send_amsdal_config: bool = True,
31
+ ):
23
32
  self.alias = alias
24
33
  if module_or_cmd in ("python", "python3"):
25
34
  self._command = module_or_cmd
@@ -27,7 +36,9 @@ class StdioClient(ToolClient):
27
36
  else:
28
37
  self._command = "python"
29
38
  self._args = ["-m", module_or_cmd]
30
-
39
+ if send_amsdal_config:
40
+ self._args.append("--amsdal-config")
41
+ self._args.append(self._build_amsdal_config_arg())
31
42
  self._persist = persist_session
32
43
  self._lock = asyncio.Lock()
33
44
  self._stack: AsyncExitStack | None = None
@@ -115,6 +126,7 @@ class StdioClient(ToolClient):
115
126
  rx, tx = await stack.enter_async_context(stdio_client(params))
116
127
  s = await stack.enter_async_context(ClientSession(rx, tx))
117
128
  await s.initialize()
129
+ print("Calling tool:", tool_name, "with args:", args) # noqa: T201
118
130
  res = await self._call_with_timeout(s.call_tool(tool_name, args), timeout=timeout)
119
131
  return getattr(res, "content", res)
120
132
 
@@ -128,3 +140,11 @@ class StdioClient(ToolClient):
128
140
  s = await self._ensure_session()
129
141
  res = await self._call_with_timeout(s.call_tool(tool_name, args), timeout=timeout)
130
142
  return getattr(res, "content", res)
143
+
144
+ def _build_amsdal_config_arg(self) -> str:
145
+ """
146
+ Build a JSON string argument representing the current Amsdal configuration.
147
+ This can be passed to the subprocess to ensure it has the same configuration context.
148
+ """
149
+ config = AmsdalConfigManager().get_config()
150
+ return base64.b64encode(config.model_dump_json().encode('utf-8')).decode('utf-8')
@@ -1,11 +1,59 @@
1
+ import argparse
2
+ import asyncio
3
+ import base64
4
+ import json
5
+ import logging
6
+ import sys
7
+ from typing import Any
8
+ from typing import cast
9
+
10
+ from amsdal.manager import AmsdalManager
11
+ from amsdal.manager import AsyncAmsdalManager
12
+ from amsdal_utils.config.data_models.amsdal_config import AmsdalConfig
13
+ from amsdal_utils.config.manager import AmsdalConfigManager
1
14
  from mcp.server.fastmcp import FastMCP
2
15
 
3
16
  from amsdal_ml.agents.retriever_tool import retriever_search
4
17
 
18
+ logging.basicConfig(
19
+ level=logging.INFO,
20
+ format='%(asctime)s [%(levelname)s] %(message)s',
21
+ handlers=[
22
+ logging.FileHandler("server.log"),
23
+ logging.StreamHandler(sys.stdout)
24
+ ]
25
+ )
26
+
27
+ parser = argparse.ArgumentParser()
28
+ parser.add_argument("--amsdal-config", required=False, help="Base64-encoded config string")
29
+ args = parser.parse_args()
30
+
31
+ logging.info(f"Starting server with args: {args}")
32
+
33
+ if args.amsdal_config:
34
+ decoded = base64.b64decode(args.amsdal_config).decode("utf-8")
35
+ amsdal_config = AmsdalConfig(**json.loads(decoded))
36
+ logging.info(f"Loaded Amsdal config: {amsdal_config}")
37
+ AmsdalConfigManager().set_config(amsdal_config)
38
+
39
+ manager: Any
40
+ if amsdal_config.async_mode:
41
+ manager = AsyncAmsdalManager()
42
+ logging.info("pre-setup")
43
+ asyncio.run(cast(Any, manager).setup())
44
+ logging.info("post-setup")
45
+ asyncio.run(cast(Any, manager).post_setup())
46
+ logging.info("manager inited")
47
+ else:
48
+ manager = AmsdalManager()
49
+ cast(Any, manager).setup()
50
+ cast(Any, manager).post_setup()
51
+
5
52
  server = FastMCP('retriever-stdio')
6
53
  server.tool(
7
- name='retriever.search',
54
+ name='search',
8
55
  description='Semantic search in knowledge base (OpenAI embeddings)',
9
56
  structured_output=True,
10
57
  )(retriever_search)
58
+
11
59
  server.run(transport='stdio')
@@ -0,0 +1,36 @@
1
+ from amsdal_models.migration import migrations
2
+ from amsdal_utils.models.enums import ModuleType
3
+
4
+
5
+ class Migration(migrations.Migration):
6
+ operations: list[migrations.Operation] = [
7
+ migrations.CreateClass(
8
+ module_type=ModuleType.CONTRIB,
9
+ class_name="EmbeddingModel",
10
+ new_schema={
11
+ "title": "EmbeddingModel",
12
+ "required": ["object_class", "object_id", "chunk_index", "raw_text", "embedding"],
13
+ "properties": {
14
+ "object_class": {"type": "string", "title": "Linked object class"},
15
+ "object_id": {"type": "string", "title": "Linked object ID"},
16
+ "chunk_index": {"type": "integer", "title": "Chunk index"},
17
+ "raw_text": {"type": "string", "title": "Raw text used for embedding"},
18
+ "embedding": {
19
+ "type": "array",
20
+ "items": {"type": "number"},
21
+ "title": "Embedding",
22
+ "additional_type": "vector",
23
+ "dimensions": 1536,
24
+ },
25
+ "tags": {"type": "array", "items": {"type": "string"}, "title": "Embedding tags"},
26
+ "ml_metadata": {"type": "anything", "title": "ML metadata"},
27
+ },
28
+ "storage_metadata": {
29
+ "table_name": "embedding_model",
30
+ "db_fields": {},
31
+ "primary_key": ["partition_key"],
32
+ "foreign_keys": {},
33
+ },
34
+ },
35
+ ),
36
+ ]
@@ -1,5 +1,11 @@
1
+ from __future__ import annotations
2
+
1
3
  from abc import ABC
2
4
  from abc import abstractmethod
5
+ from collections.abc import AsyncIterator
6
+
7
+ from amsdal_ml.fileio.base_loader import PLAIN_TEXT
8
+ from amsdal_ml.fileio.base_loader import FileAttachment
3
9
 
4
10
 
5
11
  class ModelError(Exception):
@@ -20,31 +26,62 @@ class ModelAPIError(ModelError):
20
26
 
21
27
  class MLModel(ABC):
22
28
  @abstractmethod
23
- def setup(self):
29
+ def setup(self) -> None:
24
30
  """Initialize any clients or resources needed before inference."""
25
- pass
31
+ raise NotImplementedError
26
32
 
27
33
  @abstractmethod
28
- def teardown(self):
34
+ def teardown(self) -> None:
29
35
  """Clean up resources after use."""
30
- pass
36
+ raise NotImplementedError
37
+
38
+ def supported_attachments(self) -> set[str]:
39
+ """Return a set of universal attachment kinds, e.g. {PLAIN_TEXT, FILE_ID}."""
40
+ return {PLAIN_TEXT}
31
41
 
32
42
  @abstractmethod
33
- def invoke(self, prompt: str) -> str:
43
+ def invoke(
44
+ self,
45
+ prompt: str,
46
+ *,
47
+ attachments: list[FileAttachment] | None = None,
48
+ ) -> str:
34
49
  """Run synchronous inference with the model."""
35
- pass
50
+ raise NotImplementedError
36
51
 
37
52
  @abstractmethod
38
- async def ainvoke(self, prompt: str) -> str:
53
+ async def ainvoke(
54
+ self,
55
+ prompt: str,
56
+ *,
57
+ attachments: list[FileAttachment] | None = None,
58
+ ) -> str:
39
59
  """Run asynchronous inference with the model."""
40
- pass
60
+ raise NotImplementedError
41
61
 
42
62
  @abstractmethod
43
- def stream(self, prompt: str):
63
+ def stream(
64
+ self,
65
+ prompt: str,
66
+ *,
67
+ attachments: list[FileAttachment] | None = None,
68
+ ):
44
69
  """Stream synchronous inference results from the model."""
45
- pass
70
+ raise NotImplementedError
46
71
 
47
72
  @abstractmethod
48
- async def astream(self, prompt: str):
49
- """Stream asynchronous inference results from the model."""
50
- pass
73
+ def astream(
74
+ self,
75
+ prompt: str,
76
+ *,
77
+ attachments: list[FileAttachment] | None = None,
78
+ ) -> AsyncIterator[str]:
79
+ """
80
+ Stream asynchronous inference results as an async generator.
81
+
82
+ Subclasses should implement this like:
83
+
84
+ async def astream(... ) -> AsyncIterator[str]:
85
+ yield "chunk"
86
+ """
87
+ raise NotImplementedError
@@ -2,12 +2,21 @@ from __future__ import annotations
2
2
 
3
3
  import asyncio
4
4
  import os
5
+ from collections.abc import AsyncIterator
6
+ from collections.abc import Iterator
7
+ from typing import Any
5
8
  from typing import Optional
9
+ from typing import cast
6
10
 
7
11
  import openai
8
12
  from openai import AsyncOpenAI
13
+ from openai import AsyncStream
9
14
  from openai import OpenAI
15
+ from openai import Stream
10
16
 
17
+ from amsdal_ml.fileio.base_loader import FILE_ID
18
+ from amsdal_ml.fileio.base_loader import PLAIN_TEXT
19
+ from amsdal_ml.fileio.base_loader import FileAttachment
11
20
  from amsdal_ml.ml_config import ml_config
12
21
  from amsdal_ml.ml_models.models import MLModel
13
22
  from amsdal_ml.ml_models.models import ModelAPIError
@@ -17,13 +26,110 @@ from amsdal_ml.ml_models.models import ModelRateLimitError
17
26
 
18
27
 
19
28
  class OpenAIModel(MLModel):
20
- def __init__(self):
29
+ """OpenAI LLM wrapper using a single Responses API pathway for all modes."""
30
+
31
+ def __init__(self) -> None:
21
32
  self.client: Optional[OpenAI | AsyncOpenAI] = None
22
33
  self.async_mode: bool = bool(ml_config.async_mode)
23
34
  self.model_name: str = ml_config.llm_model_name
24
35
  self.temperature: float = ml_config.llm_temperature
25
36
  self._api_key: Optional[str] = None
26
37
 
38
+ # ---------- Public sync API ----------
39
+ def invoke(
40
+ self,
41
+ prompt: str,
42
+ *,
43
+ attachments: list[FileAttachment] | None = None,
44
+ ) -> str:
45
+ if self.async_mode:
46
+ msg = "Async mode is enabled. Use 'ainvoke' instead."
47
+ raise RuntimeError(msg)
48
+ if not isinstance(self.client, OpenAI):
49
+ msg = "Sync client is not initialized. Call setup() first."
50
+ raise RuntimeError(msg)
51
+
52
+ atts = self._validate_attachments(attachments)
53
+ if self._has_file_ids(atts):
54
+ input_content = self._build_input_content(prompt, atts)
55
+ return self._call_responses(input_content)
56
+
57
+ final_prompt = self._merge_plain_text(prompt, atts)
58
+ return self._call_chat(final_prompt)
59
+
60
+ def stream(
61
+ self,
62
+ prompt: str,
63
+ *,
64
+ attachments: list[FileAttachment] | None = None,
65
+ ) -> Iterator[str]:
66
+ if self.async_mode:
67
+ msg = "Async mode is enabled. Use 'astream' instead."
68
+ raise RuntimeError(msg)
69
+ if not isinstance(self.client, OpenAI):
70
+ msg = "Sync client is not initialized. Call setup() first."
71
+ raise RuntimeError(msg)
72
+
73
+ atts = self._validate_attachments(attachments)
74
+ if self._has_file_ids(atts):
75
+ input_content = self._build_input_content(prompt, atts)
76
+ for chunk in self._call_responses_stream(input_content):
77
+ yield chunk
78
+ return
79
+
80
+ final_prompt = self._merge_plain_text(prompt, atts)
81
+ for chunk in self._call_chat_stream(final_prompt):
82
+ yield chunk
83
+
84
+ # ---------- Public async API ----------
85
+ async def ainvoke(
86
+ self,
87
+ prompt: str,
88
+ *,
89
+ attachments: list[FileAttachment] | None = None,
90
+ ) -> str:
91
+ if not self.async_mode:
92
+ msg = "Async mode is disabled. Use 'invoke' instead."
93
+ raise RuntimeError(msg)
94
+ self._ensure_async_client()
95
+ if not isinstance(self.client, AsyncOpenAI):
96
+ msg = "Async client is not initialized. Call setup() first."
97
+ raise RuntimeError(msg)
98
+
99
+ atts = self._validate_attachments(attachments)
100
+ if self._has_file_ids(atts):
101
+ input_content = self._build_input_content(prompt, atts)
102
+ return await self._acall_responses(input_content)
103
+
104
+ final_prompt = self._merge_plain_text(prompt, atts)
105
+ return await self._acall_chat(final_prompt)
106
+
107
+ async def astream(
108
+ self,
109
+ prompt: str,
110
+ *,
111
+ attachments: list[FileAttachment] | None = None,
112
+ ) -> AsyncIterator[str]:
113
+ if not self.async_mode:
114
+ msg = "Async mode is disabled. Use 'stream' instead."
115
+ raise RuntimeError(msg)
116
+ self._ensure_async_client()
117
+ if not isinstance(self.client, AsyncOpenAI):
118
+ msg = "Async client is not initialized. Call setup() first."
119
+ raise RuntimeError(msg)
120
+
121
+ atts = self._validate_attachments(attachments)
122
+ if self._has_file_ids(atts):
123
+ input_content = self._build_input_content(prompt, atts)
124
+ async for chunk in self._acall_responses_stream(input_content):
125
+ yield chunk
126
+ return
127
+
128
+ final_prompt = self._merge_plain_text(prompt, atts)
129
+ async for chunk in self._acall_chat_stream(final_prompt):
130
+ yield chunk
131
+
132
+ # ---------- lifecycle ----------
27
133
  def setup(self) -> None:
28
134
  api_key = os.getenv("OPENAI_API_KEY") or ml_config.resolved_openai_key
29
135
  if not api_key:
@@ -36,6 +142,7 @@ class OpenAIModel(MLModel):
36
142
 
37
143
  try:
38
144
  if self.async_mode:
145
+ # Only create async client if loop is running; otherwise defer.
39
146
  try:
40
147
  asyncio.get_running_loop()
41
148
  self._ensure_async_client()
@@ -43,27 +150,88 @@ class OpenAIModel(MLModel):
43
150
  self.client = None
44
151
  else:
45
152
  self.client = OpenAI(api_key=self._api_key)
46
- except Exception as e:
153
+ except Exception as e: # pragma: no cover
47
154
  raise self._map_openai_error(e) from e
48
155
 
49
156
  def _ensure_async_client(self) -> None:
50
157
  if self.client is None:
51
158
  try:
52
159
  self.client = AsyncOpenAI(api_key=self._api_key)
53
- except Exception as e:
160
+ except Exception as e: # pragma: no cover
54
161
  raise self._map_openai_error(e) from e
55
162
 
56
163
  def teardown(self) -> None:
57
164
  self.client = None
58
165
 
166
+ def _require_sync_client(self) -> OpenAI:
167
+ if not isinstance(self.client, OpenAI):
168
+ msg = "Sync client is not initialized. Call setup() first."
169
+ raise RuntimeError(msg)
170
+ return self.client
171
+
172
+ def _require_async_client(self) -> AsyncOpenAI:
173
+ if not isinstance(self.client, AsyncOpenAI):
174
+ msg = "Async client is not initialized. Call setup() first."
175
+ raise RuntimeError(msg)
176
+ return self.client
177
+
178
+ # ---------- attachments ----------
179
+ def supported_attachments(self) -> set[str]:
180
+ # Universal kinds supported by this model
181
+ return {PLAIN_TEXT, FILE_ID}
182
+
183
+ def _validate_attachments(
184
+ self, attachments: list[FileAttachment] | None
185
+ ) -> list[FileAttachment]:
186
+ atts = attachments or []
187
+ kinds = {a.type for a in atts}
188
+ unsupported = kinds - self.supported_attachments()
189
+ if unsupported:
190
+ msg = (
191
+ f"{self.__class__.__name__} does not support attachments: "
192
+ f"{', '.join(sorted(unsupported))}"
193
+ )
194
+ raise ModelAPIError(msg)
195
+
196
+ foreign = [
197
+ a for a in atts if a.type == FILE_ID and (a.metadata or {}).get("provider") != "openai"
198
+ ]
199
+ if foreign:
200
+ provs = {(a.metadata or {}).get("provider", "unknown") for a in foreign}
201
+ msg = (
202
+ f"{self.__class__.__name__} only supports FILE_ID with provider='openai'. "
203
+ f"Got providers: {', '.join(sorted(provs))}"
204
+ )
205
+ raise ModelAPIError(msg)
206
+
207
+ return atts
208
+
209
+ @staticmethod
210
+ def _has_file_ids(atts: list[FileAttachment]) -> bool:
211
+ return any(a.type == FILE_ID for a in atts)
212
+
213
+ def _build_input_content(self, prompt: str, atts: list[FileAttachment]) -> list[dict[str, Any]]:
214
+ parts: list[dict[str, Any]] = [{"type": "input_text", "text": prompt}]
215
+ for a in atts:
216
+ if a.type == PLAIN_TEXT:
217
+ parts.append({"type": "input_text", "text": str(a.content)})
218
+ elif a.type == FILE_ID:
219
+ parts.append({"type": "input_file", "file_id": str(a.content)})
220
+ return [{"role": "user", "content": parts}]
221
+
222
+ def _merge_plain_text(self, prompt: str, atts: list[FileAttachment]) -> str:
223
+ extras = [str(a.content) for a in atts if a.type == PLAIN_TEXT]
224
+ if not extras:
225
+ return prompt
226
+ return f"{prompt}\n\n[ATTACHMENTS]\n" + "\n\n".join(extras)
227
+
228
+ # ---------- error mapping ----------
59
229
  @staticmethod
60
230
  def _map_openai_error(err: Exception) -> ModelError:
61
231
  if isinstance(err, openai.RateLimitError):
62
232
  return ModelRateLimitError(str(err))
63
-
64
233
  if isinstance(err, openai.APIConnectionError):
65
234
  return ModelConnectionError(str(err))
66
-
67
235
  if isinstance(err, openai.APIStatusError):
68
236
  status = getattr(err, "status_code", None)
69
237
  resp = getattr(err, "response", None)
@@ -72,26 +240,16 @@ class OpenAIModel(MLModel):
72
240
  payload_repr = resp.json() if resp is not None else None
73
241
  except Exception:
74
242
  payload_repr = None
75
- msg = f"OpenAI API status error ({status}). payload={payload_repr!r}"
76
- return ModelAPIError(msg)
77
-
243
+ return ModelAPIError(f"OpenAI API status error ({status}). payload={payload_repr!r}")
78
244
  if isinstance(err, openai.APIError):
79
245
  return ModelAPIError(str(err))
80
-
81
246
  return ModelAPIError(str(err))
82
247
 
83
- # ---------- Sync ----------
84
- def invoke(self, prompt: str) -> str:
85
- if self.async_mode:
86
- msg = "Async mode is enabled. Use 'ainvoke' instead."
87
- raise RuntimeError(msg)
88
-
89
- if not isinstance(self.client, OpenAI):
90
- msg = "Sync client is not initialized. Call setup() first."
91
- raise RuntimeError(msg)
92
-
248
+ # ---------- Sync core callers ----------
249
+ def _call_chat(self, prompt: str) -> str:
250
+ client = self._require_sync_client()
93
251
  try:
94
- resp = self.client.chat.completions.create(
252
+ resp = client.chat.completions.create(
95
253
  model=self.model_name,
96
254
  messages=[{"role": "user", "content": prompt}],
97
255
  temperature=self.temperature,
@@ -100,17 +258,10 @@ class OpenAIModel(MLModel):
100
258
  except Exception as e:
101
259
  raise self._map_openai_error(e) from e
102
260
 
103
- def stream(self, prompt: str):
104
- if self.async_mode:
105
- msg = "Async mode is enabled. Use 'astream' instead."
106
- raise RuntimeError(msg)
107
-
108
- if not isinstance(self.client, OpenAI):
109
- msg = "Sync client is not initialized. Call setup() first."
110
- raise RuntimeError(msg)
111
-
261
+ def _call_chat_stream(self, prompt: str) -> Iterator[str]:
262
+ client = self._require_sync_client()
112
263
  try:
113
- stream = self.client.chat.completions.create(
264
+ stream = client.chat.completions.create(
114
265
  model=self.model_name,
115
266
  messages=[{"role": "user", "content": prompt}],
116
267
  temperature=self.temperature,
@@ -123,18 +274,43 @@ class OpenAIModel(MLModel):
123
274
  except Exception as e:
124
275
  raise self._map_openai_error(e) from e
125
276
 
126
- # ---------- Async ----------
127
- async def ainvoke(self, prompt: str) -> str:
128
- if not self.async_mode:
129
- msg = "Async mode is disabled. Use 'invoke' instead."
130
- raise RuntimeError(msg)
277
+ def _call_responses(self, input_content: list[dict[str, Any]]) -> str:
278
+ client = self._require_sync_client()
279
+ try:
280
+ resp: Any = client.responses.create(
281
+ model=self.model_name,
282
+ input=cast(Any, input_content),
283
+ temperature=self.temperature,
284
+ )
285
+ return (getattr(resp, "output_text", None) or "").strip()
286
+ except Exception as e:
287
+ raise self._map_openai_error(e) from e
131
288
 
132
- self._ensure_async_client()
133
- if not isinstance(self.client, AsyncOpenAI):
134
- msg = "Async client is not initialized. Call setup() first."
135
- raise RuntimeError(msg)
289
+ def _call_responses_stream(self, input_content: list[dict[str, Any]]) -> Iterator[str]:
290
+ client = self._require_sync_client()
291
+ try:
292
+ stream_or_resp = client.responses.create(
293
+ model=self.model_name,
294
+ input=cast(Any, input_content),
295
+ temperature=self.temperature,
296
+ stream=True,
297
+ )
298
+ if isinstance(stream_or_resp, Stream):
299
+ for ev in stream_or_resp:
300
+ delta = getattr(getattr(ev, "delta", None), "content", None)
301
+ if delta:
302
+ yield delta
303
+ else:
304
+ text = (getattr(stream_or_resp, "output_text", None) or "").strip()
305
+ if text:
306
+ yield text
307
+ except Exception as e:
308
+ raise self._map_openai_error(e) from e
136
309
 
137
- client = self.client
310
+ # ---------- Async core callers ----------
311
+ async def _acall_chat(self, prompt: str) -> str:
312
+ client = self._require_async_client()
313
+ print("acall_chat:", prompt) # noqa: T201
138
314
  try:
139
315
  resp = await client.chat.completions.create(
140
316
  model=self.model_name,
@@ -145,17 +321,8 @@ class OpenAIModel(MLModel):
145
321
  except Exception as e:
146
322
  raise self._map_openai_error(e) from e
147
323
 
148
- async def astream(self, prompt: str):
149
- if not self.async_mode:
150
- msg = "Async mode is disabled. Use 'stream' instead."
151
- raise RuntimeError(msg)
152
-
153
- self._ensure_async_client()
154
- if not isinstance(self.client, AsyncOpenAI):
155
- msg = "Async client is not initialized. Call setup() first."
156
- raise RuntimeError(msg)
157
-
158
- client = self.client
324
+ async def _acall_chat_stream(self, prompt: str) -> AsyncIterator[str]:
325
+ client = self._require_async_client()
159
326
  try:
160
327
  stream = await client.chat.completions.create(
161
328
  model=self.model_name,
@@ -169,3 +336,36 @@ class OpenAIModel(MLModel):
169
336
  yield delta.content
170
337
  except Exception as e:
171
338
  raise self._map_openai_error(e) from e
339
+
340
+ async def _acall_responses(self, input_content: list[dict[str, Any]]) -> str:
341
+ client = self._require_async_client()
342
+ try:
343
+ resp: Any = await client.responses.create(
344
+ model=self.model_name,
345
+ input=cast(Any, input_content),
346
+ temperature=self.temperature,
347
+ )
348
+ return (getattr(resp, "output_text", None) or "").strip()
349
+ except Exception as e:
350
+ raise self._map_openai_error(e) from e
351
+
352
+ async def _acall_responses_stream(self, input_content: list[dict[str, Any]]) -> AsyncIterator[str]:
353
+ client = self._require_async_client()
354
+ try:
355
+ stream_or_resp = await client.responses.create(
356
+ model=self.model_name,
357
+ input=cast(Any, input_content),
358
+ temperature=self.temperature,
359
+ stream=True,
360
+ )
361
+ if isinstance(stream_or_resp, AsyncStream):
362
+ async for ev in stream_or_resp:
363
+ delta = getattr(getattr(ev, "delta", None), "content", None)
364
+ if delta:
365
+ yield delta
366
+ else:
367
+ text = (getattr(stream_or_resp, "output_text", None) or "").strip()
368
+ if text:
369
+ yield text
370
+ except Exception as e:
371
+ raise self._map_openai_error(e) from e
@@ -53,7 +53,7 @@ class DefaultRetriever(MLRetriever, ABC):
53
53
  )
54
54
 
55
55
  pre = max(k * 5, 100)
56
- rows = list(qs.execute())[:pre]
56
+ rows = list(qs[:pre].execute())
57
57
 
58
58
  rows = self._filter_rows_by_tags(rows, include_tags, exclude_tags)
59
59
  rows = rows[:k]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: amsdal_ml
3
- Version: 0.1.0
3
+ Version: 0.1.1
4
4
  Summary: amsdal_ml plugin for AMSDAL Framework
5
5
  Requires-Python: >=3.11
6
6
  Requires-Dist: aiohttp==3.12.15
@@ -1,35 +1,39 @@
1
1
  amsdal_ml/Third-Party Materials - AMSDAL Dependencies - License Notices.md,sha256=0A3jjw91I-NaTHXCw8IQiKUdFJgVavhgHaFX4Nag4jk,81658
2
- amsdal_ml/__about__.py,sha256=IMjkMO3twhQzluVTo8Z6rE7Eg-9U79_LGKMcsWLKBkY,22
2
+ amsdal_ml/__about__.py,sha256=ls1camlIoMxEZz9gSkZ1OJo-MXqHWwKPtdPbZJmwp7E,22
3
3
  amsdal_ml/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
4
  amsdal_ml/app.py,sha256=yq_XIstjXSKplsjHLn9Jb65zXx4HOdjPKVlbz5FT9ec,715
5
5
  amsdal_ml/ml_config.py,sha256=UONMVG2RbKVbaYF-GdlhRQbm8flp52GxBzfqdzuLZ5w,1788
6
6
  amsdal_ml/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
7
  amsdal_ml/agents/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
- amsdal_ml/agents/agent.py,sha256=Eb_jCSIZy2tX2dAPqa65iUd4aE26g8V3G76gAbiTub0,1123
9
- amsdal_ml/agents/default_qa_agent.py,sha256=8Y02ovsWKRVBNywRziBYtPKBQjJL0lKL5TCrJRIruWY,13375
10
- amsdal_ml/agents/retriever_tool.py,sha256=EzmjyN4CaRGue5DHsEQ0GbxVohx6XEhvOtPl9DZkXaU,1276
8
+ amsdal_ml/agents/agent.py,sha256=L00RTIkopdGnJp_NctEtWRXAgWy1DAn7AEg0rMoXdJw,1514
9
+ amsdal_ml/agents/default_qa_agent.py,sha256=MjrA5XYOKe5PwfSnkoeGmEnC8mP9asRHGHPHd1oPH6g,14017
10
+ amsdal_ml/agents/retriever_tool.py,sha256=SqIGqYi_xMcm_MgLcwdMW2ALzYphkk02KBevH8n-Op0,1850
11
11
  amsdal_ml/agents/promts/__init__.py,sha256=3AKR3lrV067CO03XYFDmAKJv0x2oqSTcVeDIqNwoAu0,1571
12
- amsdal_ml/agents/promts/react_chat.prompt,sha256=vw80QrkwMq-tRWgvWhMQB8NV1KVeo5yZLO-DwH2F96k,927
12
+ amsdal_ml/agents/promts/react_chat.prompt,sha256=EArdA102sSBOWchhub1QoaKws_DVpMqDflc5htBkFKI,1762
13
+ amsdal_ml/fileio/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
+ amsdal_ml/fileio/base_loader.py,sha256=rFeClp-ZkSX6Ja1vz1RutlyMqYIGgR48CrtXDWXr4Ns,1866
15
+ amsdal_ml/fileio/openai_loader.py,sha256=4YyxK5pBAnRm55et5yMu6X5OdK2kWTqpuPF1E2-oFZc,2572
13
16
  amsdal_ml/mcp_client/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
17
  amsdal_ml/mcp_client/base.py,sha256=cNuHHCSSND4BkIyhQZ-1mJLPp630wTCtiCQSHnK2zmA,441
15
18
  amsdal_ml/mcp_client/http_client.py,sha256=2fFCaubp_cFeyCqN9CP7bVLTp4UIIEhJPq9BVHBf6-w,1719
16
- amsdal_ml/mcp_client/stdio_client.py,sha256=9dNrFDiQceEFBknfrSCGnZirDRCwwRuNL2eezPz84AM,4988
19
+ amsdal_ml/mcp_client/stdio_client.py,sha256=bYfS2yCeJNl8fxBn6Yerg4RBZKbW2pgcdKiwX-NL6io,5744
17
20
  amsdal_ml/mcp_server/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
18
- amsdal_ml/mcp_server/server_retriever_stdio.py,sha256=1CFZLmB-7mRsNNIxfqzz3-MXZttAE3qZeRkz2l0-kX0,331
21
+ amsdal_ml/mcp_server/server_retriever_stdio.py,sha256=iUQNwWEDZE0OIS9vyTyJp_EOn4kvl9Gn0Okzcq-ymo0,1762
22
+ amsdal_ml/migrations/0000_initial.py,sha256=vbgDcBK3_CI0jUn0sl0e4_XdDncBHAQ7T4x2FZZkGnQ,1613
19
23
  amsdal_ml/ml_ingesting/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
20
24
  amsdal_ml/ml_ingesting/default_ingesting.py,sha256=Hg5D_CjIZJAxH8juw42R9i-OyUlL0qzhbn6R43ETkA4,12839
21
25
  amsdal_ml/ml_ingesting/embedding_data.py,sha256=ZWxhYq8mkFVC3HMM5nEiObgy62X8hLh1McaGegnRUsk,355
22
26
  amsdal_ml/ml_ingesting/ingesting.py,sha256=h-GdNCtutO8C75jbLko40CMjHym9Xk_nlMdNYLfBs58,1458
23
27
  amsdal_ml/ml_ingesting/openai_ingesting.py,sha256=_2dU2id2a3xR_y0GvZc5LUDw3_wI-wbhEFogEOKF-H0,1421
24
28
  amsdal_ml/ml_models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
25
- amsdal_ml/ml_models/models.py,sha256=8MZLZygR5AbthJyXQtmV-c6ZD2JQ6RgfkzP1cP2N1kM,1250
26
- amsdal_ml/ml_models/openai_model.py,sha256=4bk5lZVy_C23iLDvS_VYQ_mdGvDwZ4IjWYrexc3PHTU,6042
29
+ amsdal_ml/ml_models/models.py,sha256=gZzZrjsoAnFc5pPwqqLPNh5RYxwKKaFyNg56NJnsn4w,2286
30
+ amsdal_ml/ml_models/openai_model.py,sha256=yQ6r4K7AOeG3NfWT6pXgjkRPMT0FuQPqFUWeHSwz0u8,14295
27
31
  amsdal_ml/ml_retrievers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
28
- amsdal_ml/ml_retrievers/default_retriever.py,sha256=OSvE3LWgy30dua4j5FBIhooGOD90VvK2wcjgvW6FQho,3213
32
+ amsdal_ml/ml_retrievers/default_retriever.py,sha256=ncB9jnvnEfw2j58mINn9vs34pfTHE838cs30SjGkt7Y,3213
29
33
  amsdal_ml/ml_retrievers/openai_retriever.py,sha256=oY71oPJdFuECWaJL_ByKZFE7HRJ-S6ypRzzJ7XjI7H8,1236
30
34
  amsdal_ml/ml_retrievers/retriever.py,sha256=zHvaAOAD6H4IaIaYA_wy2e8tMmGtOnyNsDS2B4dQjBU,1005
31
35
  amsdal_ml/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
32
36
  amsdal_ml/models/embedding_model.py,sha256=26sLxJ1VPbW0ZuY0mQOfHm_ADcKNzxq9_ffJ91SzHg8,764
33
- amsdal_ml-0.1.0.dist-info/METADATA,sha256=Bvngk7esO1e-zkc8lmOv8IcEGmZdncWyOLRLz-5vIN4,1676
34
- amsdal_ml-0.1.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
35
- amsdal_ml-0.1.0.dist-info/RECORD,,
37
+ amsdal_ml-0.1.1.dist-info/METADATA,sha256=doT-XMQ_fMnwVg2Wi1hwuIN_mUIVQYgRLYIBwZYmEDg,1676
38
+ amsdal_ml-0.1.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
39
+ amsdal_ml-0.1.1.dist-info/RECORD,,