Skip to content

Commit d13e944

Browse files
committed
feat: Add artifact server support with MinIO integration and update documentation
Signed-off-by: Eden Reich <eden.reich@gmail.com>
1 parent 335ccdb commit d13e944

7 files changed

Lines changed: 243 additions & 14 deletions

File tree

.adl-ignore

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,6 @@ skills/validate.go
1717

1818
main.go
1919

20-
# Mock implementations
21-
internal/mock/
22-
2320
# Go dependency files
2421
go.mod
2522

README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,20 @@ Configure the agent via environment variables:
8181
| **Storage** | `A2A_QUEUE_URL` | Redis connection URL (when using Redis) | - |
8282
| **Storage** | `A2A_QUEUE_MAX_SIZE` | Maximum queue size | `100` |
8383
| **Storage** | `A2A_QUEUE_CLEANUP_INTERVAL` | Task cleanup interval | `30s` |
84+
| **Artifacts** | `ARTIFACTS_ENABLE` | Enable artifacts support | `false` |
85+
| **Artifacts** | `ARTIFACTS_SERVER_HOST` | Artifacts server host | `localhost` |
86+
| **Artifacts** | `ARTIFACTS_SERVER_PORT` | Artifacts server port | `8081` |
87+
| **Artifacts** | `ARTIFACTS_STORAGE_PROVIDER` | Storage backend (`filesystem` or `minio`) | `filesystem` |
88+
| **Artifacts** | `ARTIFACTS_STORAGE_BASE_PATH` | Base path for filesystem storage | `./artifacts` |
89+
| **Artifacts** | `ARTIFACTS_STORAGE_BASE_URL` | Override base URL for direct downloads | (auto-generated) |
90+
| **Artifacts** | `ARTIFACTS_STORAGE_ENDPOINT` | MinIO/S3 endpoint URL | - |
91+
| **Artifacts** | `ARTIFACTS_STORAGE_ACCESS_KEY` | MinIO/S3 access key | - |
92+
| **Artifacts** | `ARTIFACTS_STORAGE_SECRET_KEY` | MinIO/S3 secret key | - |
93+
| **Artifacts** | `ARTIFACTS_STORAGE_BUCKET_NAME` | MinIO/S3 bucket name | `artifacts` |
94+
| **Artifacts** | `ARTIFACTS_STORAGE_USE_SSL` | Use SSL for MinIO/S3 connections | `true` |
95+
| **Artifacts** | `ARTIFACTS_RETENTION_MAX_ARTIFACTS` | Max artifacts per task (0 = unlimited) | `5` |
96+
| **Artifacts** | `ARTIFACTS_RETENTION_MAX_AGE` | Max artifact age (0 = no age limit) | `7d` |
97+
| **Artifacts** | `ARTIFACTS_RETENTION_CLEANUP_INTERVAL` | Cleanup frequency (0 = manual only) | `24h` |
8498
| **Authentication** | `A2A_AUTH_ENABLE` | Enable OIDC authentication | `false` |
8599

86100
## Development

agent.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ spec:
1010
streaming: true
1111
pushNotifications: false
1212
stateTransitionHistory: false
13+
artifacts:
14+
enabled: true
1315
agent:
1416
provider: ""
1517
model: ""

example/README.md

Lines changed: 95 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ This example demonstrates how to use the mock-agent for testing and development.
99
- **5 Testing Skills** - Pre-configured skills for various testing scenarios
1010
- **Fast & Reliable** - Instant, predictable responses perfect for testing
1111
- **A2A Protocol Compliant** - Full implementation of the A2A protocol
12+
- **Artifact Server** - MinIO-backed artifact storage for testing file artifacts
1213

1314
## Quick Start
1415

@@ -188,6 +189,63 @@ docker compose run --rm a2a-debugger tasks submit 'Generate 10 test email addres
188189
docker compose run --rm a2a-debugger tasks submit 'Generate 3 JSON objects for testing'
189190
```
190191

192+
### Testing Artifacts
193+
194+
The mock agent includes a fully operational artifact server with MinIO storage backend:
195+
196+
```bash
197+
# Verify artifact server is running
198+
curl http://localhost:8081/health
199+
# Response: {"status":"ok","time":"2025-10-19T17:37:20Z"}
200+
201+
# Verify artifact service is initialized
202+
docker compose logs mock-agent | grep "artifact service initialized"
203+
# Should show: artifact service initialized with MinIO storage
204+
205+
# Submit a task
206+
docker compose run --rm a2a-debugger tasks submit 'Please create an artifact with sample data'
207+
```
208+
209+
**Infrastructure Status:**
210+
- ✅ Artifact server running on port 8081
211+
- ✅ MinIO storage backend connected (minio:9000)
212+
- ✅ Artifact service initialized successfully
213+
- ✅ Download endpoint: `GET /artifacts/:artifactId/:filename`
214+
-`create_artifact` tool available to LLM
215+
216+
**How Artifacts Work:**
217+
1. The ADK automatically provides a `create_artifact` tool to the LLM
218+
2. When the LLM calls this tool, artifacts are stored in MinIO
219+
3. The artifact metadata (ID, download URL) is returned to the client
220+
4. Clients download artifacts from `:8081/artifacts/:artifactId/:filename`
221+
222+
**Testing with Real LLM:**
223+
The mock LLM is designed for testing A2A protocol infrastructure without API costs. To test actual artifact creation:
224+
225+
1. Replace mock LLM with a real provider in `main.go`:
226+
```go
227+
// Instead of:
228+
llmClient := mock.NewMockLLMClient()
229+
230+
// Use:
231+
llmClient, err := server.NewOpenAICompatibleLLMClient(&cfg.A2A.AgentConfig, l)
232+
```
233+
234+
2. Set environment variables:
235+
```bash
236+
A2A_AGENT_CLIENT_PROVIDER=openai
237+
A2A_AGENT_CLIENT_API_KEY=your-key
238+
A2A_AGENT_CLIENT_MODEL=gpt-4
239+
```
240+
241+
3. The LLM will then call `create_artifact` and store files in MinIO
242+
243+
Access MinIO console to view/manage artifacts:
244+
```bash
245+
# Open in browser: http://localhost:9001
246+
# Credentials: minioadmin / minioadmin
247+
```
248+
191249
## Monitoring and Debugging
192250

193251
### View Agent Logs
@@ -219,19 +277,45 @@ docker compose down
219277
## Architecture
220278

221279
```
222-
┌─────────────┐ ┌──────────────┐
223-
│ a2a-debugger│────────>│ mock-agent │
224-
│ (CLI) │ A2A │ :8080 │
225-
└─────────────┘ Protocol└──────────────┘
226-
227-
v
228-
┌──────────────┐
229-
│ Mock LLM │
230-
│ Client │
231-
│ (No API Key) │
232-
└──────────────┘
280+
┌─────────────┐ A2A Protocol ┌──────────────┐
281+
│ a2a-debugger│────────────────>│ mock-agent │
282+
│ (A2A Client)│ │ (A2A Server) │
283+
│ │ │ :8080 │
284+
│ │ └──────┬───────┘
285+
│ │ │
286+
│ │ │ Uses
287+
│ │ v
288+
│ │ ┌──────────────┐
289+
│ │ │ Mock LLM │
290+
│ │ │ Client │
291+
│ │ │ (Generates │
292+
│ │ │ Artifacts) │
293+
│ │ └──────┬───────┘
294+
│ │ │
295+
│ │ │ Stores
296+
│ │ v
297+
│ │ Download ┌──────────────┐
298+
│ │ Artifacts │ MinIO │
299+
│ │<─────────────────│ :9000 │
300+
└─────────────┘ via :8081 └──────────────┘
301+
Artifact Server
233302
```
234303

304+
## Services
305+
306+
This example runs the following services:
307+
308+
1. **mock-agent** (:8080) - The main A2A agent server with Mock LLM client
309+
2. **artifacts-server** (:8081) - HTTP server for clients to download generated artifacts
310+
3. **minio** (:9000, :9001) - Object storage backend where artifacts are stored
311+
4. **a2a-debugger** - CLI tool for interacting with the agent (manual profile)
312+
313+
The flow is:
314+
1. Client sends request to mock-agent via A2A protocol
315+
2. Mock LLM generates artifacts and stores them in MinIO
316+
3. Mock-agent returns artifact metadata to client
317+
4. Client downloads artifacts from artifact server (:8081) which retrieves them from MinIO
318+
235319
## Why Use the Mock Agent?
236320

237321
**No API Costs** - Completely free to run

example/docker-compose.yaml

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,25 @@
11
---
22
services:
3+
minio:
4+
image: minio/minio:latest
5+
container_name: mock-agent-minio
6+
command: server /data --console-address ":9001"
7+
environment:
8+
MINIO_ROOT_USER: minioadmin
9+
MINIO_ROOT_PASSWORD: minioadmin
10+
ports:
11+
- "9000:9000"
12+
- "9001:9001"
13+
networks:
14+
- mock-network
15+
volumes:
16+
- minio-data:/data
17+
healthcheck:
18+
test: ["CMD", "mc", "ready", "local"]
19+
interval: 5s
20+
timeout: 5s
21+
retries: 5
22+
323
mock-agent:
424
build:
525
context: ..
@@ -26,10 +46,28 @@ services:
2646
A2A_QUEUE_MAX_SIZE: 1000
2747
A2A_QUEUE_CLEANUP_INTERVAL: 1h
2848
A2A_AUTH_ENABLE: false
49+
A2A_AGENT_CLIENT_TOOLS_CREATE_ARTIFACT: "true"
50+
A2A_ARTIFACTS_ENABLE: "true"
51+
A2A_ARTIFACTS_SERVER_HOST: "0.0.0.0"
52+
A2A_ARTIFACTS_SERVER_PORT: "8081"
53+
A2A_ARTIFACTS_STORAGE_PROVIDER: minio
54+
A2A_ARTIFACTS_STORAGE_ENDPOINT: minio:9000
55+
A2A_ARTIFACTS_STORAGE_ACCESS_KEY: minioadmin
56+
A2A_ARTIFACTS_STORAGE_SECRET_KEY: minioadmin
57+
A2A_ARTIFACTS_STORAGE_BUCKET_NAME: artifacts
58+
A2A_ARTIFACTS_STORAGE_USE_SSL: "false"
59+
A2A_ARTIFACTS_STORAGE_BASE_URL: http://localhost:9000
60+
A2A_ARTIFACTS_RETENTION_MAX_ARTIFACTS: "10"
61+
A2A_ARTIFACTS_RETENTION_MAX_AGE: 168h
62+
A2A_ARTIFACTS_RETENTION_CLEANUP_INTERVAL: 24h
2963
ports:
3064
- "8080:8080"
65+
- "8081:8081"
3166
networks:
3267
- mock-network
68+
depends_on:
69+
minio:
70+
condition: service_healthy
3371
healthcheck:
3472
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:8080/health"]
3573
interval: 10s
@@ -57,3 +95,7 @@ services:
5795
networks:
5896
mock-network:
5997
driver: bridge
98+
99+
volumes:
100+
minio-data:
101+
driver: local

internal/mock/llm_client.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,41 @@ func generateMockToolCalls(tools []sdk.ChatCompletionTool, userMessage string) [
317317
}
318318
}
319319

320+
if contains(lowerMsg, "artifact") || contains(lowerMsg, "create file") || contains(lowerMsg, "save file") {
321+
for _, tool := range tools {
322+
if tool.Function.Name == "create_artifact" {
323+
name := "sample-data.json"
324+
content := `{"id": 1, "name": "John Doe", "email": "john.doe@example.com"}`
325+
326+
if contains(lowerMsg, "text") || contains(lowerMsg, "txt") {
327+
name = "sample-data.txt"
328+
content = "This is a sample text artifact created by the mock agent."
329+
} else if contains(lowerMsg, "csv") {
330+
name = "sample-data.csv"
331+
content = "id,name,email\n1,John Doe,john.doe@example.com\n2,Jane Smith,jane.smith@example.com"
332+
}
333+
334+
args, _ := json.Marshal(map[string]any{
335+
"name": name,
336+
"content": content,
337+
"type": "url",
338+
"filename": name,
339+
})
340+
341+
return []sdk.ChatCompletionMessageToolCall{
342+
{
343+
Id: "call-" + generateID(),
344+
Type: sdk.Function,
345+
Function: sdk.ChatCompletionMessageToolCallFunction{
346+
Name: "create_artifact",
347+
Arguments: string(args),
348+
},
349+
},
350+
}
351+
}
352+
}
353+
}
354+
320355
if contains(lowerMsg, "random") || contains(lowerMsg, "generate") {
321356
for _, tool := range tools {
322357
if tool.Function.Name == "random_data" {
@@ -359,6 +394,31 @@ func generateMockToolCalls(tools []sdk.ChatCompletionTool, userMessage string) [
359394
}
360395
}
361396

397+
for _, tool := range tools {
398+
if tool.Function.Name == "create_artifact" {
399+
name := "default-file.json"
400+
content := `{"message": "Default artifact content"}`
401+
402+
args, _ := json.Marshal(map[string]any{
403+
"name": name,
404+
"content": content,
405+
"type": "url",
406+
"filename": name,
407+
})
408+
409+
return []sdk.ChatCompletionMessageToolCall{
410+
{
411+
Id: "call-" + generateID(),
412+
Type: sdk.Function,
413+
Function: sdk.ChatCompletionMessageToolCallFunction{
414+
Name: "create_artifact",
415+
Arguments: string(args),
416+
},
417+
},
418+
}
419+
}
420+
}
421+
362422
for _, tool := range tools {
363423
if tool.Function.Name == "echo" {
364424
args, _ := json.Marshal(map[string]any{

main.go

Lines changed: 30 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)