framework-agents 0.1.0__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.
Files changed (41) hide show
  1. framework_agents/__init__.py +345 -0
  2. framework_agents/a2a_client.py +360 -0
  3. framework_agents/a2a_server.py +495 -0
  4. framework_agents/agent.py +920 -0
  5. framework_agents/checkpoint.py +231 -0
  6. framework_agents/config.py +200 -0
  7. framework_agents/conversation.py +249 -0
  8. framework_agents/errors.py +47 -0
  9. framework_agents/evals.py +127 -0
  10. framework_agents/mcp_client.py +477 -0
  11. framework_agents/memory.py +103 -0
  12. framework_agents/memory_compaction.py +63 -0
  13. framework_agents/middleware.py +1302 -0
  14. framework_agents/models/__init__.py +100 -0
  15. framework_agents/models/anthropic.py +339 -0
  16. framework_agents/models/capabilities.py +45 -0
  17. framework_agents/models/ollama.py +47 -0
  18. framework_agents/models/openai_compatible.py +409 -0
  19. framework_agents/observability.py +84 -0
  20. framework_agents/ontology_review.py +321 -0
  21. framework_agents/orchestration.py +169 -0
  22. framework_agents/pricing.py +63 -0
  23. framework_agents/prompts.py +132 -0
  24. framework_agents/rag.py +123 -0
  25. framework_agents/remote_sparql.py +213 -0
  26. framework_agents/runtime.py +88 -0
  27. framework_agents/sandbox.py +327 -0
  28. framework_agents/semantic.py +1614 -0
  29. framework_agents/serve.py +436 -0
  30. framework_agents/sessions.py +249 -0
  31. framework_agents/skills.py +213 -0
  32. framework_agents/state.py +89 -0
  33. framework_agents/streaming.py +74 -0
  34. framework_agents/swarm.py +318 -0
  35. framework_agents/tools.py +114 -0
  36. framework_agents/tracing.py +131 -0
  37. framework_agents/types.py +262 -0
  38. framework_agents/workflows.py +130 -0
  39. framework_agents-0.1.0.dist-info/METADATA +118 -0
  40. framework_agents-0.1.0.dist-info/RECORD +41 -0
  41. framework_agents-0.1.0.dist-info/WHEEL +4 -0
@@ -0,0 +1,103 @@
1
+ """Memory: append + window. Parity: src/memory/index.ts (post-fix).
2
+
3
+ Pinned semantics (the TS sweep found these the hard way):
4
+ - save() APPENDS (windows to max); the agent loop does CLEAR-then-SAVE, never
5
+ plain save, or every run duplicates the transcript.
6
+ - FileMemory: corrupted files fail LOUDLY (MEMORY_CORRUPT) instead of wiping
7
+ whatever is still on disk on the next save; missing file = fresh memory.
8
+ - tmp file per flush is unique — concurrent flushes can't race on rename.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import json
14
+ import os
15
+ import tempfile
16
+ from pathlib import Path
17
+ from typing import Protocol
18
+
19
+ from .errors import AgentError
20
+ from .types import ChatMessage, deserialize_messages, serialize_messages
21
+
22
+
23
+ class Memory(Protocol):
24
+ async def load(self) -> list[ChatMessage]: ...
25
+ async def save(self, messages: list[ChatMessage]) -> None: ...
26
+ async def clear(self) -> None: ...
27
+
28
+
29
+ class InMemoryMemory:
30
+ def __init__(self, max_messages: int = 50) -> None:
31
+ self._messages: list[ChatMessage] = []
32
+ self.max_messages = max_messages
33
+
34
+ async def load(self) -> list[ChatMessage]:
35
+ return list(self._messages)
36
+
37
+ async def save(self, messages: list[ChatMessage]) -> None:
38
+ self._messages = [*self._messages, *messages][-self.max_messages:]
39
+
40
+ async def clear(self) -> None:
41
+ self._messages = []
42
+
43
+
44
+ class FileMemory:
45
+ """JSON-file backed memory: same windowing as in-memory, atomic writes."""
46
+
47
+ _MAX_UTF16_UNITS = 2048
48
+
49
+ def __init__(self, directory: str, max_messages: int = 50) -> None:
50
+ self.directory = Path(directory)
51
+ self.file = self.directory / "memory.json"
52
+ self.max_messages = max_messages
53
+ self._cache: list[ChatMessage] | None = None
54
+
55
+ async def load(self) -> list[ChatMessage]:
56
+ return list(await self._read_all())
57
+
58
+ async def save(self, messages: list[ChatMessage]) -> None:
59
+ all_messages = [*(await self._read_all()), *messages][-self.max_messages:]
60
+ self._cache = all_messages
61
+ await self._flush()
62
+
63
+ async def clear(self) -> None:
64
+ self._cache = []
65
+ await self._flush()
66
+
67
+ async def _read_all(self) -> list[ChatMessage]:
68
+ if self._cache is not None:
69
+ return self._cache
70
+ try:
71
+ raw = self.file.read_text(encoding="utf-8")
72
+ except FileNotFoundError:
73
+ self._cache = []
74
+ return self._cache
75
+ except OSError as err:
76
+ self._cache = []
77
+ return self._cache
78
+ try:
79
+ data = json.loads(raw)
80
+ # Valid JSON with the WRONG shape is still corrupt data. TS
81
+ # throws here (deserializeMessages rejects non-arrays); silently
82
+ # treating a dict as "empty" would clobber the file on save.
83
+ if not isinstance(data, list):
84
+ raise ValueError("top-level JSON is not a message array")
85
+ self._cache = deserialize_messages(data)
86
+ except (ValueError, TypeError) as err:
87
+ # Corrupted file must FAIL loudly: returning [] would make the
88
+ # next save overwrite whatever was still on disk.
89
+ raise AgentError("MEMORY_CORRUPT", f"Memory file is not valid JSON for messages ({self.file}): {err}", retryable=False) from err
90
+ return self._cache
91
+
92
+ async def _flush(self) -> None:
93
+ self.directory.mkdir(parents=True, exist_ok=True)
94
+ # Unique tmp per flush: concurrent saves sharing one fixed name race
95
+ # on the rename and can publish a torn or half-written file.
96
+ fd, tmp_path = tempfile.mkstemp(dir=self.directory, prefix="memory.json.", suffix=".tmp")
97
+ try:
98
+ with os.fdopen(fd, "w", encoding="utf-8") as fh:
99
+ json.dump(serialize_messages(self._cache or []), fh)
100
+ os.replace(tmp_path, self.file)
101
+ finally:
102
+ if os.path.exists(tmp_path): # replace succeeded -> gone already
103
+ os.unlink(tmp_path)
@@ -0,0 +1,63 @@
1
+ """Compaction: keep long runs inside the context window without losing meaning.
2
+
3
+ Parity: src/memory/compaction.ts.
4
+
5
+ Strategy (P1): oldest messages -> model/pluggable summarizer -> single
6
+ boundary message.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import inspect
12
+ from typing import Any, Callable
13
+
14
+ from .memory import Memory
15
+ from .types import ChatMessage, text_of
16
+
17
+ SummarizeFn = Callable[[list[ChatMessage]], Any] # str or awaitable str
18
+
19
+
20
+ def default_summarize(messages: list[ChatMessage]) -> str:
21
+ """Extractive stub: first five non-system messages, 200 chars each."""
22
+ head = "\n".join(
23
+ f"[{m.role}] {text_of(m.content)[:200]}"
24
+ for m in [m for m in messages if m.role != "system"][:5]
25
+ )
26
+ return f"Prior context summary ({len(messages)} msgs):\n{head}"
27
+
28
+
29
+ class CompactedMemory:
30
+ """Wrap any Memory with auto-compaction on save."""
31
+
32
+ def __init__(
33
+ self,
34
+ inner: Memory,
35
+ max_messages: int = 40,
36
+ summarize: SummarizeFn | None = None,
37
+ ) -> None:
38
+ self.inner = inner
39
+ self.max_messages = max_messages
40
+ self.summarize = summarize
41
+
42
+ async def load(self) -> list[ChatMessage]:
43
+ return await self.inner.load()
44
+
45
+ async def save(self, messages: list[ChatMessage]) -> None:
46
+ await self.inner.save(messages)
47
+ max = self.max_messages
48
+ current = await self.inner.load()
49
+ if len(current) <= max:
50
+ return
51
+ summarize = self.summarize or default_summarize
52
+ # Overflow is replaced by ONE summary message so the stored history
53
+ # shrinks back to exactly `max` (1 summary + max-1 newest).
54
+ overflow = current[: len(current) - max + 1]
55
+ rest = current[len(current) - max + 1 :]
56
+ summary = summarize(overflow)
57
+ if inspect.isawaitable(summary):
58
+ summary = await summary
59
+ await self.inner.clear()
60
+ await self.inner.save([ChatMessage(role="assistant", content=summary), *rest])
61
+
62
+ async def clear(self) -> None:
63
+ await self.inner.clear()