From 1166d27e838f7a43f735215c42b0103795444891 Mon Sep 17 00:00:00 2001 From: bjwswang Date: Mon, 21 Aug 2023 18:39:58 +0800 Subject: [PATCH] feat: add prompt controller Signed-off-by: bjwswang --- Makefile | 2 +- README.md | 50 +++++++++ api/v1alpha1/condition.go | 2 + api/v1alpha1/llm.go | 35 ------ api/v1alpha1/llm_types.go | 6 +- api/v1alpha1/prompt_types.go | 7 +- api/v1alpha1/zz_generated.deepcopy.go | 23 ++-- .../crds/arcadia.kubeagi.k8s.com.cn_llms.yaml | 1 - .../arcadia.kubeagi.k8s.com.cn_prompts.yaml | 87 ++++++++++++++- ...cadia.kubeagi.k8s.com.cn_laboratories.yaml | 50 +++++++++ config/arcadia.kubeagi.k8s.com.cn_llms.yaml | 100 ++++++++++++++++++ .../arcadia.kubeagi.k8s.com.cn_prompts.yaml | 84 +++++++++++++++ .../arcadia.kubeagi.k8s.com.cn_llms.yaml | 1 - .../arcadia.kubeagi.k8s.com.cn_prompts.yaml | 47 ++++++++ config/samples/arcadia_v1alpha1_llm.yaml | 14 ++- config/samples/arcadia_v1alpha1_prompt.yaml | 12 ++- controllers/prompt_controller.go | 83 ++++++++++++++- pkg/llms/zhipuai/api.go | 14 +++ pkg/llms/zhipuai/params.go | 4 +- 19 files changed, 550 insertions(+), 72 deletions(-) delete mode 100644 api/v1alpha1/llm.go create mode 100644 config/arcadia.kubeagi.k8s.com.cn_laboratories.yaml create mode 100644 config/arcadia.kubeagi.k8s.com.cn_llms.yaml create mode 100644 config/arcadia.kubeagi.k8s.com.cn_prompts.yaml diff --git a/Makefile b/Makefile index 1e01b2614..e0d32fc74 100644 --- a/Makefile +++ b/Makefile @@ -87,7 +87,7 @@ help: ## Display this help. .PHONY: manifests manifests: controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects. - $(CONTROLLER_GEN) rbac:roleName=manager-role crd webhook paths="./..." output:crd:artifacts:config=config/crd/bases + $(CONTROLLER_GEN) rbac:roleName=manager-role crd:allowDangerousTypes=true webhook paths="./..." output:crd:artifacts:config=config/crd/bases .PHONY: generate generate: controller-gen ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations. diff --git a/README.md b/README.md index 7249ece74..4677157a2 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,56 @@ The idea of `Arcadia` comes from the chat with `ClaudeAI`.See[POE Chat](https:// Our vision is to realize the ideal paradise for all through AI. To achieve a perfect fusion of technology and ideal life.We hope that through our work, we can take steps towards this grander goal of harnessing AI for the good of all. +## Quick start + +### 1. Install arcadia + +```shell +helm repo add arcadia https://kubeagi.github.io/arcadia +helm repo update +helm install arcadia arcadia/arcadia +``` + +### 2. Add a LLM + +> Take [ZhiPuAI] as an example. + +1. Prepare auth info + +Update apiKey in [zhipuai's secret](https://github.com/kubeagi/arcadia/blob/main/config/samples/core_v1alpha1_arcadia_llm.yaml). + +> On how to get apiKey, please refer to [ZhiPuAI](https://open.bigmodel.cn/dev/api#auth) + +2. Create a LLM along with the auth secret + +```shell +kubectl apply -f config/samples/arcadia_v1alpha1_llm.yaml +``` + +### 3. Create a prompt + +```shell +kubectl apply -f config/samples/arcadia_v1alpha1_prompt.yaml +``` + +After prompt got created, you can see the prompt in the following command: + +```shell +kubectl get prompt prompt-zhipuai-sample -oyaml +``` + +If no error found,you can use this command to get the prompt response data. + +```shell +kubectl get prompt prompt-zhipuai-sample --output="jsonpath={.status.data}" | base64 --decode +``` + +Output: + +```shell +{"code":200,"data":{"choices":[{"content":"\" Kubernetes (also known as K8s) is an open-source container orchestration system for automating the deployment, scaling, and management of containerized applications. It was originally designed by Google, and is now maintained by the Cloud Native Computing Foundation (CNCF).\\n\\nKubernetes provides a platform-as-a-service (PaaS) model, which allows developers to deploy, run, and scale containerized applications with minimal configuration and effort. It does this by abstracting the underlying infrastructure and providing a common set of APIs and tools that can be used to deploy, manage, and scale applications consistently across different environments.\\n\\nKubernetes is widely adopted by organizations of all sizes and has a large, active community of developers contributing to its continued development and improvement. It is available on a variety of platforms, including Linux, Windows, and 移动设备,and can be deployed on-premises, in the cloud, or in a hybrid environment.\"","role":"assistant"}],"request_id":"7865480399259975113","task_id":"7865480399259975113","task_status":"SUCCESS","usage":{"total_tokens":203}},"msg":"操作成功","success":true} +``` + ## Why in Kubernetes? Kubernetes provides an ideal platform for building the intelligent systems required to realize our vision of an AI-powered utopia. Some of the key reasons are: diff --git a/api/v1alpha1/condition.go b/api/v1alpha1/condition.go index a2ea5b028..2cbb1d4c7 100644 --- a/api/v1alpha1/condition.go +++ b/api/v1alpha1/condition.go @@ -32,6 +32,8 @@ const ( TypeReady ConditionType = "Ready" // TypeUnknown resources are unknown to the system TypeUnknown ConditionType = "Unknown" + // TypeDone resources are believed to be processed + TypeDone ConditionType = "Done" ) // A ConditionReason represents the reason a resource is in a condition. diff --git a/api/v1alpha1/llm.go b/api/v1alpha1/llm.go deleted file mode 100644 index 69f5f4eaf..000000000 --- a/api/v1alpha1/llm.go +++ /dev/null @@ -1,35 +0,0 @@ -/* -Copyright 2023 KubeAGI. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package v1alpha1 - -import ( - "errors" - - corev1 "k8s.io/api/core/v1" -) - -var ( - ErrMissingAPIKey = errors.New("missing apikey in auth info") -) - -func (o *AuthInfo) FromSecret(secret corev1.Secret) error { - o.APIKey = string(secret.Data["apiKey"]) - if o.APIKey == "" { - return ErrMissingAPIKey - } - return nil -} diff --git a/api/v1alpha1/llm_types.go b/api/v1alpha1/llm_types.go index 9c109e928..28e2c66f7 100644 --- a/api/v1alpha1/llm_types.go +++ b/api/v1alpha1/llm_types.go @@ -27,16 +27,12 @@ type LLMSpec struct { // Type defines the type of llm Type llms.LLMType `json:"type"` // URL keeps the URL of the llm service(Must required) - URL string `json:"url"` + URL string `json:"url,omitempty"` // Auth keeps the authentication credentials when access llm // keeps in k8s secret Auth string `json:"auth,omitempty"` } -type AuthInfo struct { - APIKey string `json:"apiKey,omitempty"` -} - // LLMStatus defines the observed state of LLM type LLMStatus struct { // ConditionedStatus is the current status diff --git a/api/v1alpha1/prompt_types.go b/api/v1alpha1/prompt_types.go index 1a294a277..e8196d874 100644 --- a/api/v1alpha1/prompt_types.go +++ b/api/v1alpha1/prompt_types.go @@ -31,8 +31,11 @@ type PromptSpec struct { // PromptStatus defines the observed state of Prompt type PromptStatus struct { - // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster - // Important: Run "make" to regenerate code after modifying this file + // ConditionedStatus is the current status + ConditionedStatus `json:",inline"` + + // Data retrieved after LLM Call + Data []byte `json:"data"` } //+kubebuilder:object:root=true diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 0e1437b9e..6bd5406b5 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -26,21 +26,6 @@ import ( "k8s.io/apimachinery/pkg/runtime" ) -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *AuthInfo) DeepCopyInto(out *AuthInfo) { - *out = *in -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AuthInfo. -func (in *AuthInfo) DeepCopy() *AuthInfo { - if in == nil { - return nil - } - out := new(AuthInfo) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Condition) DeepCopyInto(out *Condition) { *out = *in @@ -265,7 +250,7 @@ func (in *Prompt) DeepCopyInto(out *Prompt) { out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) in.Spec.DeepCopyInto(&out.Spec) - out.Status = in.Status + in.Status.DeepCopyInto(&out.Status) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Prompt. @@ -341,6 +326,12 @@ func (in *PromptSpec) DeepCopy() *PromptSpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *PromptStatus) DeepCopyInto(out *PromptStatus) { *out = *in + in.ConditionedStatus.DeepCopyInto(&out.ConditionedStatus) + if in.Data != nil { + in, out := &in.Data, &out.Data + *out = make([]byte, len(*in)) + copy(*out, *in) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PromptStatus. diff --git a/charts/arcadia/crds/arcadia.kubeagi.k8s.com.cn_llms.yaml b/charts/arcadia/crds/arcadia.kubeagi.k8s.com.cn_llms.yaml index e3477d1ac..71be044a1 100644 --- a/charts/arcadia/crds/arcadia.kubeagi.k8s.com.cn_llms.yaml +++ b/charts/arcadia/crds/arcadia.kubeagi.k8s.com.cn_llms.yaml @@ -49,7 +49,6 @@ spec: type: string required: - type - - url type: object status: description: LLMStatus defines the observed state of LLM diff --git a/charts/arcadia/crds/arcadia.kubeagi.k8s.com.cn_prompts.yaml b/charts/arcadia/crds/arcadia.kubeagi.k8s.com.cn_prompts.yaml index 103ffa99f..262ed626b 100644 --- a/charts/arcadia/crds/arcadia.kubeagi.k8s.com.cn_prompts.yaml +++ b/charts/arcadia/crds/arcadia.kubeagi.k8s.com.cn_prompts.yaml @@ -35,13 +35,94 @@ spec: spec: description: PromptSpec defines the desired state of Prompt properties: - foo: - description: Foo is an example field of Prompt. Edit prompt_types.go - to remove/update + llm: + description: LLM serivice name(CRD LLM) type: string + zhiPuAIParams: + description: ZhiPuAIParams defines the params of ZhiPuAI + properties: + incremental: + description: Incremental is only Used for SSE Invoke + type: boolean + method: + description: Method used for this prompt call + type: string + model: + description: Model used for this prompt call + type: string + prompt: + description: Contents + items: + description: Prompt defines the content of ZhiPuAI Prompt Call + properties: + content: + type: string + role: + type: string + type: object + type: array + task_id: + description: TaskID is used for getting result of AsyncInvoke + type: string + temperature: + description: Temperature is float in zhipuai + type: number + top_p: + description: TopP is float in zhipuai + type: number + required: + - prompt + type: object + required: + - llm type: object status: description: PromptStatus defines the observed state of Prompt + properties: + conditions: + description: Conditions of the resource. + items: + description: A Condition that may apply to a resource. + properties: + lastSuccessfulTime: + description: LastSuccessfulTime is repository Last Successful + Update Time + format: date-time + type: string + lastTransitionTime: + description: LastTransitionTime is the last time this condition + transitioned from one status to another. + format: date-time + type: string + message: + description: A Message containing details about this condition's + last transition from one status to another, if any. + type: string + reason: + description: A Reason for this condition's last transition from + one status to another. + type: string + status: + description: Status of this condition; is it currently True, + False, or Unknown + type: string + type: + description: Type of this condition. At most one of each condition + type may apply to a resource at any point in time. + type: string + required: + - lastTransitionTime + - reason + - status + - type + type: object + type: array + data: + description: Data retrieved after LLM Call + format: byte + type: string + required: + - data type: object type: object served: true diff --git a/config/arcadia.kubeagi.k8s.com.cn_laboratories.yaml b/config/arcadia.kubeagi.k8s.com.cn_laboratories.yaml new file mode 100644 index 000000000..c45a1f152 --- /dev/null +++ b/config/arcadia.kubeagi.k8s.com.cn_laboratories.yaml @@ -0,0 +1,50 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.9.2 + creationTimestamp: null + name: laboratories.arcadia.kubeagi.k8s.com.cn +spec: + group: arcadia.kubeagi.k8s.com.cn + names: + kind: Laboratory + listKind: LaboratoryList + plural: laboratories + singular: laboratory + scope: Cluster + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: Laboratory is the Schema for the laboratories API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: LaboratorySpec defines the desired state of Laboratory + properties: + foo: + description: Foo is an example field of Laboratory. Edit laboratory_types.go + to remove/update + type: string + type: object + status: + description: LaboratoryStatus defines the observed state of Laboratory + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/arcadia.kubeagi.k8s.com.cn_llms.yaml b/config/arcadia.kubeagi.k8s.com.cn_llms.yaml new file mode 100644 index 000000000..e3477d1ac --- /dev/null +++ b/config/arcadia.kubeagi.k8s.com.cn_llms.yaml @@ -0,0 +1,100 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.9.2 + creationTimestamp: null + name: llms.arcadia.kubeagi.k8s.com.cn +spec: + group: arcadia.kubeagi.k8s.com.cn + names: + kind: LLM + listKind: LLMList + plural: llms + singular: llm + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: LLM is the Schema for the llms API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: LLMSpec defines the desired state of LLM + properties: + auth: + description: Auth keeps the authentication credentials when access + llm keeps in k8s secret + type: string + displayName: + type: string + type: + description: Type defines the type of llm + type: string + url: + description: URL keeps the URL of the llm service(Must required) + type: string + required: + - type + - url + type: object + status: + description: LLMStatus defines the observed state of LLM + properties: + conditions: + description: Conditions of the resource. + items: + description: A Condition that may apply to a resource. + properties: + lastSuccessfulTime: + description: LastSuccessfulTime is repository Last Successful + Update Time + format: date-time + type: string + lastTransitionTime: + description: LastTransitionTime is the last time this condition + transitioned from one status to another. + format: date-time + type: string + message: + description: A Message containing details about this condition's + last transition from one status to another, if any. + type: string + reason: + description: A Reason for this condition's last transition from + one status to another. + type: string + status: + description: Status of this condition; is it currently True, + False, or Unknown + type: string + type: + description: Type of this condition. At most one of each condition + type may apply to a resource at any point in time. + type: string + required: + - lastTransitionTime + - reason + - status + - type + type: object + type: array + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/arcadia.kubeagi.k8s.com.cn_prompts.yaml b/config/arcadia.kubeagi.k8s.com.cn_prompts.yaml new file mode 100644 index 000000000..f3369cb8c --- /dev/null +++ b/config/arcadia.kubeagi.k8s.com.cn_prompts.yaml @@ -0,0 +1,84 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.9.2 + creationTimestamp: null + name: prompts.arcadia.kubeagi.k8s.com.cn +spec: + group: arcadia.kubeagi.k8s.com.cn + names: + kind: Prompt + listKind: PromptList + plural: prompts + singular: prompt + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: Prompt is the Schema for the prompts API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: PromptSpec defines the desired state of Prompt + properties: + llm: + description: LLM serivice name(CRD LLM) + type: string + zhiPuAIParams: + description: ZhiPuAIParams defines the params of ZhiPuAI + properties: + incremental: + description: Incremental is only Used for SSE Invoke + type: boolean + method: + description: Method used for this prompt call + type: string + model: + description: Model used for this prompt call + type: string + prompt: + description: Contents + items: + description: Prompt defines the content of ZhiPuAI Prompt Call + properties: + content: + type: string + role: + type: string + type: object + type: array + task_id: + description: TaskID is used for getting result of AsyncInvoke + type: string + temperature: + description: Temperature is float in zhipuai + top_p: + description: TopP is float in zhipuai + required: + - prompt + type: object + required: + - llm + type: object + status: + description: PromptStatus defines the observed state of Prompt + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/bases/arcadia.kubeagi.k8s.com.cn_llms.yaml b/config/crd/bases/arcadia.kubeagi.k8s.com.cn_llms.yaml index e3477d1ac..71be044a1 100644 --- a/config/crd/bases/arcadia.kubeagi.k8s.com.cn_llms.yaml +++ b/config/crd/bases/arcadia.kubeagi.k8s.com.cn_llms.yaml @@ -49,7 +49,6 @@ spec: type: string required: - type - - url type: object status: description: LLMStatus defines the observed state of LLM diff --git a/config/crd/bases/arcadia.kubeagi.k8s.com.cn_prompts.yaml b/config/crd/bases/arcadia.kubeagi.k8s.com.cn_prompts.yaml index f3369cb8c..262ed626b 100644 --- a/config/crd/bases/arcadia.kubeagi.k8s.com.cn_prompts.yaml +++ b/config/crd/bases/arcadia.kubeagi.k8s.com.cn_prompts.yaml @@ -66,8 +66,10 @@ spec: type: string temperature: description: Temperature is float in zhipuai + type: number top_p: description: TopP is float in zhipuai + type: number required: - prompt type: object @@ -76,6 +78,51 @@ spec: type: object status: description: PromptStatus defines the observed state of Prompt + properties: + conditions: + description: Conditions of the resource. + items: + description: A Condition that may apply to a resource. + properties: + lastSuccessfulTime: + description: LastSuccessfulTime is repository Last Successful + Update Time + format: date-time + type: string + lastTransitionTime: + description: LastTransitionTime is the last time this condition + transitioned from one status to another. + format: date-time + type: string + message: + description: A Message containing details about this condition's + last transition from one status to another, if any. + type: string + reason: + description: A Reason for this condition's last transition from + one status to another. + type: string + status: + description: Status of this condition; is it currently True, + False, or Unknown + type: string + type: + description: Type of this condition. At most one of each condition + type may apply to a resource at any point in time. + type: string + required: + - lastTransitionTime + - reason + - status + - type + type: object + type: array + data: + description: Data retrieved after LLM Call + format: byte + type: string + required: + - data type: object type: object served: true diff --git a/config/samples/arcadia_v1alpha1_llm.yaml b/config/samples/arcadia_v1alpha1_llm.yaml index 06650acf0..b8ec0863d 100644 --- a/config/samples/arcadia_v1alpha1_llm.yaml +++ b/config/samples/arcadia_v1alpha1_llm.yaml @@ -1,6 +1,16 @@ +apiVersion: v1 +kind: Secret +metadata: + name: zhipuai +type: Opaque +data: + apiKey: "MjZiMmJjNTVmYWU0MDc1MjA1NWNhZGZjNDc5MmY5ZGUud2FnQTROSXdnNWFaSldobQ==" # replace this with your API key +--- apiVersion: arcadia.kubeagi.k8s.com.cn/v1alpha1 kind: LLM metadata: - name: llm-sample + name: zhipuai spec: - # TODO(user): Add fields here + type: "zhipuai" + url: "https://open.bigmodel.cn/api/paas/v3/model-api" # replace this with your LLM URL(Zhipuai use predefined url https://open.bigmodel.cn/api/paas/v3/model-api) + auth: "zhipuai" # replace this with your auth secret diff --git a/config/samples/arcadia_v1alpha1_prompt.yaml b/config/samples/arcadia_v1alpha1_prompt.yaml index 49cd012ad..eb3acccb9 100644 --- a/config/samples/arcadia_v1alpha1_prompt.yaml +++ b/config/samples/arcadia_v1alpha1_prompt.yaml @@ -1,6 +1,14 @@ apiVersion: arcadia.kubeagi.k8s.com.cn/v1alpha1 kind: Prompt metadata: - name: prompt-sample + name: prompt-zhipuai-sample spec: - # TODO(user): Add fields here + llm: "zhipuai" + zhiPuAIParams: + model: "chatglm_lite" + method: "invoke" + temperature: 0.75 + top_p: 0.5 + prompt: + - role: "user" + content: "What is kubernetes?" diff --git a/controllers/prompt_controller.go b/controllers/prompt_controller.go index 17e92a79a..034749ec1 100644 --- a/controllers/prompt_controller.go +++ b/controllers/prompt_controller.go @@ -18,13 +18,23 @@ package controllers import ( "context" + "encoding/json" + "fmt" + "github.com/go-logr/logr" + arcadiav1alpha1 "github.com/kubeagi/arcadia/api/v1alpha1" + "github.com/kubeagi/arcadia/pkg/llms" + llmszhipuai "github.com/kubeagi/arcadia/pkg/llms/zhipuai" + corev1 "k8s.io/api/core/v1" + v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/log" - - arcadiav1alpha1 "github.com/kubeagi/arcadia/api/v1alpha1" + "sigs.k8s.io/controller-runtime/pkg/reconcile" ) // PromptReconciler reconciles a Prompt object @@ -47,11 +57,78 @@ type PromptReconciler struct { // For more details, check Reconcile and its Result here: // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.12.2/pkg/reconcile func (r *PromptReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - _ = log.FromContext(ctx) + logger := log.FromContext(ctx) + logger.Info("Starting prompt reconcile") + + // Prompt engineering + prompt := &arcadiav1alpha1.Prompt{} + if err := r.Get(ctx, req.NamespacedName, prompt); err != nil { + if errors.IsNotFound(err) { + // Prompt has been deleted. + return reconcile.Result{}, nil + } + return reconcile.Result{}, err + } + + err := r.CallLLM(ctx, logger, prompt) + if err != nil { + logger.Error(err, "Failed to call LLM") + // Update conditioned status + return reconcile.Result{}, err + } return ctrl.Result{}, nil } +func (r *PromptReconciler) CallLLM(ctx context.Context, logger logr.Logger, prompt *arcadiav1alpha1.Prompt) error { + llm := &arcadiav1alpha1.LLM{} + if err := r.Get(ctx, types.NamespacedName{Name: prompt.Spec.LLM, Namespace: prompt.Namespace}, llm); err != nil { + return err + } + + var apiKey string + if llm.Spec.Auth != "" { + authSecret := corev1.Secret{} + if err := r.Get(ctx, types.NamespacedName{Name: llm.Spec.Auth, Namespace: prompt.Namespace}, &authSecret); err != nil { + return err + } + apiKey = string(authSecret.Data["apiKey"]) + } + + // llm call + var resp = make(map[string]interface{}) + var err error + switch llm.Spec.Type { + case llms.ZhiPuAI: + resp, err = llmszhipuai.NewZhiPuAI(apiKey).Call(*prompt.Spec.ZhiPuAIParams) + case llms.OpenAI: + err = fmt.Errorf("OpenAI not supported yet") + default: + err = fmt.Errorf("unknown LLM type: %s", llm.Spec.Type) + } + + promptDeepCodpy := prompt.DeepCopy() + + newCond := arcadiav1alpha1.Condition{ + Type: arcadiav1alpha1.TypeDone, + Status: v1.ConditionTrue, + LastTransitionTime: metav1.Now(), + Reason: arcadiav1alpha1.ReasonReconcileSuccess, + Message: "Finished CallLLM", + } + if err != nil { + newCond.Status = corev1.ConditionFalse + newCond.Reason = arcadiav1alpha1.ReasonReconcileError + newCond.Message = err.Error() + } + promptDeepCodpy.Status.ConditionedStatus = arcadiav1alpha1.ConditionedStatus{Conditions: []arcadiav1alpha1.Condition{newCond}} + if resp != nil { + promptDeepCodpy.Status.Data, _ = json.Marshal(resp) + } + + return r.Status().Update(ctx, promptDeepCodpy) +} + // SetupWithManager sets up the controller with the Manager. func (r *PromptReconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). diff --git a/pkg/llms/zhipuai/api.go b/pkg/llms/zhipuai/api.go index cd140071b..4c61ab2b9 100644 --- a/pkg/llms/zhipuai/api.go +++ b/pkg/llms/zhipuai/api.go @@ -62,6 +62,20 @@ func NewZhiPuAI(apiKey string) *ZhiPuAI { } } +// Call wraps a common AI api call +func (z *ZhiPuAI) Call(params ModelParams) (map[string]interface{}, error) { + switch params.Method { + case ZhiPuAIInvoke: + return z.Invoke(params) + case ZhiPuAIAsyncInvoke: + return z.AsyncInvoke(params) + case ZhiPuAIAsyncGet: + return z.Get(params) + default: + return nil, errors.New("unknown method") + } +} + // Invoke calls zhipuai and returns result immediately func (z *ZhiPuAI) Invoke(params ModelParams) (map[string]interface{}, error) { url := BuildAPIURL(params.Model, ZhiPuAIInvoke) diff --git a/pkg/llms/zhipuai/params.go b/pkg/llms/zhipuai/params.go index 9f8524e02..84bab5e12 100644 --- a/pkg/llms/zhipuai/params.go +++ b/pkg/llms/zhipuai/params.go @@ -18,7 +18,9 @@ limitations under the License. package zhipuai -import "errors" +import ( + "errors" +) type Role string