keystone-agent-tools 0.1.5__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.
- keystone_agent_tools-0.1.5/PKG-INFO +15 -0
- keystone_agent_tools-0.1.5/__init__.py +18 -0
- keystone_agent_tools-0.1.5/attachments/__init__.py +61 -0
- keystone_agent_tools-0.1.5/attachments/audio.py +104 -0
- keystone_agent_tools-0.1.5/attachments/blocks.py +491 -0
- keystone_agent_tools-0.1.5/attachments/budget.py +61 -0
- keystone_agent_tools-0.1.5/attachments/images.py +387 -0
- keystone_agent_tools-0.1.5/files/__init__.py +105 -0
- keystone_agent_tools-0.1.5/files/client.py +650 -0
- keystone_agent_tools-0.1.5/files/content.py +147 -0
- keystone_agent_tools-0.1.5/files/descriptions.py +223 -0
- keystone_agent_tools-0.1.5/files/models.py +296 -0
- keystone_agent_tools-0.1.5/files/tools.py +979 -0
- keystone_agent_tools-0.1.5/files/wire.py +168 -0
- keystone_agent_tools-0.1.5/kb/__init__.py +36 -0
- keystone_agent_tools-0.1.5/kb/descriptions.py +147 -0
- keystone_agent_tools-0.1.5/kb/results.py +196 -0
- keystone_agent_tools-0.1.5/kb/tools.py +302 -0
- keystone_agent_tools-0.1.5/keystone_agent_tools.egg-info/PKG-INFO +15 -0
- keystone_agent_tools-0.1.5/keystone_agent_tools.egg-info/SOURCES.txt +36 -0
- keystone_agent_tools-0.1.5/keystone_agent_tools.egg-info/dependency_links.txt +1 -0
- keystone_agent_tools-0.1.5/keystone_agent_tools.egg-info/requires.txt +11 -0
- keystone_agent_tools-0.1.5/keystone_agent_tools.egg-info/top_level.txt +1 -0
- keystone_agent_tools-0.1.5/parity.py +120 -0
- keystone_agent_tools-0.1.5/pyproject.toml +53 -0
- keystone_agent_tools-0.1.5/setup.cfg +4 -0
- keystone_agent_tools-0.1.5/tests/test_attachments.py +504 -0
- keystone_agent_tools-0.1.5/tests/test_client.py +447 -0
- keystone_agent_tools-0.1.5/tests/test_content_wire.py +74 -0
- keystone_agent_tools-0.1.5/tests/test_kb_repeats.py +150 -0
- keystone_agent_tools-0.1.5/tests/test_kb_tools.py +123 -0
- keystone_agent_tools-0.1.5/tests/test_own_conversation.py +231 -0
- keystone_agent_tools-0.1.5/tests/test_tools.py +378 -0
- keystone_agent_tools-0.1.5/tests/test_transcription.py +233 -0
- keystone_agent_tools-0.1.5/transcription.py +347 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: keystone-agent-tools
|
|
3
|
+
Version: 0.1.5
|
|
4
|
+
Summary: The file tools a platform agent gets (read / search / create / render / edit an artifact through file-artifact), the extended knowledge-base toolset (FR-180), and how a turn's attachments reach the model. One definition for visual-builder and master-agent, so the two cannot drift.
|
|
5
|
+
Requires-Python: >=3.12
|
|
6
|
+
Requires-Dist: httpx<1,>=0.27
|
|
7
|
+
Requires-Dist: pydantic<3,>=2.7
|
|
8
|
+
Requires-Dist: langchain-core<2,>=1
|
|
9
|
+
Requires-Dist: langgraph<2,>=1
|
|
10
|
+
Requires-Dist: Pillow<14,>=13
|
|
11
|
+
Provides-Extra: dev
|
|
12
|
+
Requires-Dist: pytest<9,>=8; extra == "dev"
|
|
13
|
+
Requires-Dist: pytest-asyncio<1,>=0.24; extra == "dev"
|
|
14
|
+
Requires-Dist: pytest-cov<7,>=6; extra == "dev"
|
|
15
|
+
Requires-Dist: ruff==0.15.15; extra == "dev"
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""The tools a platform agent uses on files, shared by visual-builder and master-agent (FDP-6967).
|
|
2
|
+
|
|
3
|
+
* :mod:`keystone.agent_tools.files` — the file-artifact client (create / render as background
|
|
4
|
+
jobs, FDP-7030) and the file tools the model calls (``read_artifact``, ``search_in_artifact``,
|
|
5
|
+
``search_across_thread``, ``create_artifact``, ``render_artifact``, ``edit_artifact`` and the
|
|
6
|
+
``list`` / ``get_metadata`` / ``publish`` tools they refer to). The names, arguments and the
|
|
7
|
+
words the model reads are written ONCE, here.
|
|
8
|
+
* :mod:`keystone.agent_tools.kb` — the extended knowledge-base toolset (FR-180, FDP-7133): the six
|
|
9
|
+
``kb_*`` tools, their descriptions and the Knowledge Bases block of the system prompt, with rag's
|
|
10
|
+
agent guide (and default metadata filter) of each KB. visual-builder and pro-code use it as is.
|
|
11
|
+
* :mod:`keystone.agent_tools.attachments` — how the files attached to a turn become content
|
|
12
|
+
blocks for the model: one token budget per turn, pictures downscaled to the provider limit,
|
|
13
|
+
what could not be sent noted for the model.
|
|
14
|
+
|
|
15
|
+
What a service still owns: who the call acts as (its headers), which turn a file belongs to, how
|
|
16
|
+
a file card reaches its chat, and the shape of a tool result in its graph state. A service plugs
|
|
17
|
+
those in through :class:`keystone.agent_tools.files.FileToolHost`.
|
|
18
|
+
"""
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""How a turn's attachments reach the model (FDP-6967, item 2) — one set of rules for every agent.
|
|
2
|
+
|
|
3
|
+
from keystone.agent_tools.attachments import AttachedFile, TurnPictures, document_blocks, image_blocks
|
|
4
|
+
|
|
5
|
+
See :mod:`keystone.agent_tools.attachments.blocks` for the rules, :mod:`.budget` for the turn budget
|
|
6
|
+
and :mod:`.images` for fitting a picture to the provider's limit.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from keystone.agent_tools.attachments.audio import AUDIO_HEARD_NOTE, audio_blocks, transcript_blocks
|
|
10
|
+
from keystone.agent_tools.attachments.blocks import (
|
|
11
|
+
IMAGE_MIMES,
|
|
12
|
+
AttachedFile,
|
|
13
|
+
AttachmentLimits,
|
|
14
|
+
FileBlocks,
|
|
15
|
+
TurnPictures,
|
|
16
|
+
aggregate_refused_note,
|
|
17
|
+
attached_marker,
|
|
18
|
+
document_blocks,
|
|
19
|
+
fmt_size,
|
|
20
|
+
image_blocks,
|
|
21
|
+
is_document,
|
|
22
|
+
is_image,
|
|
23
|
+
named,
|
|
24
|
+
text_cap_chars,
|
|
25
|
+
)
|
|
26
|
+
from keystone.agent_tools.attachments.budget import (
|
|
27
|
+
MIN_FLOOR_TOKENS_PER_FILE,
|
|
28
|
+
STATIC_TURN_ATTACHMENT_BUDGET,
|
|
29
|
+
cap_bytes_for_share,
|
|
30
|
+
compute_share_per_file,
|
|
31
|
+
refuses_content,
|
|
32
|
+
)
|
|
33
|
+
from keystone.agent_tools.attachments.images import DecodeRefused, fit_image, fit_image_async
|
|
34
|
+
|
|
35
|
+
__all__ = [
|
|
36
|
+
"AUDIO_HEARD_NOTE",
|
|
37
|
+
"audio_blocks",
|
|
38
|
+
"transcript_blocks",
|
|
39
|
+
"IMAGE_MIMES",
|
|
40
|
+
"MIN_FLOOR_TOKENS_PER_FILE",
|
|
41
|
+
"STATIC_TURN_ATTACHMENT_BUDGET",
|
|
42
|
+
"AttachedFile",
|
|
43
|
+
"AttachmentLimits",
|
|
44
|
+
"DecodeRefused",
|
|
45
|
+
"FileBlocks",
|
|
46
|
+
"TurnPictures",
|
|
47
|
+
"aggregate_refused_note",
|
|
48
|
+
"attached_marker",
|
|
49
|
+
"cap_bytes_for_share",
|
|
50
|
+
"compute_share_per_file",
|
|
51
|
+
"document_blocks",
|
|
52
|
+
"fit_image",
|
|
53
|
+
"fit_image_async",
|
|
54
|
+
"fmt_size",
|
|
55
|
+
"image_blocks",
|
|
56
|
+
"is_document",
|
|
57
|
+
"is_image",
|
|
58
|
+
"named",
|
|
59
|
+
"refuses_content",
|
|
60
|
+
"text_cap_chars",
|
|
61
|
+
]
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"""An audio clip attached to a turn, as blocks: its transcript, or why it could not be heard (FDP-6967).
|
|
2
|
+
|
|
3
|
+
The words are visual-builder's (``services/agent_version/attachments.py``), which master-agent had
|
|
4
|
+
copied; now one copy. :func:`audio_blocks` calls the shared speech-to-text client
|
|
5
|
+
(:mod:`keystone.agent_tools.transcription`) — a host passes its identity headers, the agent's STT
|
|
6
|
+
model, and how to reach the clip (bytes, a spool, or a presigned URL for a long one).
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
# Feature: FR-130 | Tracking: FDP-3032, FDP-6967
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import logging
|
|
14
|
+
from typing import Any
|
|
15
|
+
|
|
16
|
+
from keystone.agent_tools.attachments.blocks import AttachedFile, AttachmentLimits, FileBlocks, text_cap_chars
|
|
17
|
+
from keystone.agent_tools.transcription import Transcript, TranscriptionFailed
|
|
18
|
+
|
|
19
|
+
__all__ = ["AUDIO_HEARD_NOTE", "audio_blocks", "transcript_blocks"]
|
|
20
|
+
|
|
21
|
+
_logger = logging.getLogger("keystone.agent_tools.attachments")
|
|
22
|
+
|
|
23
|
+
#: Once per turn that carries a heard clip: without it models answered "I can't listen to audio, but
|
|
24
|
+
#: I can read the transcript you attached" — the block read to them as a transcript the USER pasted.
|
|
25
|
+
AUDIO_HEARD_NOTE = (
|
|
26
|
+
"[system-note: the audio above is what the user said, heard through speech-to-text. "
|
|
27
|
+
"Answer from what was said; do not say you cannot listen to audio.]"
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def transcript_blocks(name: str | None, result: Transcript | str, *, cap_chars: int) -> list[dict[str, Any]]:
|
|
32
|
+
"""What the model reads for one clip: its transcript, or (``result`` a str) why it was not heard."""
|
|
33
|
+
label = name or "audio"
|
|
34
|
+
if isinstance(result, str):
|
|
35
|
+
return [
|
|
36
|
+
{
|
|
37
|
+
"type": "text",
|
|
38
|
+
"text": (
|
|
39
|
+
f"[system-note: the user attached the audio file {label}, but it could not be "
|
|
40
|
+
f"transcribed ({result}). Tell the user you could not hear it and ask them to "
|
|
41
|
+
"send it again or type the message.]"
|
|
42
|
+
),
|
|
43
|
+
}
|
|
44
|
+
]
|
|
45
|
+
text = result.text
|
|
46
|
+
if not text:
|
|
47
|
+
return [
|
|
48
|
+
{
|
|
49
|
+
"type": "text",
|
|
50
|
+
"text": f"[system-note: the audio file {label} was transcribed, but no speech was found in it.]",
|
|
51
|
+
}
|
|
52
|
+
]
|
|
53
|
+
length = f", {result.duration_seconds:.0f} s" if result.duration_seconds else ""
|
|
54
|
+
note = ""
|
|
55
|
+
if cap_chars > 0 and len(text) > cap_chars:
|
|
56
|
+
text = text[:cap_chars]
|
|
57
|
+
note = " Only the start is shown: the conversation has no room for the rest."
|
|
58
|
+
# Worded as audio the agent HEARD, not a transcript the user supplied (see AUDIO_HEARD_NOTE).
|
|
59
|
+
return [
|
|
60
|
+
{
|
|
61
|
+
"type": "text",
|
|
62
|
+
"text": (
|
|
63
|
+
f"[The user sent an audio clip ({label}{length}). What was said, transcribed "
|
|
64
|
+
f"automatically (a word may be misheard):{note}]\n{text}"
|
|
65
|
+
),
|
|
66
|
+
}
|
|
67
|
+
]
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
async def audio_blocks(
|
|
71
|
+
transcriber: Any,
|
|
72
|
+
file: AttachedFile,
|
|
73
|
+
*,
|
|
74
|
+
model: str,
|
|
75
|
+
headers: dict[str, str],
|
|
76
|
+
share_tokens: int,
|
|
77
|
+
limits: AttachmentLimits = AttachmentLimits(), # noqa: B008 - frozen, shared default
|
|
78
|
+
content: bytes | None = None,
|
|
79
|
+
open_content: Any = None,
|
|
80
|
+
audio_url: Any = None,
|
|
81
|
+
) -> FileBlocks:
|
|
82
|
+
"""One clip through the shared STT client. ``status``: ``shown`` when heard (speech or not),
|
|
83
|
+
``unreadable`` when it could not be transcribed — never raises."""
|
|
84
|
+
try:
|
|
85
|
+
heard = await transcriber.transcribe(
|
|
86
|
+
filename=file.name,
|
|
87
|
+
mime=file.base_mime,
|
|
88
|
+
model=model,
|
|
89
|
+
headers=headers,
|
|
90
|
+
size=file.size,
|
|
91
|
+
content=content,
|
|
92
|
+
open_content=open_content,
|
|
93
|
+
audio_url=audio_url,
|
|
94
|
+
)
|
|
95
|
+
except TranscriptionFailed as exc:
|
|
96
|
+
_logger.warning("audio attachment not transcribed artifact_id=%s error=%s", file.artifact_id, exc)
|
|
97
|
+
reason = (
|
|
98
|
+
"it is over the size limit"
|
|
99
|
+
if exc.code == "AUDIO_TOO_LARGE"
|
|
100
|
+
else "the speech-to-text service could not process it"
|
|
101
|
+
)
|
|
102
|
+
return FileBlocks(transcript_blocks(file.name, reason, cap_chars=0), "unreadable")
|
|
103
|
+
cap = text_cap_chars(share_tokens, limits)
|
|
104
|
+
return FileBlocks(transcript_blocks(file.name, heard, cap_chars=cap), "shown")
|