foil-sdk 0.5.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.
- foil_sdk-0.5.0/LICENSE +21 -0
- foil_sdk-0.5.0/PKG-INFO +665 -0
- foil_sdk-0.5.0/README.md +631 -0
- foil_sdk-0.5.0/examples/01_customer_support_bot.py +265 -0
- foil_sdk-0.5.0/examples/02_rag_pipeline.py +225 -0
- foil_sdk-0.5.0/examples/03_ecommerce_product_agent.py +245 -0
- foil_sdk-0.5.0/examples/04_knowledge_base_assistant.py +251 -0
- foil_sdk-0.5.0/examples/05_content_generation_agent.py +261 -0
- foil_sdk-0.5.0/examples/_demo_utils.py +179 -0
- foil_sdk-0.5.0/examples/custom_evaluations_example.py +380 -0
- foil_sdk-0.5.0/examples/customer_support_agent_example.py +563 -0
- foil_sdk-0.5.0/examples/multimodal_test.py +366 -0
- foil_sdk-0.5.0/examples/openai_agent_test.py +364 -0
- foil_sdk-0.5.0/examples/otel_example.py +110 -0
- foil_sdk-0.5.0/examples/semantic_search_example.py +176 -0
- foil_sdk-0.5.0/examples/signals_test.py +648 -0
- foil_sdk-0.5.0/examples/tracer_test.py +329 -0
- foil_sdk-0.5.0/examples/user_device_tracking_test.py +118 -0
- foil_sdk-0.5.0/foil/__init__.py +57 -0
- foil_sdk-0.5.0/foil/client.py +1447 -0
- foil_sdk-0.5.0/foil/integrations/__init__.py +10 -0
- foil_sdk-0.5.0/foil/integrations/langchain.py +539 -0
- foil_sdk-0.5.0/foil/otel.py +643 -0
- foil_sdk-0.5.0/foil/py.typed +0 -0
- foil_sdk-0.5.0/foil/tracer.py +790 -0
- foil_sdk-0.5.0/foil_sdk.egg-info/PKG-INFO +665 -0
- foil_sdk-0.5.0/foil_sdk.egg-info/SOURCES.txt +32 -0
- foil_sdk-0.5.0/foil_sdk.egg-info/dependency_links.txt +1 -0
- foil_sdk-0.5.0/foil_sdk.egg-info/requires.txt +14 -0
- foil_sdk-0.5.0/foil_sdk.egg-info/top_level.txt +4 -0
- foil_sdk-0.5.0/pyproject.toml +51 -0
- foil_sdk-0.5.0/setup.cfg +4 -0
- foil_sdk-0.5.0/tests/__init__.py +1 -0
- foil_sdk-0.5.0/tests/test_otel.py +463 -0
foil_sdk-0.5.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Foil
|
|
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.
|
foil_sdk-0.5.0/PKG-INFO
ADDED
|
@@ -0,0 +1,665 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: foil-sdk
|
|
3
|
+
Version: 0.5.0
|
|
4
|
+
Summary: Foil SDK for monitoring and logging AI model invocations
|
|
5
|
+
Author: Foil
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://getfoil.ai
|
|
8
|
+
Project-URL: Documentation, https://docs.getfoil.ai
|
|
9
|
+
Project-URL: Repository, https://github.com/getfoil/foil
|
|
10
|
+
Keywords: ai,monitoring,logging,openai,llm,opentelemetry,tracing
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
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
|
+
Requires-Python: >=3.8
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
Requires-Dist: requests>=2.25.0
|
|
23
|
+
Provides-Extra: dev
|
|
24
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
25
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
|
|
26
|
+
Provides-Extra: otel
|
|
27
|
+
Requires-Dist: opentelemetry-api>=1.20.0; extra == "otel"
|
|
28
|
+
Requires-Dist: opentelemetry-sdk>=1.20.0; extra == "otel"
|
|
29
|
+
Provides-Extra: openllmetry
|
|
30
|
+
Requires-Dist: opentelemetry-api>=1.20.0; extra == "openllmetry"
|
|
31
|
+
Requires-Dist: opentelemetry-sdk>=1.20.0; extra == "openllmetry"
|
|
32
|
+
Requires-Dist: traceloop-sdk>=0.15.0; extra == "openllmetry"
|
|
33
|
+
Dynamic: license-file
|
|
34
|
+
|
|
35
|
+
# Foil Python SDK
|
|
36
|
+
|
|
37
|
+
Python SDK for monitoring and logging AI agent invocations with Foil. Features distributed tracing, semantic search, custom evaluations, multimodal content support (images, documents), signals/feedback, and OpenAI integration.
|
|
38
|
+
|
|
39
|
+
## Installation
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
pip install foil-sdk
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Or install from source:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
pip install -e /path/to/foil-sdk
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Quick Start
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
from foil import Foil, create_foil_tracer, SpanKind
|
|
55
|
+
|
|
56
|
+
# Create a tracer for your agent
|
|
57
|
+
tracer = create_foil_tracer(
|
|
58
|
+
api_key="your-api-key",
|
|
59
|
+
agent_name="my-agent",
|
|
60
|
+
base_url="https://api.foil.dev/api", # or your self-hosted URL
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
# Trace an agent execution
|
|
64
|
+
async def my_agent():
|
|
65
|
+
async with tracer.trace("weather-query", input="User asked about weather") as ctx:
|
|
66
|
+
# Create an LLM span
|
|
67
|
+
span = await ctx.start_span(SpanKind.LLM, "gpt-4", input="What is the weather?")
|
|
68
|
+
|
|
69
|
+
# ... call your LLM ...
|
|
70
|
+
|
|
71
|
+
await span.end(
|
|
72
|
+
output="The weather is sunny today!",
|
|
73
|
+
tokens={"prompt": 10, "completion": 8, "total": 18}
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
return "Agent completed"
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Table of Contents
|
|
80
|
+
|
|
81
|
+
- [Distributed Tracing](#distributed-tracing)
|
|
82
|
+
- [Semantic Search](#semantic-search)
|
|
83
|
+
- [Custom Evaluations](#custom-evaluations)
|
|
84
|
+
- [Multimodal Content](#multimodal-content)
|
|
85
|
+
- [Signals & Feedback](#signals--feedback)
|
|
86
|
+
- [OpenAI Integration](#openai-integration)
|
|
87
|
+
- [Experiments (A/B Testing)](#experiments-ab-testing)
|
|
88
|
+
- [Low-Level API](#low-level-api)
|
|
89
|
+
- [API Reference](#api-reference)
|
|
90
|
+
- [Examples](#examples)
|
|
91
|
+
|
|
92
|
+
## Distributed Tracing
|
|
93
|
+
|
|
94
|
+
### Using the Tracer
|
|
95
|
+
|
|
96
|
+
```python
|
|
97
|
+
from foil import create_foil_tracer, SpanKind
|
|
98
|
+
|
|
99
|
+
tracer = create_foil_tracer(
|
|
100
|
+
api_key="your-api-key",
|
|
101
|
+
agent_name="customer-support-agent",
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
async with tracer.trace("support-query", input=user_message, session_id=conversation_id) as ctx:
|
|
105
|
+
# LLM span for the main model call
|
|
106
|
+
span = await ctx.start_span(SpanKind.LLM, "gpt-4-turbo", input=messages)
|
|
107
|
+
|
|
108
|
+
response = await openai.chat.completions.create(
|
|
109
|
+
model="gpt-4-turbo",
|
|
110
|
+
messages=messages,
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
await span.end(
|
|
114
|
+
output=response.choices[0].message.content,
|
|
115
|
+
tokens={
|
|
116
|
+
"prompt": response.usage.prompt_tokens,
|
|
117
|
+
"completion": response.usage.completion_tokens,
|
|
118
|
+
"total": response.usage.total_tokens,
|
|
119
|
+
}
|
|
120
|
+
)
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Span Kinds
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
from foil import SpanKind
|
|
127
|
+
|
|
128
|
+
SpanKind.AGENT # Root agent span
|
|
129
|
+
SpanKind.LLM # Language model calls
|
|
130
|
+
SpanKind.TOOL # Tool/function executions
|
|
131
|
+
SpanKind.CHAIN # Chain of operations
|
|
132
|
+
SpanKind.RETRIEVER # RAG retrieval operations
|
|
133
|
+
SpanKind.EMBEDDING # Embedding model calls
|
|
134
|
+
SpanKind.CUSTOM # Custom operation types
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Nested Spans
|
|
138
|
+
|
|
139
|
+
```python
|
|
140
|
+
async with tracer.trace("order-lookup") as ctx:
|
|
141
|
+
# LLM decides to use a tool
|
|
142
|
+
llm_span = await ctx.start_span(SpanKind.LLM, "gpt-4", input="Find order #12345")
|
|
143
|
+
|
|
144
|
+
# Tool execution (child of LLM span)
|
|
145
|
+
tool_span = await ctx.start_span(SpanKind.TOOL, "lookup_order", input={"order_id": "12345"})
|
|
146
|
+
result = await database.find_order("12345")
|
|
147
|
+
await tool_span.end(output=result)
|
|
148
|
+
|
|
149
|
+
await llm_span.end(output=f"Order found: {result}")
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## Semantic Search
|
|
153
|
+
|
|
154
|
+
Search through your traces using natural language queries. Foil automatically embeds your trace content and enables AI-powered semantic search.
|
|
155
|
+
|
|
156
|
+
### Basic Search
|
|
157
|
+
|
|
158
|
+
```python
|
|
159
|
+
from foil import Foil
|
|
160
|
+
|
|
161
|
+
foil = Foil(
|
|
162
|
+
api_key="your-api-key",
|
|
163
|
+
base_url="https://api.foil.dev/api",
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
# Search for conversations about a topic
|
|
167
|
+
results = foil.semantic_search("conversations about refund requests")
|
|
168
|
+
|
|
169
|
+
print(f"Found {len(results['results'])} matching traces")
|
|
170
|
+
for result in results["results"]:
|
|
171
|
+
print(f"Trace: {result['traceId']}, Similarity: {result['similarity'] * 100:.1f}%")
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### Search with Filters
|
|
175
|
+
|
|
176
|
+
```python
|
|
177
|
+
results = foil.semantic_search(
|
|
178
|
+
"user asking about pricing",
|
|
179
|
+
agent_id="customer-support-agent", # Filter by specific agent
|
|
180
|
+
agent_name="support-bot", # Or filter by agent name
|
|
181
|
+
from_date="2024-01-01", # Start date (ISO format)
|
|
182
|
+
to_date="2024-12-31", # End date (ISO format)
|
|
183
|
+
limit=10, # Max results (default: 20)
|
|
184
|
+
offset=0, # Pagination offset
|
|
185
|
+
threshold=0.4, # Min similarity score 0-1 (default: 0.3)
|
|
186
|
+
)
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### Find Similar Traces
|
|
190
|
+
|
|
191
|
+
Find traces that are semantically similar to a specific trace:
|
|
192
|
+
|
|
193
|
+
```python
|
|
194
|
+
# Find traces similar to a known trace
|
|
195
|
+
similar = foil.find_similar_traces(
|
|
196
|
+
"trace-abc-123",
|
|
197
|
+
limit=5,
|
|
198
|
+
threshold=0.4,
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
print(f"Found {len(similar['results'])} similar traces")
|
|
202
|
+
for result in similar["results"]:
|
|
203
|
+
print(f"{result['traceId']}: {result['similarity'] * 100:.1f}% similar")
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### Check Semantic Search Status
|
|
207
|
+
|
|
208
|
+
```python
|
|
209
|
+
# Get overall embedding status
|
|
210
|
+
status = foil.get_semantic_search_status()
|
|
211
|
+
print(f"Embedded: {status['embeddedSpans']} / {status['totalSpans']} spans")
|
|
212
|
+
print(f"Coverage: {status['coveragePercent']}%")
|
|
213
|
+
print(f"Ready: {status['ready']}")
|
|
214
|
+
|
|
215
|
+
# Get status for specific agent
|
|
216
|
+
agent_status = foil.get_semantic_search_status("my-agent-id")
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
## Custom Evaluations
|
|
220
|
+
|
|
221
|
+
Define custom evaluation criteria to analyze your agent's responses. Evaluations run automatically on new traces and can be boolean, score-based, or categorical.
|
|
222
|
+
|
|
223
|
+
### Get Evaluation Templates
|
|
224
|
+
|
|
225
|
+
```python
|
|
226
|
+
# List available pre-built evaluation templates
|
|
227
|
+
templates = foil.get_evaluation_templates()
|
|
228
|
+
|
|
229
|
+
for t in templates.get("templates", []):
|
|
230
|
+
print(f"{t['name']}: {t.get('description', 'N/A')}")
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### Create a Boolean Evaluation
|
|
234
|
+
|
|
235
|
+
```python
|
|
236
|
+
evaluation = foil.create_evaluation("agent-123", {
|
|
237
|
+
"name": "professional_tone",
|
|
238
|
+
"description": "Checks if the response maintains a professional tone",
|
|
239
|
+
"prompt": """Evaluate the assistant's response for professional tone.
|
|
240
|
+
|
|
241
|
+
Consider:
|
|
242
|
+
- Is the language respectful and courteous?
|
|
243
|
+
- Does it avoid slang or casual expressions?
|
|
244
|
+
- Is it helpful without being condescending?
|
|
245
|
+
|
|
246
|
+
Return true if professional, false otherwise.""",
|
|
247
|
+
"evaluationType": "boolean",
|
|
248
|
+
"enabled": True,
|
|
249
|
+
})
|
|
250
|
+
|
|
251
|
+
print(f"Created evaluation: {evaluation['evaluation']['name']}")
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
### Create a Score Evaluation (1-10)
|
|
255
|
+
|
|
256
|
+
```python
|
|
257
|
+
evaluation = foil.create_evaluation("agent-123", {
|
|
258
|
+
"name": "helpfulness_score",
|
|
259
|
+
"description": "Rates response helpfulness on a scale of 1-10",
|
|
260
|
+
"prompt": """Rate the helpfulness of the response on a scale of 1-10.
|
|
261
|
+
|
|
262
|
+
Scoring:
|
|
263
|
+
- 1-3: Unhelpful
|
|
264
|
+
- 4-5: Somewhat helpful
|
|
265
|
+
- 6-7: Helpful
|
|
266
|
+
- 8-9: Very helpful
|
|
267
|
+
- 10: Exceptional
|
|
268
|
+
|
|
269
|
+
Return a single number from 1 to 10.""",
|
|
270
|
+
"evaluationType": "score",
|
|
271
|
+
"scoreMin": 1,
|
|
272
|
+
"scoreMax": 10,
|
|
273
|
+
"enabled": True,
|
|
274
|
+
})
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
### Create a Category Evaluation
|
|
278
|
+
|
|
279
|
+
```python
|
|
280
|
+
evaluation = foil.create_evaluation("agent-123", {
|
|
281
|
+
"name": "response_intent",
|
|
282
|
+
"description": "Categorizes the type of response",
|
|
283
|
+
"prompt": """Classify the response into one category:
|
|
284
|
+
|
|
285
|
+
- informational: Provides facts or explanations
|
|
286
|
+
- action: Guides through a process
|
|
287
|
+
- clarification: Asks for more information
|
|
288
|
+
- escalation: Suggests human assistance
|
|
289
|
+
- closure: Wraps up the conversation
|
|
290
|
+
|
|
291
|
+
Return only the category name.""",
|
|
292
|
+
"evaluationType": "category",
|
|
293
|
+
"categories": ["informational", "action", "clarification", "escalation", "closure"],
|
|
294
|
+
"enabled": True,
|
|
295
|
+
})
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
### Test an Evaluation
|
|
299
|
+
|
|
300
|
+
Validate your evaluation prompt before enabling:
|
|
301
|
+
|
|
302
|
+
```python
|
|
303
|
+
result = foil.test_evaluation("agent-123", evaluation_id, {
|
|
304
|
+
"input": "How do I reset my password?",
|
|
305
|
+
"output": "I'd be happy to help! Go to Settings > Security > Reset Password.",
|
|
306
|
+
})
|
|
307
|
+
|
|
308
|
+
print(f"Result: {result['result']}") # true/false, score, or category
|
|
309
|
+
print(f"Reasoning: {result['reasoning']}") # Explanation
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
### Add Few-Shot Examples
|
|
313
|
+
|
|
314
|
+
Improve evaluation accuracy with examples:
|
|
315
|
+
|
|
316
|
+
```python
|
|
317
|
+
# Add a positive example
|
|
318
|
+
foil.add_evaluation_example("agent-123", evaluation_id, {
|
|
319
|
+
"input": "Why is my order late?",
|
|
320
|
+
"output": "I apologize for the delay. Let me check the status for you.",
|
|
321
|
+
"expectedResult": True,
|
|
322
|
+
"reasoning": "Professional and helpful response",
|
|
323
|
+
})
|
|
324
|
+
|
|
325
|
+
# Add a negative example
|
|
326
|
+
foil.add_evaluation_example("agent-123", evaluation_id, {
|
|
327
|
+
"input": "Why is my order late?",
|
|
328
|
+
"output": "idk check tracking yourself",
|
|
329
|
+
"expectedResult": False,
|
|
330
|
+
"reasoning": "Casual and unhelpful",
|
|
331
|
+
})
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
### Manage Evaluations
|
|
335
|
+
|
|
336
|
+
```python
|
|
337
|
+
# List all evaluations for an agent
|
|
338
|
+
evaluations = foil.get_agent_evaluations("agent-123")
|
|
339
|
+
|
|
340
|
+
# Get specific evaluation details
|
|
341
|
+
evaluation = foil.get_evaluation("agent-123", evaluation_id)
|
|
342
|
+
|
|
343
|
+
# Update an evaluation
|
|
344
|
+
foil.update_evaluation("agent-123", evaluation_id, {
|
|
345
|
+
"description": "Updated description",
|
|
346
|
+
"enabled": False, # Disable temporarily
|
|
347
|
+
})
|
|
348
|
+
|
|
349
|
+
# Get evaluation analytics
|
|
350
|
+
analytics = foil.get_evaluation_analytics("agent-123", evaluation_id)
|
|
351
|
+
print(f"Total evaluations: {analytics.get('totalEvaluations', 0)}")
|
|
352
|
+
print(f"Distribution: {analytics.get('distribution')}")
|
|
353
|
+
|
|
354
|
+
# Clone a template
|
|
355
|
+
cloned = foil.clone_evaluation_template("agent-123", template_id)
|
|
356
|
+
|
|
357
|
+
# Remove an example
|
|
358
|
+
foil.remove_evaluation_example("agent-123", evaluation_id, example_id)
|
|
359
|
+
|
|
360
|
+
# Delete an evaluation
|
|
361
|
+
foil.delete_evaluation("agent-123", evaluation_id)
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
## Multimodal Content
|
|
365
|
+
|
|
366
|
+
Foil supports multimodal input/output including images, documents, and other media types.
|
|
367
|
+
|
|
368
|
+
### Media Categories
|
|
369
|
+
|
|
370
|
+
```python
|
|
371
|
+
from foil import MediaCategory
|
|
372
|
+
|
|
373
|
+
MediaCategory.IMAGE # Images (png, jpg, gif, webp, etc.)
|
|
374
|
+
MediaCategory.DOCUMENT # Documents (pdf, doc, docx, etc.)
|
|
375
|
+
MediaCategory.SPREADSHEET # Spreadsheets (xlsx, csv, etc.)
|
|
376
|
+
MediaCategory.CODE # Code files
|
|
377
|
+
MediaCategory.AUDIO # Audio files
|
|
378
|
+
MediaCategory.VIDEO # Video files
|
|
379
|
+
MediaCategory.ARCHIVE # Archives (zip, tar, etc.)
|
|
380
|
+
MediaCategory.NOTEBOOK # Jupyter notebooks
|
|
381
|
+
MediaCategory.OTHER # Other file types
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
### Uploading Media
|
|
385
|
+
|
|
386
|
+
```python
|
|
387
|
+
from foil import Foil
|
|
388
|
+
|
|
389
|
+
foil = Foil(
|
|
390
|
+
api_key="your-api-key",
|
|
391
|
+
base_url="https://api.foil.dev/api",
|
|
392
|
+
)
|
|
393
|
+
|
|
394
|
+
# Upload from file path
|
|
395
|
+
result = foil.upload_media("/path/to/image.png")
|
|
396
|
+
print(result["mediaId"]) # 'media_abc123'
|
|
397
|
+
print(result["category"]) # 'image'
|
|
398
|
+
print(result["filename"]) # 'image.png'
|
|
399
|
+
print(result["mimeType"]) # 'image/png'
|
|
400
|
+
|
|
401
|
+
# Upload from bytes
|
|
402
|
+
with open("/path/to/document.pdf", "rb") as f:
|
|
403
|
+
content = f.read()
|
|
404
|
+
result = foil.upload_media(
|
|
405
|
+
content,
|
|
406
|
+
filename="document.pdf",
|
|
407
|
+
mime_type="application/pdf",
|
|
408
|
+
)
|
|
409
|
+
|
|
410
|
+
# Upload with trace/span association
|
|
411
|
+
result = foil.upload_media(
|
|
412
|
+
"/path/to/image.png",
|
|
413
|
+
trace_id="trace_123",
|
|
414
|
+
span_id="span_456",
|
|
415
|
+
direction="input", # or "output"
|
|
416
|
+
)
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
### Content Blocks
|
|
420
|
+
|
|
421
|
+
Use content blocks to create multimodal input/output:
|
|
422
|
+
|
|
423
|
+
```python
|
|
424
|
+
from foil import content, ContentBlock, MediaCategory
|
|
425
|
+
|
|
426
|
+
# Create multimodal content array
|
|
427
|
+
multimodal_input = content(
|
|
428
|
+
"Please analyze this image:",
|
|
429
|
+
ContentBlock.media(
|
|
430
|
+
upload_result["mediaId"],
|
|
431
|
+
category=MediaCategory.IMAGE,
|
|
432
|
+
filename="photo.jpg",
|
|
433
|
+
mime_type="image/jpeg",
|
|
434
|
+
),
|
|
435
|
+
"Focus on the composition and colors."
|
|
436
|
+
)
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
### Media Retrieval
|
|
440
|
+
|
|
441
|
+
```python
|
|
442
|
+
# Get media information
|
|
443
|
+
media_info = foil.get_media(media_id)
|
|
444
|
+
|
|
445
|
+
# Get presigned URL for download
|
|
446
|
+
url_info = foil.get_media_url(media_id, "original")
|
|
447
|
+
print(url_info["url"]) # Presigned S3 URL
|
|
448
|
+
|
|
449
|
+
# Get extracted content URL (for documents)
|
|
450
|
+
extracted_url = foil.get_media_url(media_id, "extracted")
|
|
451
|
+
|
|
452
|
+
# Batch get multiple media
|
|
453
|
+
batch_info = foil.batch_media_info([media_id1, media_id2, media_id3])
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
## Signals & Feedback
|
|
457
|
+
|
|
458
|
+
Record user feedback and custom signals for your traces:
|
|
459
|
+
|
|
460
|
+
```python
|
|
461
|
+
from foil import Foil
|
|
462
|
+
|
|
463
|
+
foil = Foil(api_key="your-api-key")
|
|
464
|
+
|
|
465
|
+
# Record a signal for a specific trace
|
|
466
|
+
foil.record_signal({
|
|
467
|
+
"traceId": "trace_123",
|
|
468
|
+
"signalName": "user_satisfaction",
|
|
469
|
+
"value": 4,
|
|
470
|
+
"signalType": "feedback",
|
|
471
|
+
"source": "user",
|
|
472
|
+
})
|
|
473
|
+
|
|
474
|
+
# Batch record signals
|
|
475
|
+
foil.record_signal_batch([
|
|
476
|
+
{"traceId": "trace_123", "signalName": "thumbs", "value": True, "source": "user"},
|
|
477
|
+
{"traceId": "trace_123", "signalName": "rating", "value": 5, "source": "user"},
|
|
478
|
+
])
|
|
479
|
+
|
|
480
|
+
# Get signals for a trace
|
|
481
|
+
signals = foil.get_trace_signals("trace_123")
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
## OpenAI Integration
|
|
485
|
+
|
|
486
|
+
Automatic logging for OpenAI calls:
|
|
487
|
+
|
|
488
|
+
```python
|
|
489
|
+
from openai import OpenAI
|
|
490
|
+
from foil import Foil
|
|
491
|
+
|
|
492
|
+
foil = Foil(api_key="your-foil-api-key")
|
|
493
|
+
client = foil.wrap_openai(OpenAI())
|
|
494
|
+
|
|
495
|
+
# All chat completion calls are now automatically logged
|
|
496
|
+
response = client.chat.completions.create(
|
|
497
|
+
model="gpt-4",
|
|
498
|
+
messages=[{"role": "user", "content": "Hello!"}]
|
|
499
|
+
)
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
Supports both streaming and non-streaming responses.
|
|
503
|
+
|
|
504
|
+
## Experiments (A/B Testing)
|
|
505
|
+
|
|
506
|
+
Get variant assignments for experiments:
|
|
507
|
+
|
|
508
|
+
```python
|
|
509
|
+
from foil import Foil
|
|
510
|
+
|
|
511
|
+
foil = Foil(api_key="your-api-key")
|
|
512
|
+
|
|
513
|
+
# Get assigned variant for a user
|
|
514
|
+
assignment = foil.get_experiment_variant("experiment_123", user_id)
|
|
515
|
+
|
|
516
|
+
if assignment["inExperiment"]:
|
|
517
|
+
print(assignment["variantName"]) # 'control' or 'treatment'
|
|
518
|
+
print(assignment["config"]) # {'model': 'gpt-4', 'temperature': 0.7}
|
|
519
|
+
```
|
|
520
|
+
|
|
521
|
+
## Low-Level API
|
|
522
|
+
|
|
523
|
+
For more control, use the Foil client directly:
|
|
524
|
+
|
|
525
|
+
```python
|
|
526
|
+
from foil import Foil
|
|
527
|
+
|
|
528
|
+
foil = Foil(
|
|
529
|
+
api_key="your-api-key",
|
|
530
|
+
base_url="https://api.foil.dev/api",
|
|
531
|
+
)
|
|
532
|
+
|
|
533
|
+
# Manual span management
|
|
534
|
+
trace_id = foil.create_trace_id()
|
|
535
|
+
span_id = foil.create_span_id()
|
|
536
|
+
|
|
537
|
+
foil.start_span({
|
|
538
|
+
"spanId": span_id,
|
|
539
|
+
"traceId": trace_id,
|
|
540
|
+
"name": "gpt-4",
|
|
541
|
+
"agentName": "my-agent",
|
|
542
|
+
"spanKind": "llm",
|
|
543
|
+
"input": messages,
|
|
544
|
+
})
|
|
545
|
+
|
|
546
|
+
# ... do work ...
|
|
547
|
+
|
|
548
|
+
foil.end_span({
|
|
549
|
+
"spanId": span_id,
|
|
550
|
+
"traceId": trace_id,
|
|
551
|
+
"agentName": "my-agent",
|
|
552
|
+
"output": response,
|
|
553
|
+
"tokens": {"prompt": 100, "completion": 50, "total": 150},
|
|
554
|
+
})
|
|
555
|
+
|
|
556
|
+
# Get trace data
|
|
557
|
+
trace = foil.get_trace(trace_id)
|
|
558
|
+
|
|
559
|
+
# List traces
|
|
560
|
+
traces = foil.list_traces(
|
|
561
|
+
agent_name="my-agent",
|
|
562
|
+
limit=10,
|
|
563
|
+
from_date="2024-01-01T00:00:00Z",
|
|
564
|
+
)
|
|
565
|
+
```
|
|
566
|
+
|
|
567
|
+
## API Reference
|
|
568
|
+
|
|
569
|
+
### Foil Client
|
|
570
|
+
|
|
571
|
+
#### Initialization
|
|
572
|
+
|
|
573
|
+
```python
|
|
574
|
+
foil = Foil(api_key="your-api-key", base_url="https://api.getfoil.ai/api")
|
|
575
|
+
```
|
|
576
|
+
|
|
577
|
+
| Parameter | Type | Required | Description |
|
|
578
|
+
|-----------|------|----------|-------------|
|
|
579
|
+
| `api_key` | str | Yes | Your Foil API key |
|
|
580
|
+
| `base_url` | str | No | API base URL (default: https://api.getfoil.ai) |
|
|
581
|
+
|
|
582
|
+
### Semantic Search Methods
|
|
583
|
+
|
|
584
|
+
| Method | Description |
|
|
585
|
+
|--------|-------------|
|
|
586
|
+
| `semantic_search(query, **options)` | Search spans using natural language |
|
|
587
|
+
| `find_similar_traces(trace_id, **options)` | Find traces similar to a given trace |
|
|
588
|
+
| `get_semantic_search_status(agent_id=None)` | Get embedding statistics |
|
|
589
|
+
|
|
590
|
+
### Custom Evaluation Methods
|
|
591
|
+
|
|
592
|
+
| Method | Description |
|
|
593
|
+
|--------|-------------|
|
|
594
|
+
| `get_evaluation_templates()` | List available evaluation templates |
|
|
595
|
+
| `get_agent_evaluations(agent_id)` | List evaluations for an agent |
|
|
596
|
+
| `get_evaluation(agent_id, evaluation_id)` | Get evaluation details |
|
|
597
|
+
| `create_evaluation(agent_id, data)` | Create a custom evaluation |
|
|
598
|
+
| `update_evaluation(agent_id, evaluation_id, data)` | Update an evaluation |
|
|
599
|
+
| `delete_evaluation(agent_id, evaluation_id)` | Delete an evaluation |
|
|
600
|
+
| `test_evaluation(agent_id, evaluation_id, data)` | Test with sample data |
|
|
601
|
+
| `clone_evaluation_template(agent_id, template_id)` | Clone a template |
|
|
602
|
+
| `add_evaluation_example(agent_id, evaluation_id, data)` | Add few-shot example |
|
|
603
|
+
| `remove_evaluation_example(agent_id, evaluation_id, example_id)` | Remove example |
|
|
604
|
+
| `get_evaluation_analytics(agent_id, evaluation_id)` | Get evaluation metrics |
|
|
605
|
+
|
|
606
|
+
### Media Methods
|
|
607
|
+
|
|
608
|
+
| Method | Description |
|
|
609
|
+
|--------|-------------|
|
|
610
|
+
| `upload_media(file, **options)` | Upload media for multimodal content |
|
|
611
|
+
| `get_media(media_id, content=None)` | Get media information |
|
|
612
|
+
| `get_media_url(media_id, content="original")` | Get presigned download URL |
|
|
613
|
+
| `batch_media_info(media_ids)` | Get info for multiple media |
|
|
614
|
+
|
|
615
|
+
### Signal Methods
|
|
616
|
+
|
|
617
|
+
| Method | Description |
|
|
618
|
+
|--------|-------------|
|
|
619
|
+
| `record_signal(data)` | Record a signal |
|
|
620
|
+
| `record_signal_batch(signals)` | Record multiple signals |
|
|
621
|
+
| `get_trace_signals(trace_id)` | Get signals for a trace |
|
|
622
|
+
|
|
623
|
+
### Trace Methods
|
|
624
|
+
|
|
625
|
+
| Method | Description |
|
|
626
|
+
|--------|-------------|
|
|
627
|
+
| `start_span(data)` | Start a span |
|
|
628
|
+
| `end_span(data)` | End a span |
|
|
629
|
+
| `get_trace(trace_id)` | Get trace with all spans |
|
|
630
|
+
| `list_traces(**options)` | List traces with filters |
|
|
631
|
+
|
|
632
|
+
### Experiment Methods
|
|
633
|
+
|
|
634
|
+
| Method | Description |
|
|
635
|
+
|--------|-------------|
|
|
636
|
+
| `get_experiment_variant(experiment_id, identifier)` | Get variant assignment |
|
|
637
|
+
|
|
638
|
+
### Legacy Methods (Deprecated)
|
|
639
|
+
|
|
640
|
+
| Method | Replacement |
|
|
641
|
+
|--------|-------------|
|
|
642
|
+
| `start_invocation(data)` | Use `start_span(data)` |
|
|
643
|
+
| `end_invocation(data)` | Use `end_span(data)` |
|
|
644
|
+
|
|
645
|
+
## Examples
|
|
646
|
+
|
|
647
|
+
See the `examples/` directory for complete working examples:
|
|
648
|
+
|
|
649
|
+
- `semantic_search_example.py` - Semantic search usage
|
|
650
|
+
- `custom_evaluations_example.py` - Custom evaluations
|
|
651
|
+
|
|
652
|
+
Run examples:
|
|
653
|
+
|
|
654
|
+
```bash
|
|
655
|
+
export FOIL_API_KEY=your-api-key
|
|
656
|
+
export FOIL_BASE_URL=https://api.getfoil.ai/api
|
|
657
|
+
export FOIL_AGENT_ID=your-agent-id
|
|
658
|
+
|
|
659
|
+
python examples/semantic_search_example.py
|
|
660
|
+
python examples/custom_evaluations_example.py
|
|
661
|
+
```
|
|
662
|
+
|
|
663
|
+
## License
|
|
664
|
+
|
|
665
|
+
MIT
|