鍵.png)
GraphRAG:基于PolarDB+通義千問api+LangChain的知識圖譜定制實(shí)踐
首先我們先看下如果想基于框架實(shí)現(xiàn)一個(gè)自定義的資源,應(yīng)該如何開發(fā),在 網(wǎng)關(guān)篇 提到 使用 AA 模式部署的 metrics-server,對外提供了 nodeMetrics 資源對象,用戶可以直接使用 kubectl 去管理,下面的幾個(gè)關(guān)鍵接口(Interface)你需要仔細(xì)看一下,之前也說到過 AA 模式提供的資源描述和內(nèi)建的資源API定義模式一樣,那我們索性就直接看內(nèi)置資源的實(shí)現(xiàn)。
K8S的內(nèi)置資源對象對外提供 REST 風(fēng)格的交互接口,通過 REST API對外提供服務(wù)都需要實(shí)現(xiàn) rest.Storage 接口。
// Single item interfaces:
// (Method: Current -> Proposed)
// GET: Getter -> NamedGetter
// WATCH: (n/a) -> NamedWatcher
// CREATE: (n/a) -> NamedCreater
// DELETE: Deleter -> NamedDeleter
// UPDATE: Update -> NamedUpdater
// Storage is a generic interface for RESTful storage services.
// Resources which are exported to the RESTful API of apiserver need to implement this interface. It is expected
// that objects may implement any of the below interfaces.
type Storage interface {
// New returns an empty object that can be used with Create and Update after request data has been put into it.
// This object must be a pointer type for use with Codec.DecodeInto([]byte, runtime.Object)
New() runtime.Object
// Destroy cleans up its resources on shutdown.
// Destroy has to be implemented in thread-safe way and be prepared
// for being called more than once.
Destroy()
}
在 Github Dir: kubernetes/pkg/registry[3] 中包括所有內(nèi)置資源的 storage 實(shí)現(xiàn),根據(jù) group 做了區(qū)分,你可以點(diǎn)開自己感興趣的 group 查看具體資源的實(shí)現(xiàn)。
要想要實(shí)現(xiàn)通過 kubectl 去管理自定義資源(get、list、create、patch、update、watch ect),其實(shí)也是對應(yīng)著 REST API中的一些 Verb 行為 還需要按需實(shí)現(xiàn) Github Interface: StandardStorage[4] 接口
// StandardStorage is an interface covering the common verbs. Provided for testing whether a
// resource satisfies the normal storage methods. Use Storage when passing opaque storage objects.
type StandardStorage interface {
Getter
Lister
CreaterUpdater
GracefulDeleter
CollectionDeleter
Watcher
}
每個(gè)接口都對應(yīng)著不同的 REST HTTP Verb,比如:
你可能想了解,我實(shí)現(xiàn)了這些接口之后,k8s.io/apiserver 是如何幫我實(shí)現(xiàn) HTTP Verb 和 接口方法的映射,那么實(shí)現(xiàn)細(xì)節(jié)請看 Github Method: registerResourceHandlers[5]
大致思路是通過斷言接口類型,來為不同的 HTTP Verb 注冊對應(yīng)的 Handler
// what verbs are supported by the storage, used to know what verbs we support per path
creater, isCreater := storage.(rest.Creater)
namedCreater, isNamedCreater := storage.(rest.NamedCreater)
lister, isLister := storage.(rest.Lister)
getter, isGetter := storage.(rest.Getter)
getterWithOptions, isGetterWithOptions := storage.(rest.GetterWithOptions)
gracefulDeleter, isGracefulDeleter := storage.(rest.GracefulDeleter)
collectionDeleter, isCollectionDeleter := storage.(rest.CollectionDeleter)
updater, isUpdater := storage.(rest.Updater)
patcher, isPatcher := storage.(rest.Patcher)
watcher, isWatcher := storage.(rest.Watcher)
connecter, isConnecter := storage.(rest.Connecter)
storageMeta, isMetadata := storage.(rest.StorageMetadata)
storageVersionProvider, isStorageVersionProvider := storage.(rest.StorageVersionProvider)
不過一般內(nèi)置資源通過內(nèi)嵌 Github Object: genericregiser.Store[6] 對象就可以自動(dòng)實(shí)現(xiàn)所有的接口,對象 genericregiser.Store 主要是實(shí)現(xiàn)了資源存儲(chǔ)到 ETCD。
其中Github File: kubernetes/pkg/registry/core/rest/storage_core.go[7] 注冊所有 core 組資源,我們以 core 下的 Github File: Pod[8] 對象為例.
// NewStorage returns a RESTStorage object that will work against pods.
func NewStorage(optsGetter generic.RESTOptionsGetter, k client.ConnectionInfoGetter, proxyTransport http.RoundTripper, podDisruptionBudgetClient policyclient.PodDisruptionBudgetsGetter) (PodStorage, error) {
store := &genericregistry.Store{
NewFunc: func() runtime.Object { return &api.Pod{} },
NewListFunc: func() runtime.Object { return &api.PodList{} },
PredicateFunc: registrypod.MatchPod,
DefaultQualifiedResource: api.Resource("pods"),
CreateStrategy: registrypod.Strategy,
UpdateStrategy: registrypod.Strategy,
DeleteStrategy: registrypod.Strategy,
ReturnDeletedObject: true,
TableConvertor: printerstorage.TableConvertor{TableGenerator: printers.NewTableGenerator().With(printersinternal.AddHandlers)},
}
...
return PodStorage{
Pod: &REST{store, proxyTransport},
Binding: &BindingREST{store: store},
LegacyBinding: &LegacyBindingREST{bindingREST},
Eviction: newEvictionStorage(store, podDisruptionBudgetClient),
Status: &StatusREST{store: &statusStore},
EphemeralContainers: &EphemeralContainersREST{store: &ephemeralContainersStore},
Log: &podrest.LogREST{Store: store, KubeletConn: k},
Proxy: &podrest.ProxyREST{Store: store, ProxyTransport: proxyTransport},
Exec: &podrest.ExecREST{Store: store, KubeletConn: k},
Attach: &podrest.AttachREST{Store: store, KubeletConn: k},
PortForward: &podrest.PortForwardREST{Store: store, KubeletConn: k},
}, nil
}
在 Pod Storage 描述中還有一些我們常見的資源的存儲(chǔ)實(shí)現(xiàn),以 Github File: Binding[9] 為例,當(dāng)我們創(chuàng)建 Pod 的Binding子資源的時(shí)候,其實(shí)是給Pod綁定一個(gè)Node,最終效果看起來是給 Pod 的NodeName字段設(shè)定一個(gè)值。同樣的,你可以參考下 Pod 的 Github File Exec[10] 子資源的實(shí)現(xiàn),實(shí)現(xiàn)了 Connecter 接口,當(dāng)請求 Exec 子資源的時(shí)候,請求會(huì)被代理到 Pod 所在 Node的 kubelet 啟動(dòng)的服務(wù)上,這種實(shí)現(xiàn)方式和我們上一篇問題提到的 Proxy 子資源實(shí)現(xiàn)方式一樣,細(xì)節(jié)還需各位自己看代碼研究。
// Create ensures a pod is bound to a specific host.
func (r *BindingREST) Create(ctx context.Context, name string, obj runtime.Object, createValidation rest.ValidateObjectFunc, options *metav1.CreateOptions) (out runtime.Object, err error) {
binding, ok := obj.(*api.Binding)
if !ok {
return nil, errors.NewBadRequest(fmt.Sprintf("not a Binding object: %#v", obj))
}
if name != binding.Name {
return nil, errors.NewBadRequest("name in URL does not match name in Binding object")
}
// TODO: move me to a binding strategy
if errs := validation.ValidatePodBinding(binding); len(errs) != 0 {
return nil, errs.ToAggregate()
}
if createValidation != nil {
if err := createValidation(ctx, binding.DeepCopyObject()); err != nil {
return nil, err
}
}
err = r.assignPod(ctx, binding.UID, binding.ResourceVersion, binding.Name, binding.Target.Name, binding.Annotations, dryrun.IsDryRun(options.DryRun))
out = &metav1.Status{Status: metav1.StatusSuccess}
return
}
在講解認(rèn)證/授權(quán)前,我們先看上面的這張圖,這張圖的右側(cè)的 Hanlder Chain 里我們可以看到每個(gè) Handler 都實(shí)現(xiàn)了我們熟悉的功能,認(rèn)證授權(quán)限流審計(jì)等功能。配合著圖閱讀 Github Func: DefaultBuildHandlerChain[11] 函數(shù)的實(shí)現(xiàn),你會(huì)微微一笑(原來就是這么個(gè)回事)
func DefaultBuildHandlerChain(apiHandler http.Handler, c *Config) http.Handler {
handler := apiHandler
handler = filterlatency.TrackCompleted(handler)
handler = genericapifilters.WithAuthorization(handler, c.Authorization.Authorizer, c.Serializer)
handler = filterlatency.TrackStarted(handler, c.TracerProvider, "authorization")
// APF 服務(wù)端限流
if c.FlowControl != nil {
workEstimatorCfg := flowcontrolrequest.DefaultWorkEstimatorConfig()
requestWorkEstimator := flowcontrolrequest.NewWorkEstimator(
c.StorageObjectCountTracker.Get, c.FlowControl.GetInterestedWatchCount, workEstimatorCfg, c.FlowControl.GetMaxSeats)
handler = filterlatency.TrackCompleted(handler)
handler = genericfilters.WithPriorityAndFairness(handler, c.LongRunningFunc, c.FlowControl, requestWorkEstimator, c.RequestTimeout/4)
handler = filterlatency.TrackStarted(handler, c.TracerProvider, "priorityandfairness")
} else {
handler = genericfilters.WithMaxInFlightLimit(handler, c.MaxRequestsInFlight, c.MaxMutatingRequestsInFlight, c.LongRunningFunc)
}
handler = filterlatency.TrackCompleted(handler)
// 角色扮演
handler = genericapifilters.WithImpersonation(handler, c.Authorization.Authorizer, c.Serializer)
handler = filterlatency.TrackStarted(handler, c.TracerProvider, "impersonation")
handler = filterlatency.TrackCompleted(handler)
// 審計(jì)日志
handler = genericapifilters.WithAudit(handler, c.AuditBackend, c.AuditPolicyRuleEvaluator, c.LongRunningFunc)
handler = filterlatency.TrackStarted(handler, c.TracerProvider, "audit")
// authn/authz
failedHandler := genericapifilters.Unauthorized(c.Serializer)
failedHandler = genericapifilters.WithFailedAuthenticationAudit(failedHandler, c.AuditBackend, c.AuditPolicyRuleEvaluator)
...
}
這個(gè)函數(shù)實(shí)現(xiàn)了 HTTP Middleware 的能力,用戶請求需要經(jīng)過層層Handler的執(zhí)行,鑒權(quán)、授權(quán)的操作就放在這里實(shí)現(xiàn),不僅僅是鑒權(quán)授權(quán),審計(jì)、準(zhǔn)入控制、流量管理都是通過HTTP Middleware嵌套Handler來完成。
如果想自定義認(rèn)證能力,比如接入公司內(nèi)部的認(rèn)證系統(tǒng),ok沒關(guān)系完全可以
K8S 自己有多種認(rèn)證器 BasicAuth、ClientCA、TokenAuth、ServiceAccountAuth等, Github Interface: authenticator.Request[12]中 定義了認(rèn)證器的接口,
// Request attempts to extract authentication information from a request and
// returns a Response or an error if the request could not be checked.
type Request interface {
AuthenticateRequest(req *http.Request) (*Response, bool, error)
}
在初始化 RecommendedOptions.Authentication[13] 的時(shí)候會(huì)把這些認(rèn)證器注冊進(jìn)去,只有有一個(gè)認(rèn)證器認(rèn)證通過就通過,所以你可以在這里把你自定義認(rèn)證器注冊進(jìn)去,只要實(shí)現(xiàn) Request 接口即可。
其實(shí)我們最常見的是 x509 的認(rèn)證器(就是對你的kubeconfig中的客戶端證書的認(rèn)證) Github Type: x509.Authenticator[14]。
對于一些自定義資源的 CURD 操作可能需要連接公司內(nèi)部的授權(quán)系統(tǒng)來判斷用戶是否有權(quán)限進(jìn)行操作,你完全可以自定義授權(quán)器來接入公司管控
K8S 有多種授權(quán)器 AlwaysAllow、ABAC、Webhook、Node、RBAC等,Github Interface: authorizer.Authorizer[15] 和 Github Interface: authorizer.RuleResolver[16] 兩個(gè)接口定義了授權(quán)器,其中:
Github Interface: authorizer.Authorizer[17] 用于從請求中獲取授權(quán)信息,如果這一步?jīng)]成功那么決策為失敗
// Authorizer makes an authorization decision based on information gained by making
// zero or more calls to methods of the Attributes interface. It returns nil when an action is
// authorized, otherwise it returns an error.
type Authorizer interface {
Authorize(ctx context.Context, a Attributes) (authorized Decision, reason string, err error)
}
Github Interface: authorizer.RuleResolver[18] 用于解析規(guī)則,看是否可以對資源進(jìn)行操作
// RuleResolver provides a mechanism for resolving the list of rules that apply to a given user within a namespace.
type RuleResolver interface {
// RulesFor get the list of cluster wide rules, the list of rules in the specific namespace, incomplete status and errors.
RulesFor(user user.Info, namespace string) ([]ResourceRuleInfo, []NonResourceRuleInfo, bool, error)
}
在初始化 RecommendedOptions.Authorization[19] 的時(shí)候把自定義授權(quán)器注冊進(jìn)來,如果對RBAC機(jī)制感興趣你可以查看 Github Type: rbac.RBACAuthorizer[20] 的實(shí)現(xiàn),不需要太多代碼就給你的APIServer 支持RBAC的能力
其實(shí)準(zhǔn)入控制可以對應(yīng)為請求體的參數(shù)校驗(yàn),以及校驗(yàn)一些不合規(guī)的參數(shù),或者給請求體中填充一些字段,?? 甚至你可以接入公司的變更系統(tǒng),在封網(wǎng)期間禁止對資源修改,審批通過后才允許執(zhí)行操作
實(shí)現(xiàn) Github Snippet: 變更準(zhǔn)入控制和驗(yàn)證準(zhǔn)入控制接口[21] 接口就可以完成自定義準(zhǔn)入校驗(yàn)
type Interface interface {
// Handles returns true if this admission controller can handle the given operation
// where operation can be one of CREATE, UPDATE, DELETE, or CONNECT
Handles(operation Operation) bool
}
type MutationInterface interface {
Interface
// Admit makes an admission decision based on the request attributes.
// Context is used only for timeout/deadline/cancellation and tracing information.
Admit(ctx context.Context, a Attributes, o ObjectInterfaces) (err error)
}
// ValidationInterface is an abstract, pluggable interface for Admission Control decisions.
type ValidationInterface interface {
Interface
// Validate makes an admission decision based on the request attributes. It is NOT allowed to mutate
// Context is used only for timeout/deadline/cancellation and tracing information.
Validate(ctx context.Context, a Attributes, o ObjectInterfaces) (err error)
}
kube-apiserver 中所有已啟用的準(zhǔn)入控制器由 Github Variable: chainAdmissionHandler[22] 數(shù)據(jù)結(jié)構(gòu)管理,當(dāng)客戶端發(fā)送請求給 kube-apiserver 時(shí),Handler 會(huì)遍歷 Github Variable: chainAdmissionHandler[23] 中啟用的準(zhǔn)入控制器并執(zhí)行變更和驗(yàn)證操作,我們想要自定義準(zhǔn)入控制就需要把自己的準(zhǔn)入控制注冊進(jìn)來。
Github Dir: kubernetes/plugin/pkg/admission[24] 目錄下為 K8S 內(nèi)置的準(zhǔn)入控制器, kube-apiserver 在啟動(dòng)時(shí)候會(huì)調(diào)用 Github CodeLine: kubeoptions.NewAdmissionOptions()[25] 把內(nèi)置的準(zhǔn)入控制器列表傳到 ServerRunOptions上。
數(shù)據(jù)的持久化也可以自定義,apiserver 默認(rèn)是使用 Etcd 來作為后端存儲(chǔ),不過沒關(guān)系!,后端存儲(chǔ)也可以自定義,我司就使用 MYSQL 作為后端存儲(chǔ)來做必要的數(shù)據(jù)備份
Github Type: Store[26] 用于etcd的存儲(chǔ),一般k8s資源都嵌入了這個(gè)結(jié)構(gòu)體,他實(shí)現(xiàn)了下面列出的這些接口,這樣k8s資源把 Store 作為內(nèi)嵌資源,就自然可以實(shí)現(xiàn)下面這些接口,也就天然的可以支持一些 REST 的操作。
// Note: the rest.StandardStorage interface aggregates the common REST verbs
var _ rest.StandardStorage = &Store{}
var _ rest.Exporter = &Store{}
var _ rest.TableConvertor = &Store{}
var _ GenericStore = &Store{}
但是我們完全可以自定義一個(gè)自己的存儲(chǔ),這里給大家一個(gè)參考 Github Repo: mink[27] 來實(shí)現(xiàn)一個(gè)對接MySQL的存儲(chǔ)對象
審計(jì)是一個(gè) APIServer 不可或缺需要用來扯皮的功能
k8s.io/apiserver 框架已經(jīng)自帶了審計(jì)能力 Github Func: WithAudit[28]
你完全可以參考 https://kubernetes.io/zh-cn/docs/tasks/debug/debug-cluster/audit/ 文檔里的 Policy 策略完成審計(jì)日志的配置
apiVersion: audit.k8s.io/v1 # 這是必填項(xiàng)。
kind: Policy
# 不要在 RequestReceived 階段為任何請求生成審計(jì)事件。
omitStages:
- "RequestReceived"
rules:
# 在日志中用 RequestResponse 級別記錄 Pod 變化。
- level: RequestResponse
resources:
# 可以替換為你自定義資源的資源組和資源名稱
- group: ""
resources: ["pods"]
只需要在服務(wù)的啟動(dòng)參數(shù)里指定下面的參數(shù)就可以擁有和K8S一樣的審計(jì)能力,而你我的朋友,這是你用 k8s.io/apiserver 構(gòu)建你服務(wù)所應(yīng)得的。
- --audit-policy-file=/etc/kubernetes/audit-policy.yaml
- --audit-log-path=/var/log/kubernetes/audit/audit.log
k8s有非常強(qiáng)的服務(wù)端限流能力APF,不過目前還不能直接使用,因?yàn)樾枰蕾囈粋€(gè)etcd
APF的原理可以參考 https://alexstocks.github.io/html/k8s-apf.html,因?yàn)閗ube-apiserver在啟動(dòng)后的PostHook里會(huì)有專門處理FlowSchema和PriorityLevelConfiguration動(dòng)態(tài)控制流量權(quán)重,如果想直接使用,需要做一些兼容改造。
不過 上一篇文章中提到的 KubeGateway 提供一另一種服務(wù)端限流能力,雖然沒有APF那么能的細(xì)化限流能力,不過對于需要的小伙伴也足夠了,感興趣的同學(xué)可以自行翻閱代碼。
[1]
阿里云云產(chǎn)品 SAE-https://www.aliyun.com/product/sae: https://www.aliyun.com/product/sae[2]
Github Repo: k8s.io/apiserver : https://github.com/kubernetes/apiserver[3]
Github Dir: kubernetes/pkg/registry: https://github.com/kubernetes/kubernetes/tree/8b98305858b107369f2c9b9fd8ef1c5b0da078c0/pkg/registry[4]
Github Interface: StandardStorage: https://github.com/kubernetes/kubernetes/blob/4d33d837c8be778044d50755de83f8738e957c13/staging/src/k8s.io/apiserver/pkg/registry/rest/rest.go#L276[5]
Github Method: registerResourceHandlers: https://github.com/kubernetes/kubernetes/blob/1c2d446648662529282a3bb1528a6dbb50700fdb/staging/src/k8s.io/apiserver/pkg/endpoints/installer.go#L190[6]
Github Object: genericregiser.Store: https://github.com/kubernetes/kubernetes/blob/1c2d446648662529282a3bb1528a6dbb50700fdb/staging/src/k8s.io/apiserver/pkg/registry/generic/registry/store.go#L81[7]
Github File: kubernetes/pkg/registry/core/rest/storage_core.go: https://github.com/kubernetes/kubernetes/blob/d1c296431e0ff2363131707054c4c75ad59cd2c0/pkg/registry/core/rest/storage_core.go#L104[8]
Github File: Pod: https://github.com/kubernetes/kubernetes/blob/d1c296431e0ff2363131707054c4c75ad59cd2c0/pkg/registry/core/rest/storage_core.go#L173[9]
Github File: Binding: https://github.com/kubernetes/kubernetes/blob/d1c296431e0ff2363131707054c4c75ad59cd2c0/pkg/registry/core/pod/storage/storage.go#L159[10]
Github File Exec: https://github.com/kubernetes/kubernetes/blob/2acdbae664bbc5ff9cd5d1ec07f93a14f444cef5/pkg/registry/core/pod/rest/subresources.go#L168[11]
Github Func: DefaultBuildHandlerChain: https://github.com/kubernetes/apiserver/blob/9dc08c72a8d36aad9e4508497417d5c6231610fa/pkg/server/config.go#L978[12]
Github Interface: authenticator.Request: https://github.com/kubernetes/apiserver/blob/9dc08c72a8d36aad9e4508497417d5c6231610fa/pkg/authentication/authenticator/interfaces.go#L34[13]
RecommendedOptions.Authentication: https://github.com/kubernetes/apiserver/blob/9dc08c72a8d36aad9e4508497417d5c6231610fa/pkg/server/options/recommended.go#L67[14]
Github Type: x509.Authenticator: https://github.com/kubernetes/apiserver/blob/9dc08c72a8d36aad9e4508497417d5c6231610fa/pkg/authentication/request/x509/x509.go#L133[15]
Github Interface: authorizer.Authorizer: https://github.com/kubernetes/apiserver/blob/9dc08c72a8d36aad9e4508497417d5c6231610fa/pkg/authorization/authorizer/interfaces.go#L70[16]
Github Interface: authorizer.RuleResolver: https://github.com/kubernetes/apiserver/blob/9dc08c72a8d36aad9e4508497417d5c6231610fa/pkg/authorization/authorizer/interfaces.go#L81[17]
Github Interface: authorizer.Authorizer: https://github.com/kubernetes/apiserver/blob/9dc08c72a8d36aad9e4508497417d5c6231610fa/pkg/authorization/authorizer/interfaces.go#L70[18]
Github Interface: authorizer.RuleResolver: https://github.com/kubernetes/apiserver/blob/9dc08c72a8d36aad9e4508497417d5c6231610fa/pkg/authorization/authorizer/interfaces.go#L81[19]
RecommendedOptions.Authorization: https://github.com/kubernetes/apiserver/blob/9dc08c72a8d36aad9e4508497417d5c6231610fa/pkg/server/options/recommended.go#L68[20]
Github Type: rbac.RBACAuthorizer: https://github.com/kubernetes/kubernetes/blob/4a89df5617b8e1e26abb16150502d04e6c180533/plugin/pkg/auth/authorizer/rbac/rbac.go#L50[21]
Github Snippet: 變更準(zhǔn)入控制和驗(yàn)證準(zhǔn)入控制接口: https://github.com/kubernetes/apiserver/blob/9dc08c72a8d36aad9e4508497417d5c6231610fa/pkg/admission/interfaces.go#L123-L144[22]
Github Variable: chainAdmissionHandler: https://github.com/kubernetes/apiserver/blob/master/pkg/admission/chain.go#L23[23]
Github Variable: chainAdmissionHandler: https://github.com/kubernetes/apiserver/blob/master/pkg/admission/chain.go#L23[24]
Github Dir: kubernetes/plugin/pkg/admission: https://github.com/kubernetes/kubernetes/tree/release-1.20/plugin/pkg/admission[25]
Github CodeLine: kubeoptions.NewAdmissionOptions(): https://github.com/kubernetes/kubernetes/blob/4a89df5617b8e1e26abb16150502d04e6c180533/cmd/kube-apiserver/app/options/options.go#L105[26]
Github Type: Store: https://github.com/kubernetes/apiserver/blob/9dc08c72a8d36aad9e4508497417d5c6231610fa/pkg/registry/generic/registry/store.go#L97[27]
Github Repo: mink: https://github.com/acorn-io/mink[28]
Github Func: WithAudit: https://github.com/kubernetes/apiserver/blob/9dc08c72a8d36aad9e4508497417d5c6231610fa/pkg/endpoints/filters/audit.go#L42
文章轉(zhuǎn)自微信公眾號@CNCF
GraphRAG:基于PolarDB+通義千問api+LangChain的知識圖譜定制實(shí)踐
使用Node.js、Express和MySQL構(gòu)建REST API
天氣API推薦:精準(zhǔn)獲取氣象數(shù)據(jù)的首選
基于自定義數(shù)據(jù)集的微調(diào):Alpaca與LLaMA模型的訓(xùn)練
OAuth和OpenID Connect圖解指南
有哪些新聞媒體提供Open API?
現(xiàn)在做大模型,還有靠譜且免費(fèi)的API接口嗎?
如何運(yùn)用AI提高自己的工作效率?
區(qū)塊鏈API推薦,快速開發(fā)去中心化應(yīng)用