aiagentnotes 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.
- aiagentnotes/__init__.py +19 -0
- aiagentnotes/client.py +240 -0
- aiagentnotes/env.py +61 -0
- aiagentnotes/integrations/__init__.py +1 -0
- aiagentnotes/integrations/crewai.py +26 -0
- aiagentnotes/integrations/langchain.py +126 -0
- aiagentnotes/paths.py +5 -0
- aiagentnotes-0.1.0.dist-info/METADATA +103 -0
- aiagentnotes-0.1.0.dist-info/RECORD +10 -0
- aiagentnotes-0.1.0.dist-info/WHEEL +4 -0
aiagentnotes/__init__.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from aiagentnotes.client import AgentNotes
|
|
2
|
+
from aiagentnotes.env import get_client, is_configured, load_env_file, require_client
|
|
3
|
+
from aiagentnotes.paths import (
|
|
4
|
+
DEFAULT_COMPLETE_PATH,
|
|
5
|
+
DEFAULT_HEALTH_PATH,
|
|
6
|
+
DEFAULT_LOG_PATH,
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
"AgentNotes",
|
|
11
|
+
"get_client",
|
|
12
|
+
"require_client",
|
|
13
|
+
"is_configured",
|
|
14
|
+
"load_env_file",
|
|
15
|
+
"DEFAULT_LOG_PATH",
|
|
16
|
+
"DEFAULT_COMPLETE_PATH",
|
|
17
|
+
"DEFAULT_HEALTH_PATH",
|
|
18
|
+
]
|
|
19
|
+
__version__ = "0.1.0"
|
aiagentnotes/client.py
ADDED
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
"""AgentNotes Python SDK — SparkNotes for your AI agents."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import atexit
|
|
6
|
+
import functools
|
|
7
|
+
import os
|
|
8
|
+
import threading
|
|
9
|
+
import traceback
|
|
10
|
+
import uuid
|
|
11
|
+
from typing import Any, Callable, Optional, TypeVar
|
|
12
|
+
|
|
13
|
+
import requests
|
|
14
|
+
|
|
15
|
+
from aiagentnotes.env import load_env_file
|
|
16
|
+
from aiagentnotes.paths import DEFAULT_COMPLETE_PATH, DEFAULT_LOG_PATH
|
|
17
|
+
|
|
18
|
+
F = TypeVar("F", bound=Callable[..., Any])
|
|
19
|
+
|
|
20
|
+
DEFAULT_BASE_URL = "https://agentnotes.app"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _api_url(base_url: str, path: str) -> str:
|
|
24
|
+
return f"{base_url.rstrip('/')}/{path.lstrip('/')}"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class AgentNotes:
|
|
28
|
+
"""Track agent runs and send batched logs to AgentNotes."""
|
|
29
|
+
|
|
30
|
+
def __init__(
|
|
31
|
+
self,
|
|
32
|
+
api_key: str,
|
|
33
|
+
agent_id: str,
|
|
34
|
+
*,
|
|
35
|
+
base_url: str = DEFAULT_BASE_URL,
|
|
36
|
+
log_path: str = DEFAULT_LOG_PATH,
|
|
37
|
+
complete_path: str = DEFAULT_COMPLETE_PATH,
|
|
38
|
+
summary_frequency: str = "daily",
|
|
39
|
+
batch_size: int = 20,
|
|
40
|
+
flush_interval: float = 5.0,
|
|
41
|
+
) -> None:
|
|
42
|
+
self.api_key = api_key
|
|
43
|
+
self.agent_id = agent_id
|
|
44
|
+
self.base_url = base_url.rstrip("/")
|
|
45
|
+
self.log_path = log_path
|
|
46
|
+
self.complete_path = complete_path
|
|
47
|
+
self.log_url = _api_url(self.base_url, log_path)
|
|
48
|
+
self.complete_url = _api_url(self.base_url, complete_path)
|
|
49
|
+
self.summary_frequency = summary_frequency
|
|
50
|
+
self.batch_size = batch_size
|
|
51
|
+
self.flush_interval = flush_interval
|
|
52
|
+
|
|
53
|
+
self._run_id: Optional[str] = None
|
|
54
|
+
self._buffer: list[dict[str, Any]] = []
|
|
55
|
+
self._lock = threading.Lock()
|
|
56
|
+
self._session = requests.Session()
|
|
57
|
+
self._session.headers.update(
|
|
58
|
+
{
|
|
59
|
+
"Authorization": f"Bearer {api_key}",
|
|
60
|
+
"Content-Type": "application/json",
|
|
61
|
+
}
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
atexit.register(self.flush)
|
|
65
|
+
|
|
66
|
+
@classmethod
|
|
67
|
+
def from_env(cls) -> AgentNotes:
|
|
68
|
+
"""Build client from AGENTNOTES_* environment variables (Railway, Docker, etc.)."""
|
|
69
|
+
load_env_file()
|
|
70
|
+
missing = [
|
|
71
|
+
name
|
|
72
|
+
for name, val in (
|
|
73
|
+
("AGENTNOTES_API_KEY", os.environ.get("AGENTNOTES_API_KEY")),
|
|
74
|
+
("AGENTNOTES_AGENT_ID", os.environ.get("AGENTNOTES_AGENT_ID")),
|
|
75
|
+
)
|
|
76
|
+
if not val
|
|
77
|
+
]
|
|
78
|
+
if missing:
|
|
79
|
+
raise ValueError(f"Missing required env: {', '.join(missing)}")
|
|
80
|
+
|
|
81
|
+
return cls(
|
|
82
|
+
api_key=os.environ["AGENTNOTES_API_KEY"],
|
|
83
|
+
agent_id=os.environ["AGENTNOTES_AGENT_ID"],
|
|
84
|
+
base_url=os.environ.get("AGENTNOTES_BASE_URL", DEFAULT_BASE_URL),
|
|
85
|
+
log_path=os.environ.get("AGENTNOTES_LOG_PATH", DEFAULT_LOG_PATH),
|
|
86
|
+
complete_path=os.environ.get(
|
|
87
|
+
"AGENTNOTES_COMPLETE_PATH", DEFAULT_COMPLETE_PATH
|
|
88
|
+
),
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
def _ensure_run(self) -> str:
|
|
92
|
+
if not self._run_id:
|
|
93
|
+
self._run_id = str(uuid.uuid4())
|
|
94
|
+
return self._run_id
|
|
95
|
+
|
|
96
|
+
def start_run(self, external_run_id: Optional[str] = None) -> str:
|
|
97
|
+
"""Start a new run. Returns the run ID."""
|
|
98
|
+
self.flush()
|
|
99
|
+
self._run_id = str(uuid.uuid4())
|
|
100
|
+
if external_run_id:
|
|
101
|
+
self.log("run_started", data={"external_run_id": external_run_id})
|
|
102
|
+
return self._run_id
|
|
103
|
+
|
|
104
|
+
def log(
|
|
105
|
+
self,
|
|
106
|
+
message: str,
|
|
107
|
+
*,
|
|
108
|
+
data: Optional[dict[str, Any]] = None,
|
|
109
|
+
step_name: Optional[str] = None,
|
|
110
|
+
) -> None:
|
|
111
|
+
"""Queue a log event."""
|
|
112
|
+
self._enqueue(
|
|
113
|
+
{
|
|
114
|
+
"step_name": step_name,
|
|
115
|
+
"event_type": "log",
|
|
116
|
+
"message": message,
|
|
117
|
+
"data": data,
|
|
118
|
+
}
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
def log_error(
|
|
122
|
+
self,
|
|
123
|
+
error: BaseException,
|
|
124
|
+
*,
|
|
125
|
+
step_name: Optional[str] = None,
|
|
126
|
+
) -> None:
|
|
127
|
+
"""Queue an error event."""
|
|
128
|
+
self._enqueue(
|
|
129
|
+
{
|
|
130
|
+
"step_name": step_name,
|
|
131
|
+
"event_type": "error",
|
|
132
|
+
"message": str(error),
|
|
133
|
+
"data": {
|
|
134
|
+
"type": type(error).__name__,
|
|
135
|
+
"traceback": traceback.format_exc(),
|
|
136
|
+
},
|
|
137
|
+
}
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
def _enqueue(self, entry: dict[str, Any]) -> None:
|
|
141
|
+
with self._lock:
|
|
142
|
+
self._buffer.append(entry)
|
|
143
|
+
if len(self._buffer) >= self.batch_size:
|
|
144
|
+
self._flush_unlocked()
|
|
145
|
+
|
|
146
|
+
def flush(self) -> None:
|
|
147
|
+
"""Send buffered logs to the API."""
|
|
148
|
+
with self._lock:
|
|
149
|
+
self._flush_unlocked()
|
|
150
|
+
|
|
151
|
+
def _flush_unlocked(self) -> None:
|
|
152
|
+
if not self._buffer:
|
|
153
|
+
return
|
|
154
|
+
|
|
155
|
+
batch = self._buffer[:]
|
|
156
|
+
self._buffer.clear()
|
|
157
|
+
run_id = self._ensure_run()
|
|
158
|
+
|
|
159
|
+
payload = {
|
|
160
|
+
"agent_id": self.agent_id,
|
|
161
|
+
"run_id": run_id,
|
|
162
|
+
"logs": batch,
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
try:
|
|
166
|
+
resp = self._session.post(
|
|
167
|
+
self.log_url,
|
|
168
|
+
json=payload,
|
|
169
|
+
timeout=30,
|
|
170
|
+
)
|
|
171
|
+
resp.raise_for_status()
|
|
172
|
+
data = resp.json()
|
|
173
|
+
if data.get("run_id"):
|
|
174
|
+
self._run_id = data["run_id"]
|
|
175
|
+
except requests.RequestException:
|
|
176
|
+
# Re-queue on failure for a best-effort retry
|
|
177
|
+
self._buffer = batch + self._buffer
|
|
178
|
+
|
|
179
|
+
def complete_run(
|
|
180
|
+
self,
|
|
181
|
+
result: Optional[Any] = None,
|
|
182
|
+
*,
|
|
183
|
+
status: str = "completed",
|
|
184
|
+
error_message: Optional[str] = None,
|
|
185
|
+
) -> None:
|
|
186
|
+
"""Mark the current run as complete and flush remaining logs."""
|
|
187
|
+
self.flush()
|
|
188
|
+
if not self._run_id:
|
|
189
|
+
return
|
|
190
|
+
|
|
191
|
+
payload = {
|
|
192
|
+
"agent_id": self.agent_id,
|
|
193
|
+
"run_id": self._run_id,
|
|
194
|
+
"result": result,
|
|
195
|
+
"status": status,
|
|
196
|
+
"error_message": error_message,
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
try:
|
|
200
|
+
resp = self._session.post(
|
|
201
|
+
self.complete_url,
|
|
202
|
+
json=payload,
|
|
203
|
+
timeout=30,
|
|
204
|
+
)
|
|
205
|
+
resp.raise_for_status()
|
|
206
|
+
except requests.RequestException:
|
|
207
|
+
pass
|
|
208
|
+
finally:
|
|
209
|
+
self._run_id = None
|
|
210
|
+
|
|
211
|
+
def track(self, step_name: str) -> Callable[[F], F]:
|
|
212
|
+
"""Decorator to track a function as an agent step."""
|
|
213
|
+
|
|
214
|
+
def decorator(fn: F) -> F:
|
|
215
|
+
@functools.wraps(fn)
|
|
216
|
+
def wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
217
|
+
self._enqueue(
|
|
218
|
+
{
|
|
219
|
+
"step_name": step_name,
|
|
220
|
+
"event_type": "step_start",
|
|
221
|
+
"message": f"Starting {step_name}",
|
|
222
|
+
}
|
|
223
|
+
)
|
|
224
|
+
try:
|
|
225
|
+
result = fn(*args, **kwargs)
|
|
226
|
+
self._enqueue(
|
|
227
|
+
{
|
|
228
|
+
"step_name": step_name,
|
|
229
|
+
"event_type": "step_end",
|
|
230
|
+
"message": f"Completed {step_name}",
|
|
231
|
+
}
|
|
232
|
+
)
|
|
233
|
+
return result
|
|
234
|
+
except Exception as e:
|
|
235
|
+
self.log_error(e, step_name=step_name)
|
|
236
|
+
raise
|
|
237
|
+
|
|
238
|
+
return wrapper # type: ignore
|
|
239
|
+
|
|
240
|
+
return decorator
|
aiagentnotes/env.py
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""Environment configuration for AgentNotes clients."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from typing import TYPE_CHECKING, Optional
|
|
7
|
+
|
|
8
|
+
from aiagentnotes.paths import DEFAULT_COMPLETE_PATH, DEFAULT_LOG_PATH
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from aiagentnotes.client import AgentNotes
|
|
12
|
+
|
|
13
|
+
REQUIRED_ENV = ("AGENTNOTES_API_KEY", "AGENTNOTES_AGENT_ID")
|
|
14
|
+
OPTIONAL_ENV = (
|
|
15
|
+
"AGENTNOTES_BASE_URL",
|
|
16
|
+
"AGENTNOTES_LOG_PATH",
|
|
17
|
+
"AGENTNOTES_COMPLETE_PATH",
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def load_env_file() -> bool:
|
|
22
|
+
"""Load `.env` from the current working directory if python-dotenv is installed."""
|
|
23
|
+
try:
|
|
24
|
+
from dotenv import load_dotenv
|
|
25
|
+
except ImportError:
|
|
26
|
+
return False
|
|
27
|
+
return bool(load_dotenv())
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def is_configured() -> bool:
|
|
31
|
+
"""True when required AGENTNOTES_* variables are set."""
|
|
32
|
+
load_env_file()
|
|
33
|
+
return all(os.environ.get(name) for name in REQUIRED_ENV)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def get_client() -> Optional["AgentNotes"]:
|
|
37
|
+
"""
|
|
38
|
+
Return an AgentNotes client when env is configured, else None.
|
|
39
|
+
Safe to call in bots that should run without logging when not set up.
|
|
40
|
+
"""
|
|
41
|
+
if not is_configured():
|
|
42
|
+
return None
|
|
43
|
+
from aiagentnotes.client import AgentNotes
|
|
44
|
+
|
|
45
|
+
return AgentNotes.from_env()
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def require_client() -> "AgentNotes":
|
|
49
|
+
"""Like get_client() but raises with a helpful message if env is missing."""
|
|
50
|
+
client = get_client()
|
|
51
|
+
if client is not None:
|
|
52
|
+
return client
|
|
53
|
+
missing = [name for name in REQUIRED_ENV if not os.environ.get(name)]
|
|
54
|
+
raise ValueError(
|
|
55
|
+
"AgentNotes is not configured. Set environment variables:\n"
|
|
56
|
+
f" Required: {', '.join(REQUIRED_ENV)}\n"
|
|
57
|
+
f" Optional: {', '.join(OPTIONAL_ENV)} (defaults: "
|
|
58
|
+
f"LOG={DEFAULT_LOG_PATH}, COMPLETE={DEFAULT_COMPLETE_PATH})\n"
|
|
59
|
+
f" Missing now: {', '.join(missing)}\n"
|
|
60
|
+
"Install: pip install aiagentnotes"
|
|
61
|
+
)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Optional integrations for popular agent frameworks."""
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""CrewAI step hooks via AgentNotes."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any, Callable
|
|
6
|
+
|
|
7
|
+
from aiagentnotes.client import AgentNotes
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def crew_step_logger(notes: AgentNotes, step_name: str) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
|
|
11
|
+
"""Decorator for CrewAI task functions — same as notes.track but explicit naming."""
|
|
12
|
+
|
|
13
|
+
return notes.track(step_name)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def log_crew_kickoff(notes: AgentNotes, crew_name: str, **meta: Any) -> str:
|
|
17
|
+
"""Call before crew.kickoff(). Returns run id."""
|
|
18
|
+
run_id = notes.start_run(external_run_id=crew_name)
|
|
19
|
+
notes.log("crew_kickoff", data={"crew": crew_name, **meta})
|
|
20
|
+
return run_id
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def log_crew_complete(notes: AgentNotes, result: Any) -> None:
|
|
24
|
+
"""Call after crew.kickoff() returns."""
|
|
25
|
+
summary = str(result)[:500] if result is not None else "completed"
|
|
26
|
+
notes.complete_run({"summary": summary})
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"""LangChain callback handler for AgentNotes."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any, Dict, List, Optional, Union
|
|
6
|
+
from uuid import UUID
|
|
7
|
+
|
|
8
|
+
from aiagentnotes.client import AgentNotes
|
|
9
|
+
|
|
10
|
+
try:
|
|
11
|
+
from langchain_core.callbacks import BaseCallbackHandler
|
|
12
|
+
from langchain_core.agents import AgentAction, AgentFinish
|
|
13
|
+
from langchain_core.outputs import LLMResult
|
|
14
|
+
except ImportError as e:
|
|
15
|
+
raise ImportError(
|
|
16
|
+
"LangChain integration requires langchain-core: pip install langchain-core"
|
|
17
|
+
) from e
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class AgentNotesCallbackHandler(BaseCallbackHandler):
|
|
21
|
+
"""Log LangChain chains, tools, and LLM calls to AgentNotes."""
|
|
22
|
+
|
|
23
|
+
def __init__(self, notes: AgentNotes) -> None:
|
|
24
|
+
super().__init__()
|
|
25
|
+
self.notes = notes
|
|
26
|
+
if not notes._run_id:
|
|
27
|
+
notes.start_run()
|
|
28
|
+
|
|
29
|
+
def on_chain_start(
|
|
30
|
+
self,
|
|
31
|
+
serialized: Dict[str, Any],
|
|
32
|
+
inputs: Dict[str, Any],
|
|
33
|
+
*,
|
|
34
|
+
run_id: UUID,
|
|
35
|
+
parent_run_id: Optional[UUID] = None,
|
|
36
|
+
**kwargs: Any,
|
|
37
|
+
) -> None:
|
|
38
|
+
name = serialized.get("name") or serialized.get("id", ["chain"])[-1]
|
|
39
|
+
self.notes.log(
|
|
40
|
+
f"chain_start: {name}",
|
|
41
|
+
step_name=str(name),
|
|
42
|
+
data={"inputs_keys": list(inputs.keys())},
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
def on_chain_end(
|
|
46
|
+
self,
|
|
47
|
+
outputs: Dict[str, Any],
|
|
48
|
+
*,
|
|
49
|
+
run_id: UUID,
|
|
50
|
+
parent_run_id: Optional[UUID] = None,
|
|
51
|
+
**kwargs: Any,
|
|
52
|
+
) -> None:
|
|
53
|
+
self.notes.log("chain_end", data={"output_keys": list(outputs.keys())})
|
|
54
|
+
|
|
55
|
+
def on_tool_start(
|
|
56
|
+
self,
|
|
57
|
+
serialized: Dict[str, Any],
|
|
58
|
+
input_str: str,
|
|
59
|
+
*,
|
|
60
|
+
run_id: UUID,
|
|
61
|
+
parent_run_id: Optional[UUID] = None,
|
|
62
|
+
**kwargs: Any,
|
|
63
|
+
) -> None:
|
|
64
|
+
tool = serialized.get("name", "tool")
|
|
65
|
+
self.notes.log(
|
|
66
|
+
f"tool_start: {tool}",
|
|
67
|
+
step_name=tool,
|
|
68
|
+
data={"input_preview": input_str[:500]},
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
def on_tool_end(
|
|
72
|
+
self,
|
|
73
|
+
output: str,
|
|
74
|
+
*,
|
|
75
|
+
run_id: UUID,
|
|
76
|
+
parent_run_id: Optional[UUID] = None,
|
|
77
|
+
**kwargs: Any,
|
|
78
|
+
) -> None:
|
|
79
|
+
self.notes.log("tool_end", data={"output_preview": str(output)[:500]})
|
|
80
|
+
|
|
81
|
+
def on_tool_error(
|
|
82
|
+
self,
|
|
83
|
+
error: BaseException,
|
|
84
|
+
*,
|
|
85
|
+
run_id: UUID,
|
|
86
|
+
parent_run_id: Optional[UUID] = None,
|
|
87
|
+
**kwargs: Any,
|
|
88
|
+
) -> None:
|
|
89
|
+
self.notes.log_error(error)
|
|
90
|
+
|
|
91
|
+
def on_llm_end(
|
|
92
|
+
self,
|
|
93
|
+
response: LLMResult,
|
|
94
|
+
*,
|
|
95
|
+
run_id: UUID,
|
|
96
|
+
parent_run_id: Optional[UUID] = None,
|
|
97
|
+
**kwargs: Any,
|
|
98
|
+
) -> None:
|
|
99
|
+
self.notes.log("llm_end", data={"generations": len(response.generations)})
|
|
100
|
+
|
|
101
|
+
def on_agent_action(
|
|
102
|
+
self,
|
|
103
|
+
action: AgentAction,
|
|
104
|
+
*,
|
|
105
|
+
run_id: UUID,
|
|
106
|
+
parent_run_id: Optional[UUID] = None,
|
|
107
|
+
**kwargs: Any,
|
|
108
|
+
) -> None:
|
|
109
|
+
self.notes.log(
|
|
110
|
+
f"agent_action: {action.tool}",
|
|
111
|
+
step_name=action.tool,
|
|
112
|
+
data={"tool_input": str(action.tool_input)[:300]},
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
def on_agent_finish(
|
|
116
|
+
self,
|
|
117
|
+
finish: AgentFinish,
|
|
118
|
+
*,
|
|
119
|
+
run_id: UUID,
|
|
120
|
+
parent_run_id: Optional[UUID] = None,
|
|
121
|
+
**kwargs: Any,
|
|
122
|
+
) -> None:
|
|
123
|
+
self.notes.log(
|
|
124
|
+
"agent_finish",
|
|
125
|
+
data={"output_preview": str(finish.return_values)[:500]},
|
|
126
|
+
)
|
aiagentnotes/paths.py
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: aiagentnotes
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: SparkNotes for your AI agents — log runs to AgentNotes with one pip install and env vars.
|
|
5
|
+
Project-URL: Homepage, https://github.com/mattmerrick/agentnotes
|
|
6
|
+
Project-URL: Documentation, https://github.com/mattmerrick/agentnotes/tree/main/sdk
|
|
7
|
+
Project-URL: Repository, https://github.com/mattmerrick/agentnotes
|
|
8
|
+
Author-email: AgentNotes <yomatt41@gmail.com>
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
Keywords: agents,ai,langchain,logging,observability,railway
|
|
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: Topic :: Software Development :: Libraries :: Python Modules
|
|
21
|
+
Requires-Python: >=3.9
|
|
22
|
+
Requires-Dist: requests>=2.28.0
|
|
23
|
+
Provides-Extra: dev
|
|
24
|
+
Requires-Dist: build; extra == 'dev'
|
|
25
|
+
Requires-Dist: pytest>=7.0; extra == 'dev'
|
|
26
|
+
Requires-Dist: twine; extra == 'dev'
|
|
27
|
+
Provides-Extra: dotenv
|
|
28
|
+
Requires-Dist: python-dotenv>=1.0.0; extra == 'dotenv'
|
|
29
|
+
Provides-Extra: langchain
|
|
30
|
+
Requires-Dist: langchain-core>=0.2.0; extra == 'langchain'
|
|
31
|
+
Description-Content-Type: text/markdown
|
|
32
|
+
|
|
33
|
+
# AgentNotes Python SDK
|
|
34
|
+
|
|
35
|
+
SparkNotes for your AI agents. Install once, set env vars, call `AgentNotes.from_env()`.
|
|
36
|
+
|
|
37
|
+
## Install (PyPI)
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
pip install aiagentnotes
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Optional local `.env` support:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
pip install "aiagentnotes[dotenv]"
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Environment variables
|
|
50
|
+
|
|
51
|
+
See **[ENV.md](./ENV.md)** for the full list.
|
|
52
|
+
|
|
53
|
+
**Minimum:**
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
export AGENTNOTES_API_KEY="an_xxxxxxxx"
|
|
57
|
+
export AGENTNOTES_AGENT_ID="my-bot-slug"
|
|
58
|
+
export AGENTNOTES_BASE_URL="https://your-agentnotes.vercel.app"
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Quick start
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
from aiagentnotes import AgentNotes
|
|
65
|
+
|
|
66
|
+
notes = AgentNotes.from_env()
|
|
67
|
+
|
|
68
|
+
@notes.track("handle_request")
|
|
69
|
+
def handle_request(req):
|
|
70
|
+
notes.log("received", data={"id": getattr(req, "id", None)})
|
|
71
|
+
notes.complete_run({"summary": "Done"})
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Optional: logging only when configured
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
from aiagentnotes import get_client
|
|
78
|
+
|
|
79
|
+
notes = get_client()
|
|
80
|
+
if notes:
|
|
81
|
+
notes.log("bot started")
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## API
|
|
85
|
+
|
|
86
|
+
| Method | Description |
|
|
87
|
+
|--------|-------------|
|
|
88
|
+
| `AgentNotes.from_env()` | Build client from `AGENTNOTES_*` env vars |
|
|
89
|
+
| `get_client()` | Same, or `None` if not configured |
|
|
90
|
+
| `require_client()` | Same, or raise with setup instructions |
|
|
91
|
+
| `notes.log(message, ...)` | Log an event |
|
|
92
|
+
| `notes.complete_run(result)` | Finish run |
|
|
93
|
+
| `notes.track("step")` | Decorator for a step |
|
|
94
|
+
|
|
95
|
+
## Development (this monorepo)
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
cd sdk && pip install -e ".[dev,dotenv]"
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Publish to PyPI
|
|
102
|
+
|
|
103
|
+
See [../docs/PUBLISHING.md](../docs/PUBLISHING.md).
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
aiagentnotes/__init__.py,sha256=fvLUoM4cNmiQdzZDaZvQ1bqXb9Yu_S0Lcnf5XmGU9Mg,455
|
|
2
|
+
aiagentnotes/client.py,sha256=clDxD5pc-TagkHYyFuxNCoW3QNFZaqK06eNQg7GL9r0,7175
|
|
3
|
+
aiagentnotes/env.py,sha256=W38kxhyi6X9NLL9-lc0ih_tHIQnEWJSmDMpLvaOlfLA,1843
|
|
4
|
+
aiagentnotes/paths.py,sha256=NNW4TVbmTCSOqfwWKPTmuruaimdnTGXnwtmjyBI8dNk,179
|
|
5
|
+
aiagentnotes/integrations/__init__.py,sha256=yn0cq43EquaRZkTPgnDl5_20js-JT9afmQlwFOyiOqs,58
|
|
6
|
+
aiagentnotes/integrations/crewai.py,sha256=0volZ8jHSkGea3luvavfbBAdEB0E3eQ9Z5YSn3Ux1YA,890
|
|
7
|
+
aiagentnotes/integrations/langchain.py,sha256=c8tiTWAeWGtgRif7-ycgaFHBOeIRQHO-GZ5rPRGG5Pw,3373
|
|
8
|
+
aiagentnotes-0.1.0.dist-info/METADATA,sha256=OS36zSUg0JXvMXrUwS6VNl-AaZ9JWyoopiiQXPqf9dM,2876
|
|
9
|
+
aiagentnotes-0.1.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
|
|
10
|
+
aiagentnotes-0.1.0.dist-info/RECORD,,
|