parcle 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,19 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ .eggs/
6
+ build/
7
+ dist/
8
+
9
+ # Virtual environments
10
+ .venv
11
+ venv
12
+ env
13
+
14
+ # Tooling caches
15
+ .pytest_cache/
16
+ .mypy_cache/
17
+ .ruff_cache/
18
+ .coverage
19
+ htmlcov/
parcle-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Parcle
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
parcle-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,91 @@
1
+ Metadata-Version: 2.4
2
+ Name: parcle
3
+ Version: 0.1.0
4
+ Summary: Long-term memory for AI agents — a Python client for the Parcle Memory API.
5
+ Project-URL: Homepage, https://parcle.ai
6
+ Project-URL: Documentation, https://api.parcle.ai
7
+ Author: Parcle
8
+ License: MIT
9
+ License-File: LICENSE
10
+ Keywords: agents,ai,llm,memory,parcle,rag
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Typing :: Typed
21
+ Requires-Python: >=3.9
22
+ Requires-Dist: httpx>=0.24
23
+ Provides-Extra: dev
24
+ Requires-Dist: pytest>=7; extra == 'dev'
25
+ Requires-Dist: respx>=0.20; extra == 'dev'
26
+ Description-Content-Type: text/markdown
27
+
28
+ <div align="center">
29
+
30
+ # Parcle
31
+
32
+ **Long-term memory for AI agents**
33
+
34
+ Ingest conversations and files, then ask questions in natural language and get
35
+ cited answers back. Give every user a private, persistent agent memory.
36
+
37
+ </div>
38
+
39
+ ---
40
+
41
+ ## Why Parcle?
42
+
43
+ LLMs forget everything between calls. Parcle gives every user a private memory you
44
+ can write to and search:
45
+
46
+ - 🧠 **Per-user memory** — scope everything to a `user_id`.
47
+ - 💬 **Ingest anything** — chat transcripts and files (PDF, Markdown, text, …) go in the same place.
48
+ - 🔎 **Ask, don't query** — search returns a synthesized **answer** with **citations**, not just raw chunks.
49
+
50
+ ## Installation
51
+
52
+ ```bash
53
+ pip install parcle
54
+ ```
55
+
56
+ ## Quickstart
57
+
58
+ ```python
59
+ from parcle import Parcle
60
+
61
+ # Reads PARCLE_API_KEY from the environment if api_key is omitted.
62
+ client = Parcle(api_key="pk_live_...")
63
+
64
+ # 1. Write a conversation into a user's memory.
65
+ # Ingestion is incremental: omit session_id to start a new session, then
66
+ # pass the returned session_id back to append more turns to the same one.
67
+ result = client.ingest_dialog(
68
+ user_id="ada",
69
+ messages=[
70
+ {"role": "user", "content": "I'm allergic to peanuts."},
71
+ {"role": "assistant", "content": "Got it — I'll avoid peanuts in suggestions."},
72
+ ],
73
+ )
74
+ client.ingest_dialog(
75
+ user_id="ada",
76
+ session_id=result.session_id, # append to the same session
77
+ messages=[
78
+ {"role": "user", "content": "Also, I don't eat shellfish."},
79
+ ],
80
+ )
81
+
82
+ # 2. ...or ingest a file (PDF, Markdown, text, …).
83
+ client.ingest_file(user_id="ada", file="diet-notes.pdf")
84
+
85
+ # 3. Ask a question. You get an answer with confidence and citations.
86
+ result = client.search(user_id="ada", query="What food should I avoid?")
87
+
88
+ print(result.answer) # "You're allergic to peanuts, so avoid them."
89
+ print(result.confidence) # 0.92
90
+ print(result.citations) # [Citation(type="session", id="...")]
91
+ ```
parcle-0.1.0/README.md ADDED
@@ -0,0 +1,64 @@
1
+ <div align="center">
2
+
3
+ # Parcle
4
+
5
+ **Long-term memory for AI agents**
6
+
7
+ Ingest conversations and files, then ask questions in natural language and get
8
+ cited answers back. Give every user a private, persistent agent memory.
9
+
10
+ </div>
11
+
12
+ ---
13
+
14
+ ## Why Parcle?
15
+
16
+ LLMs forget everything between calls. Parcle gives every user a private memory you
17
+ can write to and search:
18
+
19
+ - 🧠 **Per-user memory** — scope everything to a `user_id`.
20
+ - 💬 **Ingest anything** — chat transcripts and files (PDF, Markdown, text, …) go in the same place.
21
+ - 🔎 **Ask, don't query** — search returns a synthesized **answer** with **citations**, not just raw chunks.
22
+
23
+ ## Installation
24
+
25
+ ```bash
26
+ pip install parcle
27
+ ```
28
+
29
+ ## Quickstart
30
+
31
+ ```python
32
+ from parcle import Parcle
33
+
34
+ # Reads PARCLE_API_KEY from the environment if api_key is omitted.
35
+ client = Parcle(api_key="pk_live_...")
36
+
37
+ # 1. Write a conversation into a user's memory.
38
+ # Ingestion is incremental: omit session_id to start a new session, then
39
+ # pass the returned session_id back to append more turns to the same one.
40
+ result = client.ingest_dialog(
41
+ user_id="ada",
42
+ messages=[
43
+ {"role": "user", "content": "I'm allergic to peanuts."},
44
+ {"role": "assistant", "content": "Got it — I'll avoid peanuts in suggestions."},
45
+ ],
46
+ )
47
+ client.ingest_dialog(
48
+ user_id="ada",
49
+ session_id=result.session_id, # append to the same session
50
+ messages=[
51
+ {"role": "user", "content": "Also, I don't eat shellfish."},
52
+ ],
53
+ )
54
+
55
+ # 2. ...or ingest a file (PDF, Markdown, text, …).
56
+ client.ingest_file(user_id="ada", file="diet-notes.pdf")
57
+
58
+ # 3. Ask a question. You get an answer with confidence and citations.
59
+ result = client.search(user_id="ada", query="What food should I avoid?")
60
+
61
+ print(result.answer) # "You're allergic to peanuts, so avoid them."
62
+ print(result.confidence) # 0.92
63
+ print(result.citations) # [Citation(type="session", id="...")]
64
+ ```
@@ -0,0 +1,90 @@
1
+ """End-to-end example against the live Parcle Memory API.
2
+
3
+ Run it with the SDK installed (``pip install -e .``)::
4
+
5
+ python example.py
6
+
7
+ It walks the full memory lifecycle for one user: create the user, ingest a
8
+ dialog and a file, wait for both to become searchable, ask a question, then
9
+ inspect and clean up the stored sources.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import os
15
+ import tempfile
16
+
17
+ from parcle import Parcle
18
+
19
+ # The API key and base URL. Prefer env vars; fall back to the literals so this
20
+ # example is runnable as-is. This key lives on the backend.parcle.ai environment.
21
+ API_KEY = os.environ.get("PARCLE_API_KEY", "pmem_9je7QBamPK9b3a0qFXCvCMkk5U9iQaIrkGp4lMyi9Kg")
22
+ BASE_URL = os.environ.get("PARCLE_BASE_URL", "https://backend.parcle.ai")
23
+
24
+ USER_ID = "ada"
25
+
26
+
27
+ def main() -> None:
28
+ # Search synthesizes an answer with an LLM, so allow a generous timeout.
29
+ with Parcle(api_key=API_KEY, base_url=BASE_URL, timeout=120.0) as client:
30
+ # 1. Create (or update) the user this memory belongs to.
31
+ user = client.create_user(USER_ID, name="Ada", timezone="America/New_York")
32
+ print(f"user: {user.user_id} (is_new={user.is_new})")
33
+
34
+ # 2. Ingest a conversation. Ingestion is incremental: keep the
35
+ # session_id to append more turns to the same session later.
36
+ dialog = client.ingest_dialog(
37
+ user_id=USER_ID,
38
+ messages=[
39
+ {"role": "user", "content": "I'm allergic to peanuts."},
40
+ {"role": "assistant", "content": "Got it — I'll avoid peanuts."},
41
+ ],
42
+ tag={"app": "meal-planner"},
43
+ )
44
+ print(f"dialog: session={dialog.session_id} event={dialog.event_id}")
45
+
46
+ client.ingest_dialog(
47
+ user_id=USER_ID,
48
+ session_id=dialog.session_id, # append to the same session
49
+ messages=[{"role": "user", "content": "Also, I don't eat shellfish."}],
50
+ )
51
+
52
+ # 3. Ingest a file into the same memory.
53
+ with tempfile.NamedTemporaryFile(
54
+ "w", suffix=".md", delete=False, encoding="utf-8"
55
+ ) as fh:
56
+ fh.write("# Diet notes\n\nVegetarian. Avoid nuts and shellfish.\n")
57
+ notes_path = fh.name
58
+ try:
59
+ uploaded = client.ingest_file(
60
+ user_id=USER_ID, file=notes_path, tag={"source": "diet-notes"}
61
+ )
62
+ print(f"file: id={uploaded.file_id} event={uploaded.event_id}")
63
+
64
+ # 4. Wait until both writes are searchable.
65
+ client.wait_until_ready(USER_ID, dialog.event_id)
66
+ client.wait_until_ready(USER_ID, uploaded.event_id)
67
+ print("ingestion ready")
68
+
69
+ # 5. Ask a question — you get an answer with confidence + citations.
70
+ result = client.search(
71
+ user_id=USER_ID,
72
+ query="What food should I avoid?",
73
+ tag_filter={"app": "meal-planner"},
74
+ )
75
+ print(f"\nanswer: {result.answer}")
76
+ print(f"confidence: {result.confidence}")
77
+ for c in result.citations:
78
+ print(f"citation: {c.type} {c.id}")
79
+
80
+ # 6. Inspect what's stored for this user.
81
+ print("\nsources:")
82
+ for source in client.iter_sources(USER_ID):
83
+ label = source.name or source.id
84
+ print(f" - {source.type}: {label} tag={source.tag}")
85
+ finally:
86
+ os.unlink(notes_path)
87
+
88
+
89
+ if __name__ == "__main__":
90
+ main()
@@ -0,0 +1,79 @@
1
+ """Parcle — long-term memory for AI agents.
2
+
3
+ Ingest conversations and files into a per-user memory, then ask questions in
4
+ natural language and get cited answers back.
5
+
6
+ from parcle import Parcle
7
+
8
+ client = Parcle(api_key="pk_live_...")
9
+ client.ingest_dialog(user_id="ada", messages=[{"role": "user", "content": "..."}])
10
+ result = client.search(user_id="ada", query="What food should I avoid?")
11
+ print(result.answer, result.confidence, result.citations)
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ from .client import Parcle
17
+ from .exceptions import (
18
+ AuthenticationError,
19
+ FileTooLargeError,
20
+ InternalServerError,
21
+ InvalidRequestError,
22
+ NotFoundError,
23
+ ParcleAPIError,
24
+ ParcleConfigError,
25
+ ParcleConnectionError,
26
+ ParcleError,
27
+ ParcleTimeoutError,
28
+ RateLimitError,
29
+ ServiceUnavailableError,
30
+ UnsupportedFileTypeError,
31
+ ValidationError,
32
+ )
33
+ from .models import (
34
+ Citation,
35
+ DeleteResult,
36
+ Event,
37
+ IngestDialogResult,
38
+ IngestFileResult,
39
+ Message,
40
+ SearchResult,
41
+ Session,
42
+ Source,
43
+ SourcesPage,
44
+ User,
45
+ )
46
+
47
+ __version__ = "0.1.0"
48
+
49
+ __all__ = [
50
+ "Parcle",
51
+ "__version__",
52
+ # models
53
+ "Citation",
54
+ "DeleteResult",
55
+ "Event",
56
+ "IngestDialogResult",
57
+ "IngestFileResult",
58
+ "Message",
59
+ "SearchResult",
60
+ "Session",
61
+ "Source",
62
+ "SourcesPage",
63
+ "User",
64
+ # exceptions
65
+ "ParcleError",
66
+ "ParcleConfigError",
67
+ "ParcleConnectionError",
68
+ "ParcleTimeoutError",
69
+ "ParcleAPIError",
70
+ "InvalidRequestError",
71
+ "AuthenticationError",
72
+ "NotFoundError",
73
+ "FileTooLargeError",
74
+ "UnsupportedFileTypeError",
75
+ "ValidationError",
76
+ "RateLimitError",
77
+ "InternalServerError",
78
+ "ServiceUnavailableError",
79
+ ]