Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ jobs:
make create-kind-cluster
echo "Cache key: ${{ needs.setup.outputs.cache-key }}"
make helm-install
make push-test-agent
make push-test-agent push-test-skill
kubectl wait --for=condition=Ready agents.kagent.dev -n kagent --all --timeout=60s || kubectl get po -n kagent -o wide ||:
kubectl wait --for=condition=Ready agents.kagent.dev -n kagent --all --timeout=60s

Expand Down
2 changes: 1 addition & 1 deletion DEVELOPMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ Then run the `make helm-install` command again.
create a minimal cluster with kind. scale kagent to 0 replicas, as we will run it locally.

```bash
make create-kind-cluster helm-install-provider helm-tools push-test-agent
make create-kind-cluster helm-install-provider helm-tools push-test-agent push-test-skill
kubectl scale -n kagent deployment kagent-controller --replicas 0
```

Expand Down
5 changes: 5 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,11 @@ push-test-agent: buildx-create build-kagent-adk
kubectl apply --namespace kagent --context kind-$(KIND_CLUSTER_NAME) -f go/test/e2e/agents/kebab/agent.yaml
$(DOCKER_BUILDER) build --push $(BUILD_ARGS) $(TOOLS_IMAGE_BUILD_ARGS) -t $(DOCKER_REGISTRY)/poem-flow:latest -f python/samples/crewai/poem_flow/Dockerfile ./python

.PHONY: push-test-skill
push-test-skill: buildx-create
echo "Building FROM DOCKER_REGISTRY=$(DOCKER_REGISTRY)/$(DOCKER_REPO)/kebab-maker:$(VERSION)"
$(DOCKER_BUILDER) build --push $(BUILD_ARGS) $(TOOLS_IMAGE_BUILD_ARGS) -t $(DOCKER_REGISTRY)/kebab-maker:latest -f go/test/e2e/testdata/skills/kebab/Dockerfile ./go/test/e2e/testdata/skills/kebab

.PHONY: create-kind-cluster
create-kind-cluster:
bash ./scripts/kind/setup-kind.sh
Expand Down
26 changes: 23 additions & 3 deletions go/api/v1alpha2/agent_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,23 @@ type AgentSpec struct {

// +optional
Description string `json:"description,omitempty"`

// Skills to load into the agent. They will be pulled from the specified container images.
// and made available to the agent under the `/skills` folder.
// +optional
Skills *SkillForAgent `json:"skills,omitempty"`
}

type SkillForAgent struct {
// Fetch images insecurely from registries (allowing HTTP and skipping TLS verification).
// Meant for development and testing purposes only.
// +optional
InsecureSkipVerify bool `json:"insecureSkipVerify,omitempty"`

// The list of skill images to fetch.
// +kubebuilder:validation:MinItems=1
// +kubebuilder:validation:MaxItems=20
Refs []string `json:"refs,omitempty"`
}

// +kubebuilder:validation:XValidation:rule="!has(self.systemMessage) || !has(self.systemMessageFrom)",message="systemMessage and systemMessageFrom are mutually exclusive"
Expand Down Expand Up @@ -85,6 +102,12 @@ type DeclarativeAgentSpec struct {

// +optional
Deployment *DeclarativeDeploymentSpec `json:"deployment,omitempty"`

// Allow code execution for python code blocks with this agent.
// If true, the agent will automatically execute python code blocks in the LLM responses.
// Code will be executed in a sandboxed environment.
// +optional
ExecuteCodeBlocks *bool `json:"executeCodeBlocks,omitempty"`
}

type DeclarativeDeploymentSpec struct {
Expand Down Expand Up @@ -112,10 +135,7 @@ type ByoDeploymentSpec struct {
}

type SharedDeploymentSpec struct {
// If not specified, the default value is 1.
// +optional
// +kubebuilder:validation:Minimum=1
// +kubebuilder:default=1
Replicas *int32 `json:"replicas,omitempty"`
// +optional
ImagePullSecrets []corev1.LocalObjectReference `json:"imagePullSecrets,omitempty"`
Expand Down
30 changes: 30 additions & 0 deletions go/api/v1alpha2/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

30 changes: 24 additions & 6 deletions go/config/crd/bases/kagent.dev_agents.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2579,10 +2579,7 @@ spec:
type: string
type: object
replicas:
default: 1
description: If not specified, the default value is 1.
format: int32
minimum: 1
type: integer
resources:
description: ResourceRequirements describes the compute resource
Expand Down Expand Up @@ -4858,10 +4855,7 @@ spec:
type: string
type: object
replicas:
default: 1
description: If not specified, the default value is 1.
format: int32
minimum: 1
type: integer
resources:
description: ResourceRequirements describes the compute resource
Expand Down Expand Up @@ -6887,6 +6881,12 @@ spec:
type: object
type: array
type: object
executeCodeBlocks:
description: |-
Allow code execution for python code blocks with this agent.
If true, the agent will automatically execute python code blocks in the LLM responses.
Code will be executed in a sandboxed environment.
type: boolean
modelConfig:
description: |-
The name of the model config to use.
Expand Down Expand Up @@ -7026,6 +7026,24 @@ spec:
rule: '!has(self.systemMessage) || !has(self.systemMessageFrom)'
description:
type: string
skills:
description: |-
Skills to load into the agent. They will be pulled from the specified container images.
and made available to the agent under the `/skills` folder.
properties:
insecureSkipVerify:
description: |-
Fetch images insecurely from registries (allowing HTTP and skipping TLS verification).
Meant for development and testing purposes only.
type: boolean
refs:
description: The list of skill images to fetch.
items:
type: string
maxItems: 20
minItems: 1
type: array
type: object
type:
allOf:
- enum:
Expand Down
2 changes: 2 additions & 0 deletions go/internal/adk/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -239,13 +239,15 @@ type RemoteAgentConfig struct {
Description string `json:"description,omitempty"`
}

// See `python/packages/kagent-adk/src/kagent/adk/types.py` for the python version of this
type AgentConfig struct {
Model Model `json:"model"`
Description string `json:"description"`
Instruction string `json:"instruction"`
HttpTools []HttpMcpServerConfig `json:"http_tools"`
SseTools []SseMcpServerConfig `json:"sse_tools"`
RemoteAgents []RemoteAgentConfig `json:"remote_agents"`
ExecuteCode bool `json:"execute_code,omitempty"`
}

func (a *AgentConfig) UnmarshalJSON(data []byte) error {
Expand Down
64 changes: 55 additions & 9 deletions go/internal/controller/translator/agent/adk_api_translator.go
Original file line number Diff line number Diff line change
Expand Up @@ -330,9 +330,54 @@ func (a *adkApiTranslator) buildManifest(
},
)

var skills []string
if agent.Spec.Skills != nil && len(agent.Spec.Skills.Refs) != 0 {
skills = agent.Spec.Skills.Refs
}

// Build Deployment
volumes := append(secretVol, dep.Volumes...)
volumeMounts := append(secretMounts, dep.VolumeMounts...)
needSandbox := cfg != nil && cfg.ExecuteCode

var initContainers []corev1.Container

if len(skills) > 0 {
skillsEnv := corev1.EnvVar{
Name: "KAGENT_SKILLS_FOLDER",
Value: "/skills",
}
needSandbox = true
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

by setting this to true here, are saying that if you add skills to the agent you'll always also need a sandbox? Are there agents with skills that won't need to execute code?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there are skills that don't have code... but we don't know it ahead of time...

insecure := agent.Spec.Skills.InsecureSkipVerify
command := []string{"kagent-adk", "pull-skills"}
if insecure {
command = append(command, "--insecure")
}
initContainers = append(initContainers, corev1.Container{
Name: "skills-init",
Image: dep.Image,
Command: command,
Args: skills,
VolumeMounts: []corev1.VolumeMount{
{Name: "kagent-skills", MountPath: "/skills"},
},
Env: []corev1.EnvVar{
skillsEnv,
},
})
volumes = append(volumes, corev1.Volume{
Name: "kagent-skills",
VolumeSource: corev1.VolumeSource{
EmptyDir: &corev1.EmptyDirVolumeSource{},
},
})
volumeMounts = append(volumeMounts, corev1.VolumeMount{
Name: "kagent-skills",
MountPath: "/skills",
ReadOnly: true,
})
sharedEnv = append(sharedEnv, skillsEnv)
}

// Token volume
volumes = append(volumes, corev1.Volume{
Expand Down Expand Up @@ -368,6 +413,12 @@ func (a *adkApiTranslator) buildManifest(
}
// Add config hash annotation to pod template to force rollout on config changes
podTemplateAnnotations["kagent.dev/config-hash"] = fmt.Sprintf("%d", configHash)
var securityContext *corev1.SecurityContext
if needSandbox {
securityContext = &corev1.SecurityContext{
Privileged: ptr.To(true),
}
}

deployment := &appsv1.Deployment{
TypeMeta: metav1.TypeMeta{APIVersion: "apps/v1", Kind: "Deployment"},
Expand All @@ -387,6 +438,7 @@ func (a *adkApiTranslator) buildManifest(
Spec: corev1.PodSpec{
ServiceAccountName: agent.Name,
ImagePullSecrets: dep.ImagePullSecrets,
InitContainers: initContainers,
Containers: []corev1.Container{{
Name: "kagent",
Image: dep.Image,
Expand All @@ -404,7 +456,8 @@ func (a *adkApiTranslator) buildManifest(
TimeoutSeconds: 15,
PeriodSeconds: 15,
},
VolumeMounts: volumeMounts,
SecurityContext: securityContext,
VolumeMounts: volumeMounts,
}},
Volumes: volumes,
},
Expand Down Expand Up @@ -460,6 +513,7 @@ func (a *adkApiTranslator) translateInlineAgent(ctx context.Context, agent *v1al
Description: agent.Spec.Description,
Instruction: systemMessage,
Model: model,
ExecuteCode: ptr.Deref(agent.Spec.Declarative.ExecuteCodeBlocks, false),
}
agentCard := &server.AgentCard{
Name: strings.ReplaceAll(agent.Name, "-", "_"),
Expand Down Expand Up @@ -1084,9 +1138,7 @@ func getDefaultLabels(agentName string, incoming map[string]string) map[string]s
func (a *adkApiTranslator) resolveInlineDeployment(agent *v1alpha2.Agent, mdd *modelDeploymentData) (*resolvedDeployment, error) {
// Defaults
port := int32(8080)
cmd := "kagent-adk"
args := []string{
"static",
"--host",
"0.0.0.0",
"--port",
Expand Down Expand Up @@ -1122,7 +1174,6 @@ func (a *adkApiTranslator) resolveInlineDeployment(agent *v1alpha2.Agent, mdd *m

dep := &resolvedDeployment{
Image: image,
Cmd: cmd,
Args: args,
Port: port,
ImagePullPolicy: imagePullPolicy,
Expand All @@ -1136,11 +1187,6 @@ func (a *adkApiTranslator) resolveInlineDeployment(agent *v1alpha2.Agent, mdd *m
Resources: getDefaultResources(spec.Resources), // Set default resources if not specified
}

// Set default replicas if not specified
if dep.Replicas == nil {
dep.Replicas = ptr.To(int32(1))
}

return dep, nil
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
operation: translateAgent
targetObject: agent-with-code
namespace: test
objects:
- apiVersion: v1
kind: Secret
metadata:
name: openai-secret
namespace: test
data:
api-key: c2stdGVzdC1hcGkta2V5 # base64 encoded "sk-test-api-key"
- apiVersion: kagent.dev/v1alpha2
kind: ModelConfig
metadata:
name: basic-model
namespace: test
spec:
provider: OpenAI
model: gpt-4o
apiKeySecret: openai-secret
apiKeySecretKey: api-key
openAI:
temperature: "0.7"
maxTokens: 1024
topP: "0.95"
reasoningEffort: "low"
defaultHeaders:
User-Agent: "kagent/1.0"
- apiVersion: kagent.dev/v1alpha2
kind: Agent
metadata:
name: agent-with-code
namespace: test
spec:
type: Declarative
declarative:
executeCodeBlocks: true
description: A basic test agent
systemMessage: You are a helpful assistant.
modelConfig: basic-model
deployment:
resources:
requests:
cpu: 200m
memory: 684Mi
limits:
cpu: 3000m
memory: 2Gi
tools: []
Loading
Loading