pipecat-memorysync 1.0.0__tar.gz → 1.0.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.
- pipecat_memorysync-1.0.1/CHANGELOG.md +25 -0
- {pipecat_memorysync-1.0.0 → pipecat_memorysync-1.0.1}/PKG-INFO +37 -6
- {pipecat_memorysync-1.0.0 → pipecat_memorysync-1.0.1}/README.md +34 -4
- pipecat_memorysync-1.0.1/examples/foundational.py +128 -0
- {pipecat_memorysync-1.0.0 → pipecat_memorysync-1.0.1}/pyproject.toml +2 -1
- {pipecat_memorysync-1.0.0 → pipecat_memorysync-1.0.1}/src/pipecat_memorysync/_version.py +1 -1
- {pipecat_memorysync-1.0.0 → pipecat_memorysync-1.0.1}/tests/test_service.py +9 -1
- {pipecat_memorysync-1.0.0 → pipecat_memorysync-1.0.1}/.gitignore +0 -0
- {pipecat_memorysync-1.0.0 → pipecat_memorysync-1.0.1}/src/pipecat_memorysync/__init__.py +0 -0
- {pipecat_memorysync-1.0.0 → pipecat_memorysync-1.0.1}/src/pipecat_memorysync/_api.py +0 -0
- {pipecat_memorysync-1.0.0 → pipecat_memorysync-1.0.1}/src/pipecat_memorysync/service.py +0 -0
- {pipecat_memorysync-1.0.0 → pipecat_memorysync-1.0.1}/tests/conftest.py +0 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `pipecat-memorysync` are documented here.
|
|
4
|
+
|
|
5
|
+
## 1.0.1 — 2026-08-29
|
|
6
|
+
|
|
7
|
+
- Metadata only: the source repository moved to
|
|
8
|
+
`https://github.com/memorysyncio/memorysync-pipecat`; project URLs updated.
|
|
9
|
+
No code changes.
|
|
10
|
+
|
|
11
|
+
## 1.0.0 — 2026-08-29
|
|
12
|
+
|
|
13
|
+
Initial release.
|
|
14
|
+
|
|
15
|
+
- `MemorySyncMemoryService`, a `FrameProcessor` for the position between the
|
|
16
|
+
user context aggregator and the LLM service.
|
|
17
|
+
- Budgeted memory recall (default 1.2 s hard timeout) injected into every
|
|
18
|
+
`LLMContextFrame` as a guarded system message — a slow or unreachable
|
|
19
|
+
backend degrades to an unenriched frame, never a stalled reply.
|
|
20
|
+
- Delta-only capture with deterministic idempotency seeds: only turns not
|
|
21
|
+
seen before are persisted, with role fidelity for user and assistant.
|
|
22
|
+
- Injection exclusion: the injected memory block is never re-captured.
|
|
23
|
+
- Graceful `EndFrame` flush (bounded 3 s window) and `CancelFrame` salvage.
|
|
24
|
+
- Tested with Pipecat v1.8.1 through `pipecat.tests.utils.run_test`
|
|
25
|
+
(14-test suite).
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: pipecat-memorysync
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.1
|
|
4
4
|
Summary: MemorySync for Pipecat: budgeted memory recall that never stalls a voice reply, delta-only conversation persistence with idempotency seeds, and a pipeline that cannot be broken by a memory outage.
|
|
5
5
|
Project-URL: Homepage, https://docs.memorysync.io/guides/pipecat
|
|
6
6
|
Project-URL: Documentation, https://docs.memorysync.io/guides/pipecat
|
|
7
|
-
Project-URL: Repository, https://github.com/
|
|
7
|
+
Project-URL: Repository, https://github.com/memorysyncio/memorysync-pipecat
|
|
8
|
+
Project-URL: Changelog, https://github.com/memorysyncio/memorysync-pipecat/blob/main/CHANGELOG.md
|
|
8
9
|
Author-email: MemorySync <support@memorysync.io>
|
|
9
10
|
License-Expression: MIT
|
|
10
11
|
Keywords: agents,long-term-memory,memory,memorysync,pipecat,voice
|
|
@@ -21,14 +22,22 @@ Requires-Dist: httpx<1,>=0.25
|
|
|
21
22
|
Requires-Dist: pipecat-ai<2,>=1.0.0
|
|
22
23
|
Description-Content-Type: text/markdown
|
|
23
24
|
|
|
24
|
-
# pipecat
|
|
25
|
+
# memorysync-pipecat
|
|
25
26
|
|
|
26
27
|
[MemorySync](https://memorysync.io) for [Pipecat](https://github.com/pipecat-ai/pipecat) —
|
|
27
28
|
long-term memory for voice pipelines that never stalls a reply and never
|
|
28
|
-
re-stores what it already knows.
|
|
29
|
+
re-stores what it already knows. PyPI package: **`pipecat-memorysync`**.
|
|
30
|
+
|
|
31
|
+
Built and maintained by the [MemorySync](https://memorysync.io) team —
|
|
32
|
+
MemorySync is our product, and this integration is actively maintained
|
|
33
|
+
alongside it.
|
|
34
|
+
|
|
35
|
+
**Tested with Pipecat v1.8.1** (`pipecat-ai>=1.0.0,<2`).
|
|
29
36
|
|
|
30
37
|
```bash
|
|
31
38
|
pip install pipecat-memorysync
|
|
39
|
+
# or
|
|
40
|
+
uv add pipecat-memorysync
|
|
32
41
|
```
|
|
33
42
|
|
|
34
43
|
## Where it sits
|
|
@@ -74,8 +83,7 @@ enriched or not, on time.
|
|
|
74
83
|
stalled voice reply.
|
|
75
84
|
- **Delta-only capture.** Only messages *not seen before* are stored, tracked
|
|
76
85
|
by deterministic idempotency seeds. Growing a 50-message context does not
|
|
77
|
-
re-store 50 messages per turn
|
|
78
|
-
which re-send the entire context every frame — O(n²) writes per call).
|
|
86
|
+
re-store 50 messages per turn.
|
|
79
87
|
- **Injection exclusion.** The memory block this service adds is never captured
|
|
80
88
|
back as a new memory.
|
|
81
89
|
- **Graceful end, salvaged abort.** On `EndFrame`, queued writes get a bounded
|
|
@@ -85,6 +93,25 @@ enriched or not, on time.
|
|
|
85
93
|
- **Failure-proof.** HTTP errors, quota limits, and timeouts all degrade to
|
|
86
94
|
"no memories this turn". Nothing propagates into the pipeline.
|
|
87
95
|
|
|
96
|
+
## Running the example
|
|
97
|
+
|
|
98
|
+
A single-file voice agent that remembers callers across calls lives in
|
|
99
|
+
[`examples/foundational.py`](examples/foundational.py):
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
uv add pipecat-memorysync "pipecat-ai[deepgram,cartesia,openai,silero,runner,webrtc]"
|
|
103
|
+
|
|
104
|
+
export MEMORYSYNC_API_KEY=ms_... # https://app.memorysync.io
|
|
105
|
+
export DEEPGRAM_API_KEY=...
|
|
106
|
+
export CARTESIA_API_KEY=...
|
|
107
|
+
export OPENAI_API_KEY=...
|
|
108
|
+
|
|
109
|
+
python examples/foundational.py
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Open `http://localhost:7860/client`, tell the bot your name and a preference,
|
|
113
|
+
hang up, and connect again — it remembers.
|
|
114
|
+
|
|
88
115
|
## Configuration (`InputParams`)
|
|
89
116
|
|
|
90
117
|
```python
|
|
@@ -128,6 +155,10 @@ python -m venv venv && venv/Scripts/pip install -e . pipecat-ai pytest pytest-as
|
|
|
128
155
|
venv/Scripts/python -m pytest tests -q # 14 tests, run via pipecat's official test harness
|
|
129
156
|
```
|
|
130
157
|
|
|
158
|
+
## Docs
|
|
159
|
+
|
|
160
|
+
Full guide: [docs.memorysync.io/guides/pipecat](https://docs.memorysync.io/guides/pipecat)
|
|
161
|
+
|
|
131
162
|
## License
|
|
132
163
|
|
|
133
164
|
MIT
|
|
@@ -1,11 +1,19 @@
|
|
|
1
|
-
# pipecat
|
|
1
|
+
# memorysync-pipecat
|
|
2
2
|
|
|
3
3
|
[MemorySync](https://memorysync.io) for [Pipecat](https://github.com/pipecat-ai/pipecat) —
|
|
4
4
|
long-term memory for voice pipelines that never stalls a reply and never
|
|
5
|
-
re-stores what it already knows.
|
|
5
|
+
re-stores what it already knows. PyPI package: **`pipecat-memorysync`**.
|
|
6
|
+
|
|
7
|
+
Built and maintained by the [MemorySync](https://memorysync.io) team —
|
|
8
|
+
MemorySync is our product, and this integration is actively maintained
|
|
9
|
+
alongside it.
|
|
10
|
+
|
|
11
|
+
**Tested with Pipecat v1.8.1** (`pipecat-ai>=1.0.0,<2`).
|
|
6
12
|
|
|
7
13
|
```bash
|
|
8
14
|
pip install pipecat-memorysync
|
|
15
|
+
# or
|
|
16
|
+
uv add pipecat-memorysync
|
|
9
17
|
```
|
|
10
18
|
|
|
11
19
|
## Where it sits
|
|
@@ -51,8 +59,7 @@ enriched or not, on time.
|
|
|
51
59
|
stalled voice reply.
|
|
52
60
|
- **Delta-only capture.** Only messages *not seen before* are stored, tracked
|
|
53
61
|
by deterministic idempotency seeds. Growing a 50-message context does not
|
|
54
|
-
re-store 50 messages per turn
|
|
55
|
-
which re-send the entire context every frame — O(n²) writes per call).
|
|
62
|
+
re-store 50 messages per turn.
|
|
56
63
|
- **Injection exclusion.** The memory block this service adds is never captured
|
|
57
64
|
back as a new memory.
|
|
58
65
|
- **Graceful end, salvaged abort.** On `EndFrame`, queued writes get a bounded
|
|
@@ -62,6 +69,25 @@ enriched or not, on time.
|
|
|
62
69
|
- **Failure-proof.** HTTP errors, quota limits, and timeouts all degrade to
|
|
63
70
|
"no memories this turn". Nothing propagates into the pipeline.
|
|
64
71
|
|
|
72
|
+
## Running the example
|
|
73
|
+
|
|
74
|
+
A single-file voice agent that remembers callers across calls lives in
|
|
75
|
+
[`examples/foundational.py`](examples/foundational.py):
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
uv add pipecat-memorysync "pipecat-ai[deepgram,cartesia,openai,silero,runner,webrtc]"
|
|
79
|
+
|
|
80
|
+
export MEMORYSYNC_API_KEY=ms_... # https://app.memorysync.io
|
|
81
|
+
export DEEPGRAM_API_KEY=...
|
|
82
|
+
export CARTESIA_API_KEY=...
|
|
83
|
+
export OPENAI_API_KEY=...
|
|
84
|
+
|
|
85
|
+
python examples/foundational.py
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Open `http://localhost:7860/client`, tell the bot your name and a preference,
|
|
89
|
+
hang up, and connect again — it remembers.
|
|
90
|
+
|
|
65
91
|
## Configuration (`InputParams`)
|
|
66
92
|
|
|
67
93
|
```python
|
|
@@ -105,6 +131,10 @@ python -m venv venv && venv/Scripts/pip install -e . pipecat-ai pytest pytest-as
|
|
|
105
131
|
venv/Scripts/python -m pytest tests -q # 14 tests, run via pipecat's official test harness
|
|
106
132
|
```
|
|
107
133
|
|
|
134
|
+
## Docs
|
|
135
|
+
|
|
136
|
+
Full guide: [docs.memorysync.io/guides/pipecat](https://docs.memorysync.io/guides/pipecat)
|
|
137
|
+
|
|
108
138
|
## License
|
|
109
139
|
|
|
110
140
|
MIT
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Foundational example: a voice agent that remembers callers across calls.
|
|
3
|
+
#
|
|
4
|
+
# MemorySyncMemoryService sits between the user context aggregator and the
|
|
5
|
+
# LLM: every LLMContextFrame is enriched with relevant long-term memories
|
|
6
|
+
# under a hard time budget, and new turns are persisted in the background.
|
|
7
|
+
# A slow or unreachable memory backend can never stall the voice reply.
|
|
8
|
+
#
|
|
9
|
+
# Run (choose any transport supported by the Pipecat runner):
|
|
10
|
+
#
|
|
11
|
+
# uv add pipecat-memorysync "pipecat-ai[deepgram,cartesia,openai,silero,runner,webrtc]"
|
|
12
|
+
# export MEMORYSYNC_API_KEY=ms_... # https://app.memorysync.io
|
|
13
|
+
# export DEEPGRAM_API_KEY=...
|
|
14
|
+
# export CARTESIA_API_KEY=...
|
|
15
|
+
# export OPENAI_API_KEY=...
|
|
16
|
+
# python foundational.py
|
|
17
|
+
#
|
|
18
|
+
# Then open http://localhost:7860/client, talk to the bot, tell it your
|
|
19
|
+
# name and a preference, hang up, and call again: it remembers.
|
|
20
|
+
#
|
|
21
|
+
|
|
22
|
+
import os
|
|
23
|
+
|
|
24
|
+
from dotenv import load_dotenv
|
|
25
|
+
from loguru import logger
|
|
26
|
+
|
|
27
|
+
from pipecat.audio.vad.silero import SileroVADAnalyzer
|
|
28
|
+
from pipecat.frames.frames import LLMRunFrame
|
|
29
|
+
from pipecat.pipeline.pipeline import Pipeline
|
|
30
|
+
from pipecat.pipeline.runner import PipelineRunner
|
|
31
|
+
from pipecat.pipeline.task import PipelineParams, PipelineTask
|
|
32
|
+
from pipecat.processors.aggregators.llm_context import LLMContext
|
|
33
|
+
from pipecat.processors.aggregators.llm_response_universal import (
|
|
34
|
+
LLMContextAggregatorPair,
|
|
35
|
+
LLMUserAggregatorParams,
|
|
36
|
+
)
|
|
37
|
+
from pipecat.runner.types import RunnerArguments
|
|
38
|
+
from pipecat.runner.utils import create_transport
|
|
39
|
+
from pipecat.services.cartesia.tts import CartesiaTTSService
|
|
40
|
+
from pipecat.services.deepgram.stt import DeepgramSTTService
|
|
41
|
+
from pipecat.services.openai.llm import OpenAILLMService
|
|
42
|
+
from pipecat.transports.base_transport import BaseTransport, TransportParams
|
|
43
|
+
|
|
44
|
+
from pipecat_memorysync import MemorySyncMemoryService
|
|
45
|
+
|
|
46
|
+
load_dotenv(override=True)
|
|
47
|
+
|
|
48
|
+
SYSTEM_INSTRUCTION = (
|
|
49
|
+
"You are a friendly voice assistant with long-term memory. "
|
|
50
|
+
"Relevant facts about the caller may appear as background memory context. "
|
|
51
|
+
"Use them naturally; never read them out verbatim. "
|
|
52
|
+
"Your answers are spoken aloud, so keep them short and conversational."
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
transport_params = {
|
|
56
|
+
"webrtc": lambda: TransportParams(audio_in_enabled=True, audio_out_enabled=True),
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
async def run_bot(transport: BaseTransport, runner_args: RunnerArguments):
|
|
61
|
+
logger.info("Starting bot")
|
|
62
|
+
|
|
63
|
+
stt = DeepgramSTTService(api_key=os.environ["DEEPGRAM_API_KEY"])
|
|
64
|
+
|
|
65
|
+
tts = CartesiaTTSService(
|
|
66
|
+
api_key=os.environ["CARTESIA_API_KEY"],
|
|
67
|
+
voice_id="71a7ad14-091c-4e8e-a314-022ece01c121",
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
llm = OpenAILLMService(api_key=os.environ["OPENAI_API_KEY"])
|
|
71
|
+
|
|
72
|
+
# Long-term memory. In a real deployment, derive user_id from the
|
|
73
|
+
# caller's identity (phone number, account id, ...) so each caller
|
|
74
|
+
# gets their own memories.
|
|
75
|
+
memory = MemorySyncMemoryService(
|
|
76
|
+
api_key=os.environ["MEMORYSYNC_API_KEY"],
|
|
77
|
+
user_id=os.environ.get("MEMORYSYNC_USER_ID", "demo-caller"),
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
context = LLMContext([{"role": "system", "content": SYSTEM_INSTRUCTION}])
|
|
81
|
+
context_aggregator = LLMContextAggregatorPair(
|
|
82
|
+
context,
|
|
83
|
+
user_params=LLMUserAggregatorParams(vad_analyzer=SileroVADAnalyzer()),
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
pipeline = Pipeline(
|
|
87
|
+
[
|
|
88
|
+
transport.input(),
|
|
89
|
+
stt,
|
|
90
|
+
context_aggregator.user(),
|
|
91
|
+
memory, # enrich with memories + capture new turns, on budget
|
|
92
|
+
llm,
|
|
93
|
+
tts,
|
|
94
|
+
transport.output(),
|
|
95
|
+
context_aggregator.assistant(),
|
|
96
|
+
]
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
task = PipelineTask(
|
|
100
|
+
pipeline,
|
|
101
|
+
params=PipelineParams(enable_metrics=True, enable_usage_metrics=True),
|
|
102
|
+
idle_timeout_secs=runner_args.pipeline_idle_timeout_secs,
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
@transport.event_handler("on_client_connected")
|
|
106
|
+
async def on_client_connected(transport, client):
|
|
107
|
+
logger.info("Client connected")
|
|
108
|
+
await task.queue_frames([LLMRunFrame()])
|
|
109
|
+
|
|
110
|
+
@transport.event_handler("on_client_disconnected")
|
|
111
|
+
async def on_client_disconnected(transport, client):
|
|
112
|
+
logger.info("Client disconnected")
|
|
113
|
+
await task.cancel()
|
|
114
|
+
|
|
115
|
+
runner = PipelineRunner(handle_sigint=runner_args.handle_sigint)
|
|
116
|
+
await runner.run(task)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
async def bot(runner_args: RunnerArguments):
|
|
120
|
+
"""Main bot entry point compatible with the Pipecat runner."""
|
|
121
|
+
transport = await create_transport(runner_args, transport_params)
|
|
122
|
+
await run_bot(transport, runner_args)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
if __name__ == "__main__":
|
|
126
|
+
from pipecat.runner.run import main
|
|
127
|
+
|
|
128
|
+
main()
|
|
@@ -29,7 +29,8 @@ dependencies = [
|
|
|
29
29
|
[project.urls]
|
|
30
30
|
Homepage = "https://docs.memorysync.io/guides/pipecat"
|
|
31
31
|
Documentation = "https://docs.memorysync.io/guides/pipecat"
|
|
32
|
-
Repository = "https://github.com/
|
|
32
|
+
Repository = "https://github.com/memorysyncio/memorysync-pipecat"
|
|
33
|
+
Changelog = "https://github.com/memorysyncio/memorysync-pipecat/blob/main/CHANGELOG.md"
|
|
33
34
|
|
|
34
35
|
[tool.hatch.version]
|
|
35
36
|
path = "src/pipecat_memorysync/_version.py"
|
|
@@ -28,12 +28,20 @@ def context_of(messages: List[dict]) -> LLMContext:
|
|
|
28
28
|
|
|
29
29
|
|
|
30
30
|
async def drive(service: Any, *contexts: LLMContext) -> None:
|
|
31
|
-
"""Send one LLMContextFrame per context through the real harness.
|
|
31
|
+
"""Send one LLMContextFrame per context through the real harness.
|
|
32
|
+
|
|
33
|
+
``start_timeout`` defaults to 1s inside pipecat's harness, which the
|
|
34
|
+
FIRST test in the session routinely blows on a cold shared CI runner
|
|
35
|
+
(import machinery + task spin-up), failing it with a TimeoutError
|
|
36
|
+
before any frame flows. A generous ceiling costs nothing when the
|
|
37
|
+
pipeline starts fast — it is not a sleep — and removes the flake.
|
|
38
|
+
"""
|
|
32
39
|
frames = [LLMContextFrame(context=ctx) for ctx in contexts]
|
|
33
40
|
await run_test(
|
|
34
41
|
service,
|
|
35
42
|
frames_to_send=frames,
|
|
36
43
|
expected_down_frames=[LLMContextFrame] * len(frames),
|
|
44
|
+
start_timeout=30.0,
|
|
37
45
|
)
|
|
38
46
|
|
|
39
47
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|