Skip to content
Open
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
64 changes: 64 additions & 0 deletions components/model/claude/README.md
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I wasn't sure if example > readme.
I found the readme helpful on the documentation website when I initially started using eino.
examples/ are better for more complex use cases, and I don't think this is one.

Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,70 @@ type Config struct {



## Structured Output

Use `ResponseFormat` to get JSON responses conforming to a schema:

```go
import (
"github.com/cloudwego/eino-ext/components/model/claude"
"github.com/eino-contrib/jsonschema"
)

type ContactInfo struct {
Name string `json:"name" jsonschema:"description=Full name"`
Email string `json:"email" jsonschema:"description=Email address"`
PlanInterest string `json:"plan_interest" jsonschema:"description=Plan type"`
}

r := jsonschema.Reflector{AllowAdditionalProperties: false, DoNotReference: true}
s := r.Reflect(&ContactInfo{})

cm, err := claude.NewChatModel(ctx, &claude.Config{
APIKey: "your-api-key",
Model: "claude-sonnet-4-6-20250514",
MaxTokens: 1024,
ResponseFormat: &claude.ResponseFormat{
Schema: s,
},
})
```

You can also use Eino's `utils.GoStruct2ParamsOneOf` to derive the schema from a Go struct:

```go
import (
"github.com/cloudwego/eino-ext/components/model/claude"
"github.com/cloudwego/eino/components/tool/utils"
)

type ContactInfo struct {
Name string `json:"name" jsonschema:"description=Full name"`
Email string `json:"email" jsonschema:"description=Email address"`
PlanInterest string `json:"plan_interest" jsonschema:"description=Plan type"`
}

params, _ := utils.GoStruct2ParamsOneOf[ContactInfo]()
s, _ := params.ToJSONSchema()

cm, err := claude.NewChatModel(ctx, &claude.Config{
APIKey: "your-api-key",
Model: "claude-sonnet-4-6-20250514",
MaxTokens: 1024,
ResponseFormat: &claude.ResponseFormat{
Schema: s,
},
})
```

The response format can also be set per-request using `WithResponseFormat`:

```go
resp, err := cm.Generate(ctx, messages, claude.WithResponseFormat(&claude.ResponseFormat{
Schema: s,
}))
```

## Examples

See the following examples for more usage:
Expand Down
28 changes: 27 additions & 1 deletion components/model/claude/claude.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import (
"github.com/anthropics/anthropic-sdk-go/vertex"
awsConfig "github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/credentials"
"github.com/eino-contrib/jsonschema"

"github.com/cloudwego/eino/components"

Expand Down Expand Up @@ -146,6 +147,7 @@ func NewChatModel(ctx context.Context, config *Config) (*ChatModel, error) {
thinking: config.Thinking,
topK: config.TopK,
topP: config.TopP,
responseFormat: config.ResponseFormat,
disableParallelToolUse: config.DisableParallelToolUse,
}, nil
}
Expand Down Expand Up @@ -240,6 +242,10 @@ type Config struct {

DisableParallelToolUse *bool `json:"disable_parallel_tool_use"`

// ResponseFormat specifies the format of the model's response
// Optional. Use for structured outputs
ResponseFormat *ResponseFormat `json:"response_format,omitempty"`

// Additional fields to set in the HTTP request header.
AdditionalHeaderFields map[string]string `json:"additional_header_fields"`

Expand All @@ -253,6 +259,12 @@ type Thinking struct {
BudgetTokens int `json:"budget_tokens"`
}

// ResponseFormat configures structured JSON output using a JSON schema.
type ResponseFormat struct {
// Schema is the JSON schema that the model's response must conform to.
Schema *jsonschema.Schema `json:"schema"`
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I was hesitant on providing a wrapper for ResponseFormat, but this is consistent in how all the other models define it.

}

type ChatModel struct {
cli anthropic.Client

Expand All @@ -263,6 +275,7 @@ type ChatModel struct {
topK *int32
topP *float32
thinking *Thinking
responseFormat *ResponseFormat
tools []anthropic.ToolUnionParam
origTools []*schema.ToolInfo
toolChoice *schema.ToolChoice
Expand Down Expand Up @@ -527,7 +540,8 @@ func (cm *ChatModel) genMessageNewParams(input []*schema.Message, opts ...model.
specOptions := model.GetImplSpecificOptions(&options{
TopK: cm.topK,
Thinking: cm.thinking,
DisableParallelToolUse: cm.disableParallelToolUse}, opts...)
DisableParallelToolUse: cm.disableParallelToolUse,
ResponseFormat: cm.responseFormat}, opts...)

params := anthropic.MessageNewParams{}
if commonOptions.Model != nil {
Expand Down Expand Up @@ -558,6 +572,18 @@ func (cm *ChatModel) genMessageNewParams(input []*schema.Message, opts ...model.
}
}

if specOptions.ResponseFormat != nil && specOptions.ResponseFormat.Schema != nil {
schemaMap, mErr := jsonSchemaToMap(specOptions.ResponseFormat.Schema)
if mErr != nil {
return anthropic.MessageNewParams{}, fmt.Errorf("failed to marshal response format schema: %w", mErr)
}
params.OutputConfig = anthropic.OutputConfigParam{
Format: anthropic.JSONOutputFormatParam{
Schema: schemaMap,
},
}
}

if err = cm.populateTools(&params, commonOptions, specOptions); err != nil {
return anthropic.MessageNewParams{}, err
}
Expand Down
21 changes: 16 additions & 5 deletions components/model/claude/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ cloud.google.com/go/compute/metadata v0.5.0 h1:Zr0eK8JbFv6+Wi4ilXAR8FJ3wyNdpxHKJ
cloud.google.com/go/compute/metadata v0.5.0/go.mod h1:aHnloV2TPI38yx4s9+wAZhHykWvVCfu7hQbF+9CWoiY=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/airbrake/gobrake v3.6.1+incompatible/go.mod h1:wM4gu3Cn0W0K7GUuVWnlXZU11AGBXMILnrdOU8Kn00o=
github.com/anthropics/anthropic-sdk-go v1.4.0 h1:fU1jKxYbQdQDiEXCxeW5XZRIOwKevn/PMg8Ay1nnUx0=
github.com/anthropics/anthropic-sdk-go v1.4.0/go.mod h1:AapDW22irxK2PSumZiQXYUFvsdQgkwIWlpESweWZI/c=
github.com/anthropics/anthropic-sdk-go v1.26.0 h1:oUTzFaUpAevfuELAP1sjL6CQJ9HHAfT7CoSYSac11PY=
github.com/anthropics/anthropic-sdk-go v1.26.0/go.mod h1:qUKmaW+uuPB64iy1l+4kOSvaLqPXnHTTBKH6RVZ7q5Q=
github.com/aws/aws-sdk-go-v2 v1.33.0 h1:Evgm4DI9imD81V0WwD+TN4DCwjUMdc94TrduMLbgZJs=
Expand Down Expand Up @@ -64,8 +66,6 @@ github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGX
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI=
github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/eino-contrib/jsonschema v1.0.3 h1:2Kfsm1xlMV0ssY2nuxshS4AwbLFuqmPmzIjLVJ1Fsp0=
Expand Down Expand Up @@ -221,6 +221,8 @@ golang.org/x/arch v0.11.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
Expand All @@ -236,14 +238,20 @@ golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73r
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs=
golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
Expand All @@ -253,12 +261,17 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg=
golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ=
golang.org/x/term v0.33.0 h1:NuFncQrRcaRvVmgRkvM3j/F00gWIAlcmlB8ACEKmGIg=
golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
Expand Down Expand Up @@ -302,8 +315,6 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
Expand Down
9 changes: 9 additions & 0 deletions components/model/claude/option.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ type options struct {
DisableParallelToolUse *bool

AutoCacheControl *CacheControl

ResponseFormat *ResponseFormat
}

func WithTopK(k int32) model.Option {
Expand Down Expand Up @@ -72,3 +74,10 @@ func WithAutoCacheControl(ctrl *CacheControl) model.Option {
o.AutoCacheControl = ctrl
})
}

// WithResponseFormat sets the response format for structured JSON output.
func WithResponseFormat(rf *ResponseFormat) model.Option {
return model.WrapImplSpecificOptFn(func(o *options) {
o.ResponseFormat = rf
})
}
18 changes: 18 additions & 0 deletions components/model/claude/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,24 @@

package claude

import (
"encoding/json"

"github.com/eino-contrib/jsonschema"
)

func jsonSchemaToMap(s *jsonschema.Schema) (map[string]any, error) {
b, err := json.Marshal(s)
if err != nil {
return nil, err
}
var m map[string]any
if err = json.Unmarshal(b, &m); err != nil {
return nil, err
}
return m, nil
}

func of[T any](v T) *T {
return &v
}
Loading