├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── PULL_REQUEST_TEMPLATE.md ├── static │ └── img │ │ └── eino │ │ ├── chain.png │ │ ├── chain_append_branch.png │ │ ├── chain_append_parallel.png │ │ ├── eino_concept.jpeg │ │ ├── eino_framework.jpeg │ │ ├── graph.gif │ │ ├── graph.png │ │ ├── graph_add_branch.png │ │ ├── graph_add_edge.png │ │ ├── graph_parallel.png │ │ ├── lark_group_en.png │ │ ├── lark_group_zh.png │ │ ├── react.png │ │ ├── simple_chain.png │ │ ├── stream.png │ │ └── tool_call_graph.png └── workflows │ ├── pr-check.yml │ └── tests.yml ├── .gitignore ├── .golangci.yaml ├── .licenserc.yaml ├── .testcoverage.yml ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE-APACHE ├── README.md ├── README.zh_CN.md ├── _typos.toml ├── callbacks ├── aspect_inject.go ├── aspect_inject_test.go ├── doc.go ├── handler_builder.go ├── interface.go └── interface_test.go ├── components ├── document │ ├── callback_extra_loader.go │ ├── callback_extra_transformer.go │ ├── doc.go │ ├── interface.go │ ├── option.go │ ├── option_test.go │ └── parser │ │ ├── ext_parser.go │ │ ├── interface.go │ │ ├── option.go │ │ ├── option_test.go │ │ ├── parser_test.go │ │ ├── testdata │ │ └── test.md │ │ └── text_parser.go ├── embedding │ ├── callback_extra.go │ ├── callback_extra_test.go │ ├── doc.go │ ├── interface.go │ ├── option.go │ └── option_test.go ├── indexer │ ├── callback_extra.go │ ├── callback_extra_test.go │ ├── doc.go │ ├── interface.go │ ├── option.go │ └── option_test.go ├── model │ ├── callback_extra.go │ ├── callback_extra_test.go │ ├── doc.go │ ├── interface.go │ ├── option.go │ └── option_test.go ├── prompt │ ├── callback_extra.go │ ├── callback_extra_test.go │ ├── chat_template.go │ ├── chat_template_test.go │ ├── doc.go │ ├── interface.go │ ├── option.go │ └── option_test.go ├── retriever │ ├── callback_extra.go │ ├── callback_extra_test.go │ ├── doc.go │ ├── interface.go │ ├── option.go │ └── option_test.go ├── tool │ ├── callback_extra.go │ ├── callback_extra_test.go │ ├── doc.go │ ├── interface.go │ ├── option.go │ ├── option_test.go │ └── utils │ │ ├── create_options.go │ │ ├── doc.go │ │ ├── error_handler.go │ │ ├── error_handler_test.go │ │ ├── invokable_func.go │ │ ├── invokable_func_test.go │ │ ├── streamable_func.go │ │ └── streamable_func_test.go └── types.go ├── compose ├── branch.go ├── branch_test.go ├── chain.go ├── chain_branch.go ├── chain_branch_test.go ├── chain_parallel.go ├── chain_test.go ├── checkpoint.go ├── checkpoint_test.go ├── component_to_graph_node.go ├── dag.go ├── dag_test.go ├── doc.go ├── error.go ├── error_test.go ├── field_mapping.go ├── generic_graph.go ├── generic_helper.go ├── graph.go ├── graph_add_node_options.go ├── graph_call_options.go ├── graph_call_options_test.go ├── graph_compile_options.go ├── graph_manager.go ├── graph_node.go ├── graph_run.go ├── graph_test.go ├── interrupt.go ├── introspect.go ├── pregel.go ├── runnable.go ├── runnable_test.go ├── state.go ├── state_test.go ├── stream_concat.go ├── stream_concat_test.go ├── stream_reader.go ├── stream_reader_test.go ├── tool_node.go ├── tool_node_test.go ├── types.go ├── types_composable.go ├── types_lambda.go ├── types_lambda_test.go ├── utils.go ├── utils_test.go ├── values_merge.go ├── values_merge_test.go ├── workflow.go └── workflow_test.go ├── doc.go ├── flow ├── agent │ ├── agent_option.go │ ├── multiagent │ │ └── host │ │ │ ├── callback.go │ │ │ ├── compose.go │ │ │ ├── compose_test.go │ │ │ ├── doc.go │ │ │ ├── options.go │ │ │ └── types.go │ ├── react │ │ ├── callback.go │ │ ├── doc.go │ │ ├── option.go │ │ ├── option_test.go │ │ ├── react.go │ │ └── react_test.go │ └── utils.go ├── indexer │ └── parent │ │ ├── parent.go │ │ └── parent_test.go └── retriever │ ├── multiquery │ ├── multi_query.go │ └── multi_query_test.go │ ├── parent │ ├── parent.go │ └── parent_test.go │ ├── router │ ├── router.go │ └── router_test.go │ └── utils │ └── utils.go ├── go.mod ├── go.sum ├── internal ├── callbacks │ ├── inject.go │ ├── interface.go │ └── manager.go ├── channel.go ├── channel_test.go ├── concat.go ├── generic │ ├── generic.go │ ├── generic_test.go │ ├── type_name.go │ └── type_name_test.go ├── gmap │ ├── gmap.go │ └── gmap_test.go ├── gslice │ ├── gslice.go │ └── gslice_test.go ├── merge.go ├── mock │ ├── components │ │ ├── document │ │ │ └── document_mock.go │ │ ├── embedding │ │ │ └── Embedding_mock.go │ │ ├── indexer │ │ │ └── indexer_mock.go │ │ ├── model │ │ │ └── ChatModel_mock.go │ │ └── retriever │ │ │ └── retriever_mock.go │ └── doc.go ├── safe │ ├── panic.go │ └── panic_test.go └── serialization │ ├── serialization.go │ └── serialization_test.go ├── schema ├── doc.go ├── document.go ├── document_test.go ├── message.go ├── message_parser.go ├── message_parser_test.go ├── message_test.go ├── select.go ├── stream.go ├── stream_copy_external_test.go ├── stream_test.go ├── tool.go └── tool_test.go └── utils └── callbacks ├── template.go └── template_test.go /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | 12 | A clear and concise description of what the bug is. 13 | 14 | **To Reproduce** 15 | 16 | Steps to reproduce the behavior: 17 | 1. Go to '...' 18 | 2. Click on '....' 19 | 3. Scroll down to '....' 20 | 4. See error 21 | 22 | **Expected behavior** 23 | 24 | A clear and concise description of what you expected to happen. 25 | 26 | **Screenshots** 27 | 28 | If applicable, add screenshots to help explain your problem. 29 | 30 | **Version:** 31 | 32 | Please provide the version of {project_name} you are using. 33 | 34 | **Environment:** 35 | 36 | The output of `go env`. 37 | 38 | **Additional context** 39 | 40 | Add any other context about the problem here. 41 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | 12 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 13 | 14 | **Describe the solution you'd like** 15 | 16 | A clear and concise description of what you want to happen. 17 | 18 | **Describe alternatives you've considered** 19 | 20 | A clear and concise description of any alternative solutions or features you've considered. 21 | 22 | **Additional context** 23 | 24 | Add any other context or screenshots about the feature request here. 25 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | #### What type of PR is this? 2 | 17 | 18 | #### Check the PR title. 19 | 24 | - [ ] This PR title match the format: \(optional scope): \ 25 | - [ ] The description of this PR title is user-oriented and clear enough for others to understand. 26 | - [ ] Attach the PR updating the user documentation if the current PR requires user awareness at the usage level. [User docs repo](https://github.com/cloudwego/cloudwego.github.io) 27 | 28 | 29 | #### (Optional) Translate the PR title into Chinese. 30 | 31 | 32 | #### (Optional) More detailed description for this PR(en: English/zh: Chinese). 33 | 36 | en: 37 | zh(optional): 38 | 39 | 40 | #### (Optional) Which issue(s) this PR fixes: 41 | 45 | 46 | #### (optional) The PR that updates user documentation: 47 | 50 | -------------------------------------------------------------------------------- /.github/static/img/eino/chain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudwego/eino/cef5624bf1dcb3d04edac89c66fe271fe39a1493/.github/static/img/eino/chain.png -------------------------------------------------------------------------------- /.github/static/img/eino/chain_append_branch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudwego/eino/cef5624bf1dcb3d04edac89c66fe271fe39a1493/.github/static/img/eino/chain_append_branch.png -------------------------------------------------------------------------------- /.github/static/img/eino/chain_append_parallel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudwego/eino/cef5624bf1dcb3d04edac89c66fe271fe39a1493/.github/static/img/eino/chain_append_parallel.png -------------------------------------------------------------------------------- /.github/static/img/eino/eino_concept.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudwego/eino/cef5624bf1dcb3d04edac89c66fe271fe39a1493/.github/static/img/eino/eino_concept.jpeg -------------------------------------------------------------------------------- /.github/static/img/eino/eino_framework.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudwego/eino/cef5624bf1dcb3d04edac89c66fe271fe39a1493/.github/static/img/eino/eino_framework.jpeg -------------------------------------------------------------------------------- /.github/static/img/eino/graph.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudwego/eino/cef5624bf1dcb3d04edac89c66fe271fe39a1493/.github/static/img/eino/graph.gif -------------------------------------------------------------------------------- /.github/static/img/eino/graph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudwego/eino/cef5624bf1dcb3d04edac89c66fe271fe39a1493/.github/static/img/eino/graph.png -------------------------------------------------------------------------------- /.github/static/img/eino/graph_add_branch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudwego/eino/cef5624bf1dcb3d04edac89c66fe271fe39a1493/.github/static/img/eino/graph_add_branch.png -------------------------------------------------------------------------------- /.github/static/img/eino/graph_add_edge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudwego/eino/cef5624bf1dcb3d04edac89c66fe271fe39a1493/.github/static/img/eino/graph_add_edge.png -------------------------------------------------------------------------------- /.github/static/img/eino/graph_parallel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudwego/eino/cef5624bf1dcb3d04edac89c66fe271fe39a1493/.github/static/img/eino/graph_parallel.png -------------------------------------------------------------------------------- /.github/static/img/eino/lark_group_en.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudwego/eino/cef5624bf1dcb3d04edac89c66fe271fe39a1493/.github/static/img/eino/lark_group_en.png -------------------------------------------------------------------------------- /.github/static/img/eino/lark_group_zh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudwego/eino/cef5624bf1dcb3d04edac89c66fe271fe39a1493/.github/static/img/eino/lark_group_zh.png -------------------------------------------------------------------------------- /.github/static/img/eino/react.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudwego/eino/cef5624bf1dcb3d04edac89c66fe271fe39a1493/.github/static/img/eino/react.png -------------------------------------------------------------------------------- /.github/static/img/eino/simple_chain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudwego/eino/cef5624bf1dcb3d04edac89c66fe271fe39a1493/.github/static/img/eino/simple_chain.png -------------------------------------------------------------------------------- /.github/static/img/eino/stream.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudwego/eino/cef5624bf1dcb3d04edac89c66fe271fe39a1493/.github/static/img/eino/stream.png -------------------------------------------------------------------------------- /.github/static/img/eino/tool_call_graph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudwego/eino/cef5624bf1dcb3d04edac89c66fe271fe39a1493/.github/static/img/eino/tool_call_graph.png -------------------------------------------------------------------------------- /.github/workflows/pr-check.yml: -------------------------------------------------------------------------------- 1 | name: Pull Request Check 2 | 3 | on: [ pull_request ] 4 | 5 | jobs: 6 | compliant: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v4 10 | 11 | - name: Check License Header 12 | uses: apache/skywalking-eyes/header@v0.4.0 13 | env: 14 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 15 | 16 | - name: Check Spell 17 | uses: crate-ci/typos@master 18 | 19 | golangci-lint: 20 | runs-on: ubuntu-latest 21 | permissions: 22 | contents: write 23 | pull-requests: write 24 | repository-projects: write 25 | steps: 26 | - uses: actions/checkout@v4 27 | - name: Set up Go 28 | uses: actions/setup-go@v5 29 | with: 30 | go-version: stable 31 | # for self-hosted, the cache path is shared across projects 32 | # and it works well without the cache of github actions 33 | # Enable it if we're going to use Github only 34 | cache: true 35 | 36 | - name: Golangci Lint 37 | # https://golangci-lint.run/ 38 | uses: golangci/golangci-lint-action@v6 39 | with: 40 | version: latest 41 | args: --timeout 5m 42 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | # vendor/ 16 | 17 | # Go workspace file 18 | go.work 19 | go.work.sum 20 | 21 | # env file 22 | .env 23 | 24 | # the result of the go build 25 | output* 26 | output/* 27 | 28 | # Files generated by IDEs 29 | .idea/ 30 | *.iml 31 | 32 | # Vim swap files 33 | *.swp 34 | 35 | # Vscode files 36 | .vscode 37 | 38 | /patches 39 | 40 | /vendor 41 | 42 | -------------------------------------------------------------------------------- /.golangci.yaml: -------------------------------------------------------------------------------- 1 | # output configuration options 2 | output: 3 | # Format: colored-line-number|line-number|json|tab|checkstyle|code-climate|junit-xml|github-actions 4 | formats: 5 | - format: colored-line-number 6 | # All available settings of specific linters. 7 | # Refer to https://golangci-lint.run/usage/linters 8 | linters-settings: 9 | gofumpt: 10 | # Choose whether to use the extra rules. 11 | # Default: false 12 | extra-rules: true 13 | govet: 14 | # Disable analyzers by name. 15 | # Run `go tool vet help` to see all analyzers. 16 | disable: 17 | - stdmethods 18 | linters: 19 | enable: 20 | - goimports 21 | - gofmt 22 | disable: 23 | - errcheck 24 | - typecheck 25 | - staticcheck 26 | - unused 27 | - gosimple 28 | - ineffassign 29 | - gofumpt 30 | issues: 31 | # include `vendor` `third_party` `testdata` `examples` `Godeps` `builtin` 32 | exclude-use-default: true 33 | exclude-files: 34 | - ".*\\.mock\\.go$" 35 | # exclude-dirs: -------------------------------------------------------------------------------- /.licenserc.yaml: -------------------------------------------------------------------------------- 1 | header: 2 | license: 3 | spdx-id: Apache-2.0 4 | copyright-owner: CloudWeGo Authors 5 | 6 | template: | 7 | /* 8 | * Copyright {{ .Year }} CloudWeGo Authors 9 | * 10 | * Licensed under the Apache License, Version 2.0 (the "License"); 11 | * you may not use this file except in compliance with the License. 12 | * You may obtain a copy of the License at 13 | * 14 | * http://www.apache.org/licenses/LICENSE-2.0 15 | * 16 | * Unless required by applicable law or agreed to in writing, software 17 | * distributed under the License is distributed on an "AS IS" BASIS, 18 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | * See the License for the specific language governing permissions and 20 | * limitations under the License. 21 | */ 22 | 23 | paths: 24 | - '**/*.go' 25 | - '**/*.s' 26 | 27 | comment: on-failure -------------------------------------------------------------------------------- /.testcoverage.yml: -------------------------------------------------------------------------------- 1 | # (optional; but recommended to set) 2 | # When specified reported file paths will not contain local prefix in the output. 3 | local-prefix: "github.com/cloudwego/eino" 4 | 5 | # Holds coverage thresholds percentages, values should be in range [0-100]. 6 | threshold: 7 | # (optional; default 0) 8 | # Minimum overall project coverage percentage required. 9 | total: 83 10 | 11 | package: 30 12 | 13 | # (optional; default 0) 14 | # Minimum coverage percentage required for individual files. 15 | file: 20 16 | 17 | by-package: 18 | threshold: 30 19 | show-all: false 20 | top-n: 5 21 | bottom-n: 5 22 | 23 | by-file: 24 | threshold: 20 25 | show-all: false 26 | top-n: 5 27 | bottom-n: 5 28 | 29 | diff: 30 | threshold: 80 31 | show-all: true 32 | new-code: true 33 | modified-code: true 34 | 35 | # Holds regexp rules which will exclude matched files or packages 36 | # from coverage statistics. 37 | exclude: 38 | paths: 39 | - "tests" 40 | - "examples/" 41 | - "mock/" 42 | - "callbacks/interface.go" 43 | - "utils/safe" -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to Contribute 2 | 3 | ## Your First Pull Request 4 | We use GitHub for our codebase. You can start by reading [How To Pull Request](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/about-pull-requests). 5 | 6 | ## Branch Organization 7 | We use [git-flow](https://nvie.com/posts/a-successful-git-branching-model/) as our branch organization, as known as [FDD](https://en.wikipedia.org/wiki/Feature-driven_development) 8 | 9 | ## Bugs 10 | ### 1. How to Find Known Issues 11 | We are using [Github Issues](https://github.com/cloudwego/{project_name}/issues) for our public bugs. We keep a close eye on this and try to make it clear when we have an internal fix in progress. Before filing a new task, try to make sure your problem doesn’t already exist. 12 | 13 | ### 2. Reporting New Issues 14 | Providing a reduced test code is a recommended way for reporting issues. Then can place in: 15 | - Just in issues 16 | - [Golang Playground](https://play.golang.org/) 17 | 18 | ### 3. Security Bugs 19 | Please do not report the safe disclosure of bugs to public issues. Contact us by [Support Email](mailto:conduct@cloudwego.io) 20 | 21 | ## How to Get in Touch 22 | - [Email](mailto:conduct@cloudwego.io) 23 | 24 | ## Submit a Pull Request 25 | Before you submit your Pull Request (PR) consider the following guidelines: 26 | 1. Search [GitHub](https://github.com/cloudwego/{project_name}/pulls) for an open or closed PR that relates to your submission. You don't want to duplicate existing efforts. 27 | 2. Be sure that an issue describes the problem you're fixing, or documents the design for the feature you'd like to add. Discussing the design upfront helps to ensure that we're ready to accept your work. 28 | 3. [Fork](https://docs.github.com/en/github/getting-started-with-github/fork-a-repo) the cloudwego {project_name} repo. 29 | 4. In your forked repository, make your changes in a new git branch: 30 | ``` 31 | git checkout -b my-fix-branch develop 32 | ``` 33 | 5. Create your patch, including appropriate test cases. 34 | 6. Follow our [Style Guides](#code-style-guides). 35 | 7. Commit your changes using a descriptive commit message that follows [AngularJS Git Commit Message Conventions](https://docs.google.com/document/d/1QrDFcIiPjSLDn3EL15IJygNPiHORgU1_OOAqWjiDU5Y/edit). 36 | Adherence to these conventions is necessary because release notes are automatically generated from these messages. 37 | 8. Push your branch to GitHub: 38 | ``` 39 | git push origin my-fix-branch 40 | ``` 41 | 9. In GitHub, send a pull request to `{project_name}:develop` 42 | 43 | ## Contribution Prerequisites 44 | - Our development environment keeps up with [Go Official](https://golang.org/project/). 45 | - You need fully checking with lint tools before submit your pull request. [gofmt](https://golang.org/pkg/cmd/gofmt/) and [golangci-lint](https://github.com/golangci/golangci-lint) 46 | - You are familiar with [GitHub](https://github.com) 47 | - Maybe you need familiar with [Actions](https://github.com/features/actions)(our default workflow tool). 48 | 49 | ## Code Style Guides 50 | - [Effective Go](https://golang.org/doc/effective_go) 51 | - [Go Code Review Comments](https://github.com/golang/go/wiki/CodeReviewComments) 52 | -------------------------------------------------------------------------------- /_typos.toml: -------------------------------------------------------------------------------- 1 | # Typo check: https://github.com/crate-ci/typos 2 | [default] 3 | 4 | [default.extend-words] 5 | Invokable = "Invokable" 6 | invokable = "invokable" 7 | InvokableLambda = "InvokableLambda" 8 | InvokableRun = "InvokableRun" 9 | typ = "typ" 10 | 11 | [files] 12 | extend-exclude = ["go.mod", "go.sum", "check_branch_name.sh"] 13 | -------------------------------------------------------------------------------- /callbacks/interface.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package callbacks 18 | 19 | import ( 20 | "github.com/cloudwego/eino/internal/callbacks" 21 | ) 22 | 23 | // RunInfo contains information about the running component. 24 | type RunInfo = callbacks.RunInfo 25 | 26 | // CallbackInput is the input of the callback. 27 | // the type of input is defined by the component. 28 | // using type Assert or convert func to convert the input to the right type you want. 29 | // e.g. 30 | // 31 | // CallbackInput in components/model/interface.go is: 32 | // type CallbackInput struct { 33 | // Messages []*schema.Message 34 | // Config *Config 35 | // Extra map[string]any 36 | // } 37 | // 38 | // and provide a func of model.ConvCallbackInput() to convert CallbackInput to *model.CallbackInput 39 | // in callback handler, you can use the following code to get the input: 40 | // 41 | // modelCallbackInput := model.ConvCallbackInput(in) 42 | // if modelCallbackInput == nil { 43 | // // is not a model callback input, just ignore it 44 | // return 45 | // } 46 | type CallbackInput = callbacks.CallbackInput 47 | 48 | type CallbackOutput = callbacks.CallbackOutput 49 | 50 | type Handler = callbacks.Handler 51 | 52 | // InitCallbackHandlers sets the global callback handlers. 53 | // It should be called BEFORE any callback handler by user. 54 | // It's useful when you want to inject some basic callbacks to all nodes. 55 | // Deprecated: Use AppendGlobalHandlers instead. 56 | func InitCallbackHandlers(handlers []Handler) { 57 | callbacks.GlobalHandlers = handlers 58 | } 59 | 60 | // AppendGlobalHandlers appends the given handlers to the global callback handlers. 61 | // This is the preferred way to add global callback handlers as it preserves existing handlers. 62 | // The global callback handlers will be executed for all nodes BEFORE user-specific handlers in CallOption. 63 | // Note: This function is not thread-safe and should only be called during process initialization. 64 | func AppendGlobalHandlers(handlers ...Handler) { 65 | callbacks.GlobalHandlers = append(callbacks.GlobalHandlers, handlers...) 66 | } 67 | 68 | // CallbackTiming enumerates all the timing of callback aspects. 69 | type CallbackTiming = callbacks.CallbackTiming 70 | 71 | const ( 72 | TimingOnStart CallbackTiming = iota 73 | TimingOnEnd 74 | TimingOnError 75 | TimingOnStartWithStreamInput 76 | TimingOnEndWithStreamOutput 77 | ) 78 | 79 | // TimingChecker checks if the handler is needed for the given callback aspect timing. 80 | // It's recommended for callback handlers to implement this interface, but not mandatory. 81 | // If a callback handler is created by using callbacks.HandlerHelper or handlerBuilder, then this interface is automatically implemented. 82 | // Eino's callback mechanism will try to use this interface to determine whether any handlers are needed for the given timing. 83 | // Also, the callback handler that is not needed for that timing will be skipped. 84 | type TimingChecker = callbacks.TimingChecker 85 | -------------------------------------------------------------------------------- /callbacks/interface_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package callbacks 18 | 19 | import ( 20 | "context" 21 | "testing" 22 | 23 | "github.com/stretchr/testify/assert" 24 | 25 | "github.com/cloudwego/eino/internal/callbacks" 26 | ) 27 | 28 | func TestAppendGlobalHandlers(t *testing.T) { 29 | // Clear global handlers before test 30 | callbacks.GlobalHandlers = nil 31 | 32 | // Create test handlers 33 | handler1 := NewHandlerBuilder(). 34 | OnStartFn(func(ctx context.Context, info *RunInfo, input CallbackInput) context.Context { 35 | return ctx 36 | }).Build() 37 | handler2 := NewHandlerBuilder(). 38 | OnEndFn(func(ctx context.Context, info *RunInfo, output CallbackOutput) context.Context { 39 | return ctx 40 | }).Build() 41 | 42 | // Test appending first handler 43 | AppendGlobalHandlers(handler1) 44 | assert.Equal(t, 1, len(callbacks.GlobalHandlers)) 45 | assert.Contains(t, callbacks.GlobalHandlers, handler1) 46 | 47 | // Test appending second handler 48 | AppendGlobalHandlers(handler2) 49 | assert.Equal(t, 2, len(callbacks.GlobalHandlers)) 50 | assert.Contains(t, callbacks.GlobalHandlers, handler1) 51 | assert.Contains(t, callbacks.GlobalHandlers, handler2) 52 | 53 | // Test appending nil handler 54 | AppendGlobalHandlers([]Handler{}...) 55 | assert.Equal(t, 2, len(callbacks.GlobalHandlers)) 56 | } 57 | -------------------------------------------------------------------------------- /components/document/callback_extra_loader.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package document 18 | 19 | import ( 20 | "github.com/cloudwego/eino/callbacks" 21 | "github.com/cloudwego/eino/schema" 22 | ) 23 | 24 | // LoaderCallbackInput is the input for the loader callback. 25 | type LoaderCallbackInput struct { 26 | // Source is the source of the documents. 27 | Source Source 28 | 29 | // Extra is the extra information for the callback. 30 | Extra map[string]any 31 | } 32 | 33 | // LoaderCallbackOutput is the output for the loader callback. 34 | type LoaderCallbackOutput struct { 35 | // Source is the source of the documents. 36 | Source Source 37 | 38 | // Docs is the documents to be loaded. 39 | Docs []*schema.Document 40 | 41 | // Extra is the extra information for the callback. 42 | Extra map[string]any 43 | } 44 | 45 | // ConvLoaderCallbackInput converts the callback input to the loader callback input. 46 | func ConvLoaderCallbackInput(src callbacks.CallbackInput) *LoaderCallbackInput { 47 | switch t := src.(type) { 48 | case *LoaderCallbackInput: 49 | return t 50 | case Source: 51 | return &LoaderCallbackInput{ 52 | Source: t, 53 | } 54 | default: 55 | return nil 56 | } 57 | } 58 | 59 | // ConvLoaderCallbackOutput converts the callback output to the loader callback output. 60 | func ConvLoaderCallbackOutput(src callbacks.CallbackOutput) *LoaderCallbackOutput { 61 | switch t := src.(type) { 62 | case *LoaderCallbackOutput: 63 | return t 64 | case []*schema.Document: 65 | return &LoaderCallbackOutput{ 66 | Docs: t, 67 | } 68 | default: 69 | return nil 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /components/document/callback_extra_transformer.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package document 18 | 19 | import ( 20 | "github.com/cloudwego/eino/callbacks" 21 | "github.com/cloudwego/eino/schema" 22 | ) 23 | 24 | // TransformerCallbackInput is the input for the transformer callback. 25 | type TransformerCallbackInput struct { 26 | // Input is the input documents. 27 | Input []*schema.Document 28 | 29 | // Extra is the extra information for the callback. 30 | Extra map[string]any 31 | } 32 | 33 | // TransformerCallbackOutput is the output for the transformer callback. 34 | type TransformerCallbackOutput struct { 35 | // Output is the output documents. 36 | Output []*schema.Document 37 | 38 | // Extra is the extra information for the callback. 39 | Extra map[string]any 40 | } 41 | 42 | // ConvTransformerCallbackInput converts the callback input to the transformer callback input. 43 | func ConvTransformerCallbackInput(src callbacks.CallbackInput) *TransformerCallbackInput { 44 | switch t := src.(type) { 45 | case *TransformerCallbackInput: 46 | return t 47 | case []*schema.Document: 48 | return &TransformerCallbackInput{ 49 | Input: t, 50 | } 51 | default: 52 | return nil 53 | } 54 | } 55 | 56 | // ConvTransformerCallbackOutput converts the callback output to the transformer callback output. 57 | func ConvTransformerCallbackOutput(src callbacks.CallbackOutput) *TransformerCallbackOutput { 58 | switch t := src.(type) { 59 | case *TransformerCallbackOutput: 60 | return t 61 | case []*schema.Document: 62 | return &TransformerCallbackOutput{ 63 | Output: t, 64 | } 65 | default: 66 | return nil 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /components/document/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package document 18 | -------------------------------------------------------------------------------- /components/document/interface.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package document 18 | 19 | import ( 20 | "context" 21 | 22 | "github.com/cloudwego/eino/schema" 23 | ) 24 | 25 | // Source is a document source. 26 | // e.g. https://www.bytedance.com/docx/xxxx, https://xxx.xxx.xxx/xx.pdf. 27 | // make sure the URI can be reached by service. 28 | type Source struct { 29 | URI string 30 | } 31 | 32 | //go:generate mockgen -destination ../../internal/mock/components/document/document_mock.go --package document -source interface.go 33 | 34 | // Loader is a document loader. 35 | type Loader interface { 36 | Load(ctx context.Context, src Source, opts ...LoaderOption) ([]*schema.Document, error) 37 | } 38 | 39 | // Transformer is to convert documents, such as split or filter. 40 | type Transformer interface { 41 | Transform(ctx context.Context, src []*schema.Document, opts ...TransformerOption) ([]*schema.Document, error) 42 | } 43 | -------------------------------------------------------------------------------- /components/document/option_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package document 18 | 19 | import ( 20 | "testing" 21 | 22 | "github.com/smartystreets/goconvey/convey" 23 | ) 24 | 25 | func TestImplSpecificOpts(t *testing.T) { 26 | type implSpecificOptions struct { 27 | conf string 28 | index int 29 | } 30 | 31 | withConf := func(conf string) func(o *implSpecificOptions) { 32 | return func(o *implSpecificOptions) { 33 | o.conf = conf 34 | } 35 | } 36 | 37 | withIndex := func(index int) func(o *implSpecificOptions) { 38 | return func(o *implSpecificOptions) { 39 | o.index = index 40 | } 41 | } 42 | 43 | convey.Convey("TestLoaderImplSpecificOpts", t, func() { 44 | documentOption1 := WrapLoaderImplSpecificOptFn(withConf("test_conf")) 45 | documentOption2 := WrapLoaderImplSpecificOptFn(withIndex(1)) 46 | 47 | implSpecificOpts := GetLoaderImplSpecificOptions(&implSpecificOptions{}, documentOption1, documentOption2) 48 | 49 | convey.So(implSpecificOpts, convey.ShouldResemble, &implSpecificOptions{ 50 | conf: "test_conf", 51 | index: 1, 52 | }) 53 | }) 54 | convey.Convey("TestTransformerImplSpecificOpts", t, func() { 55 | documentOption1 := WrapTransformerImplSpecificOptFn(withConf("test_conf")) 56 | documentOption2 := WrapTransformerImplSpecificOptFn(withIndex(1)) 57 | 58 | implSpecificOpts := GetTransformerImplSpecificOptions(&implSpecificOptions{}, documentOption1, documentOption2) 59 | 60 | convey.So(implSpecificOpts, convey.ShouldResemble, &implSpecificOptions{ 61 | conf: "test_conf", 62 | index: 1, 63 | }) 64 | }) 65 | } 66 | -------------------------------------------------------------------------------- /components/document/parser/ext_parser.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package parser 18 | 19 | import ( 20 | "context" 21 | "errors" 22 | "io" 23 | "path/filepath" 24 | 25 | "github.com/cloudwego/eino/schema" 26 | ) 27 | 28 | // ExtParserConfig defines the configuration for the ExtParser. 29 | type ExtParserConfig struct { 30 | // ext -> parser. 31 | // eg: map[string]Parser{ 32 | // ".pdf": &PDFParser{}, 33 | // ".md": &MarkdownParser{}, 34 | // } 35 | Parsers map[string]Parser 36 | 37 | // Fallback parser to use when no other parser is found. 38 | // Default is TextParser if not set. 39 | FallbackParser Parser 40 | } 41 | 42 | // ExtParser is a parser that uses the file extension to determine which parser to use. 43 | // You can register your own parsers by calling RegisterParser. 44 | // Default parser is TextParser. 45 | // Note: 46 | // 47 | // parse 时,是通过 filepath.Ext(uri) 的方式找到对应的 parser,因此使用时需要: 48 | // ① 必须使用 parser.WithURI 在请求时传入 URI 49 | // ② URI 必须能通过 filepath.Ext 来解析出符合预期的 ext 50 | // 51 | // eg: 52 | // 53 | // pdf, _ := os.Open("./testdata/test.pdf") 54 | // docs, err := ExtParser.Parse(ctx, pdf, parser.WithURI("./testdata/test.pdf")) 55 | type ExtParser struct { 56 | parsers map[string]Parser 57 | 58 | fallbackParser Parser 59 | } 60 | 61 | // NewExtParser creates a new ExtParser. 62 | func NewExtParser(ctx context.Context, conf *ExtParserConfig) (*ExtParser, error) { 63 | if conf == nil { 64 | conf = &ExtParserConfig{} 65 | } 66 | 67 | p := &ExtParser{ 68 | parsers: conf.Parsers, 69 | fallbackParser: conf.FallbackParser, 70 | } 71 | 72 | if p.fallbackParser == nil { 73 | p.fallbackParser = TextParser{} 74 | } 75 | 76 | if p.parsers == nil { 77 | p.parsers = make(map[string]Parser) 78 | } 79 | 80 | return p, nil 81 | } 82 | 83 | // GetParsers returns a copy of the registered parsers. 84 | // It is safe to modify the returned parsers. 85 | func (p *ExtParser) GetParsers() map[string]Parser { 86 | res := make(map[string]Parser, len(p.parsers)) 87 | for k, v := range p.parsers { 88 | res[k] = v 89 | } 90 | 91 | return res 92 | } 93 | 94 | // Parse parses the given reader and returns a list of documents. 95 | func (p *ExtParser) Parse(ctx context.Context, reader io.Reader, opts ...Option) ([]*schema.Document, error) { 96 | opt := GetCommonOptions(&Options{}, opts...) 97 | 98 | ext := filepath.Ext(opt.URI) 99 | 100 | parser, ok := p.parsers[ext] 101 | 102 | if !ok { 103 | parser = p.fallbackParser 104 | } 105 | 106 | if parser == nil { 107 | return nil, errors.New("no parser found for extension " + ext) 108 | } 109 | 110 | docs, err := parser.Parse(ctx, reader, opts...) 111 | if err != nil { 112 | return nil, err 113 | } 114 | 115 | for _, doc := range docs { 116 | if doc == nil { 117 | continue 118 | } 119 | 120 | if doc.MetaData == nil { 121 | doc.MetaData = make(map[string]any) 122 | } 123 | 124 | for k, v := range opt.ExtraMeta { 125 | doc.MetaData[k] = v 126 | } 127 | } 128 | 129 | return docs, nil 130 | } 131 | -------------------------------------------------------------------------------- /components/document/parser/interface.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package parser 18 | 19 | import ( 20 | "context" 21 | "io" 22 | 23 | "github.com/cloudwego/eino/schema" 24 | ) 25 | 26 | // Parser is a document parser, can be used to parse a document from a reader. 27 | type Parser interface { 28 | Parse(ctx context.Context, reader io.Reader, opts ...Option) ([]*schema.Document, error) 29 | } 30 | -------------------------------------------------------------------------------- /components/document/parser/option.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package parser 18 | 19 | type Options struct { 20 | // uri of source. 21 | URI string 22 | 23 | // extra metadata will merge to each document. 24 | ExtraMeta map[string]any 25 | } 26 | 27 | // Option defines call option for Parser component, which is part of the component interface signature. 28 | // Each Parser implementation could define its own options struct and option funcs within its own package, 29 | // then wrap the impl specific option funcs into this type, before passing to Transform. 30 | type Option struct { 31 | apply func(opts *Options) 32 | 33 | implSpecificOptFn any 34 | } 35 | 36 | // WithURI specifies the URI of the document. 37 | // It will be used as to select parser in ExtParser. 38 | func WithURI(uri string) Option { 39 | return Option{ 40 | apply: func(opts *Options) { 41 | opts.URI = uri 42 | }, 43 | } 44 | } 45 | 46 | // WithExtraMeta specifies the extra meta data of the document. 47 | func WithExtraMeta(meta map[string]any) Option { 48 | return Option{ 49 | apply: func(opts *Options) { 50 | opts.ExtraMeta = meta 51 | }, 52 | } 53 | } 54 | 55 | // GetCommonOptions extract parser Options from Option list, optionally providing a base Options with default values. 56 | func GetCommonOptions(base *Options, opts ...Option) *Options { 57 | if base == nil { 58 | base = &Options{} 59 | } 60 | 61 | for i := range opts { 62 | opt := opts[i] 63 | if opt.apply != nil { 64 | opt.apply(base) 65 | } 66 | } 67 | 68 | return base 69 | } 70 | 71 | // WrapImplSpecificOptFn wraps the impl specific option functions into Option type. 72 | // T: the type of the impl specific options struct. 73 | // Parser implementations are required to use this function to convert its own option functions into the unified Option type. 74 | // For example, if the Parser impl defines its own options struct: 75 | // 76 | // type customOptions struct { 77 | // conf string 78 | // } 79 | // 80 | // Then the impl needs to provide an option function as such: 81 | // 82 | // func WithConf(conf string) Option { 83 | // return WrapImplSpecificOptFn(func(o *customOptions) { 84 | // o.conf = conf 85 | // } 86 | // } 87 | // 88 | // . 89 | func WrapImplSpecificOptFn[T any](optFn func(*T)) Option { 90 | return Option{ 91 | implSpecificOptFn: optFn, 92 | } 93 | } 94 | 95 | // GetImplSpecificOptions provides Parser author the ability to extract their own custom options from the unified Option type. 96 | // T: the type of the impl specific options struct. 97 | // This function should be used within the Parser implementation's Transform function. 98 | // It is recommended to provide a base T as the first argument, within which the Parser author can provide default values for the impl specific options. 99 | func GetImplSpecificOptions[T any](base *T, opts ...Option) *T { 100 | if base == nil { 101 | base = new(T) 102 | } 103 | 104 | for i := range opts { 105 | opt := opts[i] 106 | if opt.implSpecificOptFn != nil { 107 | s, ok := opt.implSpecificOptFn.(func(*T)) 108 | if ok { 109 | s(base) 110 | } 111 | } 112 | } 113 | 114 | return base 115 | } 116 | -------------------------------------------------------------------------------- /components/document/parser/option_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package parser 18 | 19 | import ( 20 | "testing" 21 | 22 | "github.com/smartystreets/goconvey/convey" 23 | ) 24 | 25 | func TestImplSpecificOpts(t *testing.T) { 26 | type implSpecificOptions struct { 27 | conf string 28 | index int 29 | } 30 | 31 | withConf := func(conf string) func(o *implSpecificOptions) { 32 | return func(o *implSpecificOptions) { 33 | o.conf = conf 34 | } 35 | } 36 | 37 | withIndex := func(index int) func(o *implSpecificOptions) { 38 | return func(o *implSpecificOptions) { 39 | o.index = index 40 | } 41 | } 42 | 43 | convey.Convey("TestImplSpecificOpts", t, func() { 44 | parserOption1 := WrapImplSpecificOptFn(withConf("test_conf")) 45 | parserOption2 := WrapImplSpecificOptFn(withIndex(1)) 46 | 47 | implSpecificOpts := GetImplSpecificOptions(&implSpecificOptions{}, parserOption1, parserOption2) 48 | 49 | convey.So(implSpecificOpts, convey.ShouldResemble, &implSpecificOptions{ 50 | conf: "test_conf", 51 | index: 1, 52 | }) 53 | }) 54 | } 55 | -------------------------------------------------------------------------------- /components/document/parser/parser_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package parser 18 | 19 | import ( 20 | "context" 21 | "io" 22 | "os" 23 | "testing" 24 | 25 | "github.com/stretchr/testify/assert" 26 | 27 | "github.com/cloudwego/eino/schema" 28 | ) 29 | 30 | type ParserForTest struct { 31 | mock func() ([]*schema.Document, error) 32 | } 33 | 34 | func (p *ParserForTest) Parse(ctx context.Context, reader io.Reader, opts ...Option) ([]*schema.Document, error) { 35 | return p.mock() 36 | } 37 | 38 | func TestParser(t *testing.T) { 39 | ctx := context.Background() 40 | 41 | t.Run("Test default parser", func(t *testing.T) { 42 | conf := &ExtParserConfig{} 43 | 44 | p, err := NewExtParser(ctx, conf) 45 | if err != nil { 46 | t.Fatal(err) 47 | } 48 | 49 | f, err := os.Open("testdata/test.md") 50 | if err != nil { 51 | t.Fatal(err) 52 | } 53 | defer f.Close() 54 | 55 | docs, err := p.Parse(ctx, f, WithURI("testdata/test.md")) 56 | if err != nil { 57 | t.Fatal(err) 58 | } 59 | 60 | assert.Equal(t, 1, len(docs)) 61 | assert.Equal(t, "# Title\nhello world", docs[0].Content) 62 | }) 63 | 64 | t.Run("test types", func(t *testing.T) { 65 | mockParser := &ParserForTest{ 66 | mock: func() ([]*schema.Document, error) { 67 | return []*schema.Document{ 68 | { 69 | Content: "hello world", 70 | MetaData: map[string]any{ 71 | "type": "text", 72 | }, 73 | }, 74 | }, nil 75 | }, 76 | } 77 | 78 | conf := &ExtParserConfig{ 79 | Parsers: map[string]Parser{ 80 | ".md": mockParser, 81 | }, 82 | } 83 | 84 | p, err := NewExtParser(ctx, conf) 85 | if err != nil { 86 | t.Fatal(err) 87 | } 88 | 89 | f, err := os.Open("testdata/test.md") 90 | if err != nil { 91 | t.Fatal(err) 92 | } 93 | defer f.Close() 94 | 95 | docs, err := p.Parse(ctx, f, WithURI("x/test.md")) 96 | if err != nil { 97 | t.Fatal(err) 98 | } 99 | 100 | assert.Equal(t, 1, len(docs)) 101 | assert.Equal(t, "hello world", docs[0].Content) 102 | assert.Equal(t, "text", docs[0].MetaData["type"]) 103 | }) 104 | 105 | t.Run("test get parsers", func(t *testing.T) { 106 | p, err := NewExtParser(ctx, &ExtParserConfig{ 107 | Parsers: map[string]Parser{ 108 | ".md": &TextParser{}, 109 | }, 110 | }) 111 | if err != nil { 112 | t.Fatal(err) 113 | } 114 | 115 | ps := p.GetParsers() 116 | assert.Equal(t, 1, len(ps)) 117 | }) 118 | } 119 | -------------------------------------------------------------------------------- /components/document/parser/testdata/test.md: -------------------------------------------------------------------------------- 1 | # Title 2 | hello world -------------------------------------------------------------------------------- /components/document/parser/text_parser.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package parser 18 | 19 | import ( 20 | "context" 21 | "io" 22 | 23 | "github.com/cloudwego/eino/schema" 24 | ) 25 | 26 | const ( 27 | MetaKeySource = "_source" 28 | ) 29 | 30 | // TextParser is a simple parser that reads the text from a reader and returns a single document. 31 | // eg: 32 | // 33 | // docs, err := TextParser.Parse(ctx, strings.NewReader("hello world")) 34 | // fmt.Println(docs[0].Content) // "hello world" 35 | type TextParser struct{} 36 | 37 | // Parse reads the text from a reader and returns a single document. 38 | func (dp TextParser) Parse(ctx context.Context, reader io.Reader, opts ...Option) ([]*schema.Document, error) { 39 | data, err := io.ReadAll(reader) 40 | if err != nil { 41 | return nil, err 42 | } 43 | 44 | opt := GetCommonOptions(&Options{}, opts...) 45 | 46 | meta := make(map[string]any) 47 | meta[MetaKeySource] = opt.URI 48 | 49 | for k, v := range opt.ExtraMeta { 50 | meta[k] = v 51 | } 52 | 53 | doc := &schema.Document{ 54 | Content: string(data), 55 | MetaData: meta, 56 | } 57 | 58 | return []*schema.Document{doc}, nil 59 | } 60 | -------------------------------------------------------------------------------- /components/embedding/callback_extra.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package embedding 18 | 19 | import ( 20 | "github.com/cloudwego/eino/callbacks" 21 | ) 22 | 23 | // TokenUsage is the token usage for the embedding. 24 | type TokenUsage struct { 25 | // PromptTokens is the number of prompt tokens. 26 | PromptTokens int 27 | // CompletionTokens is the number of completion tokens. 28 | CompletionTokens int 29 | // TotalTokens is the total number of tokens. 30 | TotalTokens int 31 | } 32 | 33 | // Config is the config for the embedding. 34 | type Config struct { 35 | // Model is the model name. 36 | Model string 37 | // EncodingFormat is the encoding format. 38 | EncodingFormat string 39 | } 40 | 41 | // ComponentExtra is the extra information for the embedding. 42 | type ComponentExtra struct { 43 | // Config is the config for the embedding. 44 | Config *Config 45 | // TokenUsage is the token usage for the embedding. 46 | TokenUsage *TokenUsage 47 | } 48 | 49 | // CallbackInput is the input for the embedding callback. 50 | type CallbackInput struct { 51 | // Texts is the texts to be embedded. 52 | Texts []string 53 | // Config is the config for the embedding. 54 | Config *Config 55 | // Extra is the extra information for the callback. 56 | Extra map[string]any 57 | } 58 | 59 | // CallbackOutput is the output for the embedding callback. 60 | type CallbackOutput struct { 61 | // Embeddings is the embeddings. 62 | Embeddings [][]float64 63 | // Config is the config for creating the embedding. 64 | Config *Config 65 | // TokenUsage is the token usage for the embedding. 66 | TokenUsage *TokenUsage 67 | // Extra is the extra information for the callback. 68 | Extra map[string]any 69 | } 70 | 71 | // ConvCallbackInput converts the callback input to the embedding callback input. 72 | func ConvCallbackInput(src callbacks.CallbackInput) *CallbackInput { 73 | switch t := src.(type) { 74 | case *CallbackInput: 75 | return t 76 | case []string: 77 | return &CallbackInput{ 78 | Texts: t, 79 | } 80 | default: 81 | return nil 82 | } 83 | } 84 | 85 | // ConvCallbackOutput converts the callback output to the embedding callback output. 86 | func ConvCallbackOutput(src callbacks.CallbackOutput) *CallbackOutput { 87 | switch t := src.(type) { 88 | case *CallbackOutput: 89 | return t 90 | case [][]float64: 91 | return &CallbackOutput{ 92 | Embeddings: t, 93 | } 94 | default: 95 | return nil 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /components/embedding/callback_extra_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package embedding 18 | 19 | import ( 20 | "testing" 21 | 22 | "github.com/stretchr/testify/assert" 23 | ) 24 | 25 | func TestConvEmbedding(t *testing.T) { 26 | assert.NotNil(t, ConvCallbackInput(&CallbackInput{})) 27 | assert.NotNil(t, ConvCallbackInput([]string{})) 28 | assert.Nil(t, ConvCallbackInput("asd")) 29 | 30 | assert.NotNil(t, ConvCallbackOutput(&CallbackOutput{})) 31 | assert.NotNil(t, ConvCallbackOutput([][]float64{})) 32 | assert.Nil(t, ConvCallbackOutput("asd")) 33 | } 34 | -------------------------------------------------------------------------------- /components/embedding/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package embedding 18 | -------------------------------------------------------------------------------- /components/embedding/interface.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package embedding 18 | 19 | import "context" 20 | 21 | //go:generate mockgen -destination ../../internal/mock/components/embedding/Embedding_mock.go --package embedding -source interface.go 22 | type Embedder interface { 23 | EmbedStrings(ctx context.Context, texts []string, opts ...Option) ([][]float64, error) // invoke 24 | } 25 | -------------------------------------------------------------------------------- /components/embedding/option.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package embedding 18 | 19 | // Options is the options for the embedding. 20 | type Options struct { 21 | // Model is the model name for the embedding. 22 | Model *string 23 | } 24 | 25 | // Option is the call option for Embedder component. 26 | type Option struct { 27 | apply func(opts *Options) 28 | 29 | implSpecificOptFn any 30 | } 31 | 32 | // WithModel is the option to set the model for the embedding. 33 | func WithModel(model string) Option { 34 | return Option{ 35 | apply: func(opts *Options) { 36 | opts.Model = &model 37 | }, 38 | } 39 | } 40 | 41 | // GetCommonOptions extract embedding Options from Option list, optionally providing a base Options with default values. 42 | // eg. 43 | // 44 | // defaultModelName := "default_model" 45 | // embeddingOption := &embedding.Options{ 46 | // Model: &defaultModelName, 47 | // } 48 | // embeddingOption := embedding.GetCommonOptions(embeddingOption, opts...) 49 | func GetCommonOptions(base *Options, opts ...Option) *Options { 50 | if base == nil { 51 | base = &Options{} 52 | } 53 | 54 | for i := range opts { 55 | opt := opts[i] 56 | if opt.apply != nil { 57 | opt.apply(base) 58 | } 59 | } 60 | 61 | return base 62 | } 63 | 64 | // WrapImplSpecificOptFn is the option to wrap the implementation specific option function. 65 | func WrapImplSpecificOptFn[T any](optFn func(*T)) Option { 66 | return Option{ 67 | implSpecificOptFn: optFn, 68 | } 69 | } 70 | 71 | // GetImplSpecificOptions extract the implementation specific options from Option list, optionally providing a base options with default values. 72 | // e.g. 73 | // 74 | // myOption := &MyOption{ 75 | // Field1: "default_value", 76 | // } 77 | // 78 | // myOption := model.GetImplSpecificOptions(myOption, opts...) 79 | func GetImplSpecificOptions[T any](base *T, opts ...Option) *T { 80 | if base == nil { 81 | base = new(T) 82 | } 83 | 84 | for i := range opts { 85 | opt := opts[i] 86 | if opt.implSpecificOptFn != nil { 87 | optFn, ok := opt.implSpecificOptFn.(func(*T)) 88 | if ok { 89 | optFn(base) 90 | } 91 | } 92 | } 93 | 94 | return base 95 | } 96 | -------------------------------------------------------------------------------- /components/embedding/option_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package embedding 18 | 19 | import ( 20 | "testing" 21 | 22 | "github.com/stretchr/testify/assert" 23 | ) 24 | 25 | func TestOptions(t *testing.T) { 26 | defaultModel := "default_model" 27 | opts := GetCommonOptions(&Options{Model: &defaultModel}, WithModel("test_model")) 28 | assert.NotNil(t, opts.Model) 29 | assert.Equal(t, *opts.Model, "test_model") 30 | } 31 | -------------------------------------------------------------------------------- /components/indexer/callback_extra.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package indexer 18 | 19 | import ( 20 | "github.com/cloudwego/eino/callbacks" 21 | "github.com/cloudwego/eino/schema" 22 | ) 23 | 24 | // CallbackInput is the input for the indexer callback. 25 | type CallbackInput struct { 26 | // Docs is the documents to be indexed. 27 | Docs []*schema.Document 28 | // Extra is the extra information for the callback. 29 | Extra map[string]any 30 | } 31 | 32 | // CallbackOutput is the output for the indexer callback. 33 | type CallbackOutput struct { 34 | // IDs is the ids of the indexed documents returned by the indexer. 35 | IDs []string 36 | // Extra is the extra information for the callback. 37 | Extra map[string]any 38 | } 39 | 40 | // ConvCallbackInput converts the callback input to the indexer callback input. 41 | func ConvCallbackInput(src callbacks.CallbackInput) *CallbackInput { 42 | switch t := src.(type) { 43 | case *CallbackInput: 44 | return t 45 | case []*schema.Document: 46 | return &CallbackInput{ 47 | Docs: t, 48 | } 49 | default: 50 | return nil 51 | } 52 | } 53 | 54 | // ConvCallbackOutput converts the callback output to the indexer callback output. 55 | func ConvCallbackOutput(src callbacks.CallbackOutput) *CallbackOutput { 56 | switch t := src.(type) { 57 | case *CallbackOutput: 58 | return t 59 | case []string: 60 | return &CallbackOutput{ 61 | IDs: t, 62 | } 63 | default: 64 | return nil 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /components/indexer/callback_extra_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package indexer 18 | 19 | import ( 20 | "testing" 21 | 22 | "github.com/stretchr/testify/assert" 23 | 24 | "github.com/cloudwego/eino/schema" 25 | ) 26 | 27 | func TestConvIndexer(t *testing.T) { 28 | assert.NotNil(t, ConvCallbackInput(&CallbackInput{})) 29 | assert.NotNil(t, ConvCallbackInput([]*schema.Document{})) 30 | assert.Nil(t, ConvCallbackInput("asd")) 31 | 32 | assert.NotNil(t, ConvCallbackOutput(&CallbackOutput{})) 33 | assert.NotNil(t, ConvCallbackOutput([]string{})) 34 | assert.Nil(t, ConvCallbackOutput("asd")) 35 | } 36 | -------------------------------------------------------------------------------- /components/indexer/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package indexer 18 | -------------------------------------------------------------------------------- /components/indexer/interface.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package indexer 18 | 19 | import ( 20 | "context" 21 | 22 | "github.com/cloudwego/eino/schema" 23 | ) 24 | 25 | // Indexer is the interface for the indexer. 26 | // Indexer is used to store the documents. 27 | // 28 | //go:generate mockgen -destination ../../internal/mock/components/indexer/indexer_mock.go --package indexer -source interface.go 29 | type Indexer interface { 30 | // Store stores the documents. 31 | Store(ctx context.Context, docs []*schema.Document, opts ...Option) (ids []string, err error) // invoke 32 | } 33 | -------------------------------------------------------------------------------- /components/indexer/option.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package indexer 18 | 19 | import "github.com/cloudwego/eino/components/embedding" 20 | 21 | // Options is the options for the indexer. 22 | type Options struct { 23 | // SubIndexes is the sub indexes to be indexed. 24 | SubIndexes []string 25 | // Embedding is the embedding component. 26 | Embedding embedding.Embedder 27 | } 28 | 29 | // WithSubIndexes is the option to set the sub indexes for the indexer. 30 | func WithSubIndexes(subIndexes []string) Option { 31 | return Option{ 32 | apply: func(opts *Options) { 33 | opts.SubIndexes = subIndexes 34 | }, 35 | } 36 | } 37 | 38 | // WithEmbedding is the option to set the embedder for the indexer, which convert document to embeddings. 39 | func WithEmbedding(emb embedding.Embedder) Option { 40 | return Option{ 41 | apply: func(opts *Options) { 42 | opts.Embedding = emb 43 | }, 44 | } 45 | } 46 | 47 | // Option is the call option for Indexer component. 48 | type Option struct { 49 | apply func(opts *Options) 50 | 51 | implSpecificOptFn any 52 | } 53 | 54 | // GetCommonOptions extract indexer Options from Option list, optionally providing a base Options with default values. 55 | // e.g. 56 | // 57 | // indexerOption := &IndexerOption{ 58 | // SubIndexes: []string{"default_sub_index"}, // default value 59 | // } 60 | // 61 | // indexerOption := indexer.GetCommonOptions(indexerOption, opts...) 62 | func GetCommonOptions(base *Options, opts ...Option) *Options { 63 | if base == nil { 64 | base = &Options{} 65 | } 66 | 67 | for i := range opts { 68 | opt := opts[i] 69 | if opt.apply != nil { 70 | opt.apply(base) 71 | } 72 | } 73 | 74 | return base 75 | } 76 | 77 | // WrapImplSpecificOptFn is the option to wrap the implementation specific option function. 78 | func WrapImplSpecificOptFn[T any](optFn func(*T)) Option { 79 | return Option{ 80 | implSpecificOptFn: optFn, 81 | } 82 | } 83 | 84 | // GetImplSpecificOptions extract the implementation specific options from Option list, optionally providing a base options with default values. 85 | // e.g. 86 | // 87 | // myOption := &MyOption{ 88 | // Field1: "default_value", 89 | // } 90 | // 91 | // myOption := model.GetImplSpecificOptions(myOption, opts...) 92 | func GetImplSpecificOptions[T any](base *T, opts ...Option) *T { 93 | if base == nil { 94 | base = new(T) 95 | } 96 | 97 | for i := range opts { 98 | opt := opts[i] 99 | if opt.implSpecificOptFn != nil { 100 | optFn, ok := opt.implSpecificOptFn.(func(*T)) 101 | if ok { 102 | optFn(base) 103 | } 104 | } 105 | } 106 | 107 | return base 108 | } 109 | -------------------------------------------------------------------------------- /components/indexer/option_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package indexer 18 | 19 | import ( 20 | "testing" 21 | 22 | "github.com/smartystreets/goconvey/convey" 23 | 24 | "github.com/cloudwego/eino/internal/mock/components/embedding" 25 | ) 26 | 27 | func TestOptions(t *testing.T) { 28 | convey.Convey("test options", t, func() { 29 | var ( 30 | subIndexes = []string{"index_1", "index_2"} 31 | e = &embedding.MockEmbedder{} 32 | ) 33 | 34 | opts := GetCommonOptions( 35 | &Options{}, 36 | WithSubIndexes(subIndexes), 37 | WithEmbedding(e), 38 | ) 39 | 40 | convey.So(opts, convey.ShouldResemble, &Options{ 41 | SubIndexes: subIndexes, 42 | Embedding: e, 43 | }) 44 | }) 45 | } 46 | -------------------------------------------------------------------------------- /components/model/callback_extra_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package model 18 | 19 | import ( 20 | "testing" 21 | 22 | "github.com/stretchr/testify/assert" 23 | 24 | "github.com/cloudwego/eino/schema" 25 | ) 26 | 27 | func TestConvModel(t *testing.T) { 28 | assert.NotNil(t, ConvCallbackInput(&CallbackInput{})) 29 | assert.NotNil(t, ConvCallbackInput([]*schema.Message{})) 30 | assert.Nil(t, ConvCallbackInput("asd")) 31 | 32 | assert.NotNil(t, ConvCallbackOutput(&CallbackOutput{})) 33 | assert.NotNil(t, ConvCallbackOutput(&schema.Message{})) 34 | assert.Nil(t, ConvCallbackOutput("asd")) 35 | } 36 | -------------------------------------------------------------------------------- /components/model/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package model 18 | -------------------------------------------------------------------------------- /components/model/interface.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package model 18 | 19 | import ( 20 | "context" 21 | 22 | "github.com/cloudwego/eino/schema" 23 | ) 24 | 25 | // BaseChatModel defines the basic interface for chat models. 26 | // It provides methods for generating complete outputs and streaming outputs. 27 | // This interface serves as the foundation for all chat model implementations. 28 | // 29 | //go:generate mockgen -destination ../../internal/mock/components/model/ChatModel_mock.go --package model -source interface.go 30 | type BaseChatModel interface { 31 | Generate(ctx context.Context, input []*schema.Message, opts ...Option) (*schema.Message, error) 32 | Stream(ctx context.Context, input []*schema.Message, opts ...Option) ( 33 | *schema.StreamReader[*schema.Message], error) 34 | } 35 | 36 | // Deprecated: Please use ToolCallingChatModel interface instead, which provides a safer way to bind tools 37 | // without the concurrency issues and tool overwriting problems that may arise from the BindTools method. 38 | type ChatModel interface { 39 | BaseChatModel 40 | 41 | // BindTools bind tools to the model. 42 | // BindTools before requesting ChatModel generally. 43 | // notice the non-atomic problem of BindTools and Generate. 44 | BindTools(tools []*schema.ToolInfo) error 45 | } 46 | 47 | // ToolCallingChatModel extends BaseChatModel with tool calling capabilities. 48 | // It provides a WithTools method that returns a new instance with 49 | // the specified tools bound, avoiding state mutation and concurrency issues. 50 | type ToolCallingChatModel interface { 51 | BaseChatModel 52 | 53 | // WithTools returns a new ToolCallingChatModel instance with the specified tools bound. 54 | // This method does not modify the current instance, making it safer for concurrent use. 55 | WithTools(tools []*schema.ToolInfo) (ToolCallingChatModel, error) 56 | } 57 | -------------------------------------------------------------------------------- /components/model/option_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package model 18 | 19 | import ( 20 | "testing" 21 | 22 | "github.com/cloudwego/eino/schema" 23 | "github.com/smartystreets/goconvey/convey" 24 | ) 25 | 26 | func TestOptions(t *testing.T) { 27 | convey.Convey("test options", t, func() { 28 | var ( 29 | modelName = "model" 30 | temperature float32 = 0.9 31 | maxToken = 5000 32 | topP float32 = 0.8 33 | defaultModel = "default_model" 34 | defaultTemperature float32 = 1.0 35 | defaultMaxTokens = 1000 36 | defaultTopP float32 = 0.5 37 | tools = []*schema.ToolInfo{{Name: "asd"}, {Name: "qwe"}} 38 | toolChoice = schema.ToolChoiceForced 39 | ) 40 | 41 | opts := GetCommonOptions( 42 | &Options{ 43 | Model: &defaultModel, 44 | Temperature: &defaultTemperature, 45 | MaxTokens: &defaultMaxTokens, 46 | TopP: &defaultTopP, 47 | }, 48 | WithModel(modelName), 49 | WithTemperature(temperature), 50 | WithMaxTokens(maxToken), 51 | WithTopP(topP), 52 | WithStop([]string{"hello", "bye"}), 53 | WithTools(tools), 54 | WithToolChoice(toolChoice), 55 | ) 56 | 57 | convey.So(opts, convey.ShouldResemble, &Options{ 58 | Model: &modelName, 59 | Temperature: &temperature, 60 | MaxTokens: &maxToken, 61 | TopP: &topP, 62 | Stop: []string{"hello", "bye"}, 63 | Tools: tools, 64 | ToolChoice: &toolChoice, 65 | }) 66 | }) 67 | 68 | convey.Convey("test nil tools option", t, func() { 69 | opts := GetCommonOptions( 70 | &Options{ 71 | Tools: []*schema.ToolInfo{ 72 | {Name: "asd"}, 73 | {Name: "qwe"}, 74 | }, 75 | }, 76 | WithTools(nil), 77 | ) 78 | 79 | convey.So(opts.Tools, convey.ShouldNotBeNil) 80 | convey.So(len(opts.Tools), convey.ShouldEqual, 0) 81 | }) 82 | } 83 | 84 | type implOption struct { 85 | userID int64 86 | name string 87 | } 88 | 89 | func WithUserID(uid int64) Option { 90 | return WrapImplSpecificOptFn[implOption](func(i *implOption) { 91 | i.userID = uid 92 | }) 93 | } 94 | 95 | func WithName(n string) Option { 96 | return WrapImplSpecificOptFn[implOption](func(i *implOption) { 97 | i.name = n 98 | }) 99 | } 100 | 101 | func TestImplSpecificOption(t *testing.T) { 102 | convey.Convey("impl_specific_option", t, func() { 103 | opt := GetImplSpecificOptions(&implOption{}, WithUserID(101), WithName("Wang")) 104 | 105 | convey.So(opt, convey.ShouldEqual, &implOption{ 106 | userID: 101, 107 | name: "Wang", 108 | }) 109 | }) 110 | } 111 | -------------------------------------------------------------------------------- /components/prompt/callback_extra.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package prompt 18 | 19 | import ( 20 | "github.com/cloudwego/eino/callbacks" 21 | "github.com/cloudwego/eino/schema" 22 | ) 23 | 24 | // CallbackInput is the input for the callback. 25 | type CallbackInput struct { 26 | // Variables is the variables for the callback. 27 | Variables map[string]any 28 | // Templates is the templates for the callback. 29 | Templates []schema.MessagesTemplate 30 | // Extra is the extra information for the callback. 31 | Extra map[string]any 32 | } 33 | 34 | // CallbackOutput is the output for the callback. 35 | type CallbackOutput struct { 36 | // Result is the result for the callback. 37 | Result []*schema.Message 38 | // Templates is the templates for the callback. 39 | Templates []schema.MessagesTemplate 40 | // Extra is the extra information for the callback. 41 | Extra map[string]any 42 | } 43 | 44 | // ConvCallbackInput converts the callback input to the prompt callback input. 45 | func ConvCallbackInput(src callbacks.CallbackInput) *CallbackInput { 46 | switch t := src.(type) { 47 | case *CallbackInput: 48 | return t 49 | case map[string]any: 50 | return &CallbackInput{ 51 | Variables: t, 52 | } 53 | default: 54 | return nil 55 | } 56 | } 57 | 58 | // ConvCallbackOutput converts the callback output to the prompt callback output. 59 | func ConvCallbackOutput(src callbacks.CallbackOutput) *CallbackOutput { 60 | switch t := src.(type) { 61 | case *CallbackOutput: 62 | return t 63 | case []*schema.Message: 64 | return &CallbackOutput{ 65 | Result: t, 66 | } 67 | default: 68 | return nil 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /components/prompt/callback_extra_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package prompt 18 | 19 | import ( 20 | "testing" 21 | 22 | "github.com/stretchr/testify/assert" 23 | 24 | "github.com/cloudwego/eino/schema" 25 | ) 26 | 27 | func TestConvPrompt(t *testing.T) { 28 | assert.NotNil(t, ConvCallbackInput(&CallbackInput{})) 29 | assert.NotNil(t, ConvCallbackInput(map[string]any{})) 30 | assert.Nil(t, ConvCallbackInput("asd")) 31 | 32 | assert.NotNil(t, ConvCallbackOutput(&CallbackOutput{})) 33 | assert.NotNil(t, ConvCallbackOutput([]*schema.Message{})) 34 | assert.Nil(t, ConvCallbackOutput("asd")) 35 | } 36 | -------------------------------------------------------------------------------- /components/prompt/chat_template.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package prompt 18 | 19 | import ( 20 | "context" 21 | 22 | "github.com/cloudwego/eino/callbacks" 23 | "github.com/cloudwego/eino/schema" 24 | ) 25 | 26 | // DefaultChatTemplate is the default chat template implementation. 27 | type DefaultChatTemplate struct { 28 | // templates is the templates for the chat template. 29 | templates []schema.MessagesTemplate 30 | // formatType is the format type for the chat template. 31 | formatType schema.FormatType 32 | } 33 | 34 | // FromMessages creates a new DefaultChatTemplate from the given templates and format type. 35 | // eg. 36 | // 37 | // template := prompt.FromMessages(schema.FString, &schema.Message{Content: "Hello, {name}!"}, &schema.Message{Content: "how are you?"}) 38 | // // in chain, or graph 39 | // chain := compose.NewChain[map[string]any, []*schema.Message]() 40 | // chain.AppendChatTemplate(template) 41 | func FromMessages(formatType schema.FormatType, templates ...schema.MessagesTemplate) *DefaultChatTemplate { 42 | return &DefaultChatTemplate{ 43 | templates: templates, 44 | formatType: formatType, 45 | } 46 | } 47 | 48 | // Format formats the chat template with the given context and variables. 49 | func (t *DefaultChatTemplate) Format(ctx context.Context, 50 | vs map[string]any, _ ...Option) (result []*schema.Message, err error) { 51 | 52 | defer func() { 53 | if err != nil { 54 | _ = callbacks.OnError(ctx, err) 55 | } 56 | }() 57 | 58 | ctx = callbacks.OnStart(ctx, &CallbackInput{ 59 | Variables: vs, 60 | Templates: t.templates, 61 | }) 62 | 63 | result = make([]*schema.Message, 0, len(t.templates)) 64 | for _, template := range t.templates { 65 | msgs, err := template.Format(ctx, vs, t.formatType) 66 | if err != nil { 67 | return nil, err 68 | } 69 | 70 | result = append(result, msgs...) 71 | } 72 | 73 | _ = callbacks.OnEnd(ctx, &CallbackOutput{ 74 | Result: result, 75 | Templates: t.templates, 76 | }) 77 | 78 | return result, nil 79 | } 80 | 81 | // GetType returns the type of the chat template (Default). 82 | func (t *DefaultChatTemplate) GetType() string { 83 | return "Default" 84 | } 85 | 86 | // IsCallbacksEnabled checks if the callbacks are enabled for the chat template. 87 | func (t *DefaultChatTemplate) IsCallbacksEnabled() bool { 88 | return true 89 | } 90 | -------------------------------------------------------------------------------- /components/prompt/chat_template_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package prompt 18 | 19 | import ( 20 | "context" 21 | "testing" 22 | 23 | "github.com/stretchr/testify/assert" 24 | 25 | "github.com/cloudwego/eino/schema" 26 | ) 27 | 28 | func TestFormat(t *testing.T) { 29 | pyFmtTestTemplate := []schema.MessagesTemplate{ 30 | schema.SystemMessage( 31 | "you are a helpful assistant.\n" + 32 | "here is the context: {context}"), 33 | schema.MessagesPlaceholder("chat_history", true), 34 | schema.UserMessage("question: {question}"), 35 | } 36 | jinja2TestTemplate := []schema.MessagesTemplate{ 37 | schema.SystemMessage( 38 | "you are a helpful assistant.\n" + 39 | "here is the context: {{context}}"), 40 | schema.MessagesPlaceholder("chat_history", true), 41 | schema.UserMessage("question: {{question}}"), 42 | } 43 | goFmtTestTemplate := []schema.MessagesTemplate{ 44 | schema.SystemMessage( 45 | "you are a helpful assistant.\n" + 46 | "here is the context: {{.context}}"), 47 | schema.MessagesPlaceholder("chat_history", true), 48 | schema.UserMessage("question: {{.question}}"), 49 | } 50 | testValues := map[string]any{ 51 | "context": "it's beautiful day", 52 | "question": "how is the day today", 53 | "chat_history": []*schema.Message{ 54 | schema.UserMessage("who are you"), 55 | schema.AssistantMessage("I'm a helpful assistant", nil), 56 | }, 57 | } 58 | expected := []*schema.Message{ 59 | schema.SystemMessage( 60 | "you are a helpful assistant.\n" + 61 | "here is the context: it's beautiful day"), 62 | schema.UserMessage("who are you"), 63 | schema.AssistantMessage("I'm a helpful assistant", nil), 64 | schema.UserMessage("question: how is the day today"), 65 | } 66 | 67 | // FString 68 | chatTemplate := FromMessages(schema.FString, pyFmtTestTemplate...) 69 | msgs, err := chatTemplate.Format(context.Background(), testValues) 70 | assert.Nil(t, err) 71 | assert.Equal(t, expected, msgs) 72 | 73 | // Jinja2 74 | chatTemplate = FromMessages(schema.Jinja2, jinja2TestTemplate...) 75 | msgs, err = chatTemplate.Format(context.Background(), testValues) 76 | assert.Nil(t, err) 77 | assert.Equal(t, expected, msgs) 78 | 79 | // GoTemplate 80 | chatTemplate = FromMessages(schema.GoTemplate, goFmtTestTemplate...) 81 | msgs, err = chatTemplate.Format(context.Background(), testValues) 82 | assert.Nil(t, err) 83 | assert.Equal(t, expected, msgs) 84 | } 85 | 86 | func TestDocumentFormat(t *testing.T) { 87 | docs := []*schema.Document{ 88 | { 89 | ID: "1", 90 | Content: "qwe", 91 | MetaData: map[string]any{ 92 | "hello": 888, 93 | }, 94 | }, 95 | { 96 | ID: "2", 97 | Content: "asd", 98 | MetaData: map[string]any{ 99 | "bye": 111, 100 | }, 101 | }, 102 | } 103 | 104 | template := FromMessages(schema.FString, 105 | schema.SystemMessage("all:{all_docs}\nsingle:{single_doc}"), 106 | ) 107 | 108 | msgs, err := template.Format(context.Background(), map[string]any{ 109 | "all_docs": docs, 110 | "single_doc": docs[0], 111 | }) 112 | 113 | assert.Nil(t, err) 114 | t.Log(msgs) 115 | } 116 | -------------------------------------------------------------------------------- /components/prompt/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package prompt 18 | -------------------------------------------------------------------------------- /components/prompt/interface.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package prompt 18 | 19 | import ( 20 | "context" 21 | 22 | "github.com/cloudwego/eino/schema" 23 | ) 24 | 25 | var _ ChatTemplate = &DefaultChatTemplate{} 26 | 27 | type ChatTemplate interface { 28 | Format(ctx context.Context, vs map[string]any, opts ...Option) ([]*schema.Message, error) 29 | } 30 | -------------------------------------------------------------------------------- /components/prompt/option.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package prompt 18 | 19 | // Option is the call option for ChatTemplate component. 20 | type Option struct { 21 | implSpecificOptFn any 22 | } 23 | 24 | // WrapImplSpecificOptFn wraps the implementation specific option function. 25 | func WrapImplSpecificOptFn[T any](optFn func(*T)) Option { 26 | return Option{ 27 | implSpecificOptFn: optFn, 28 | } 29 | } 30 | 31 | // GetImplSpecificOptions extracts the implementation specific options from Option list, optionally providing a base options with default values. 32 | func GetImplSpecificOptions[T any](base *T, opts ...Option) *T { 33 | if base == nil { 34 | base = new(T) 35 | } 36 | 37 | for i := range opts { 38 | opt := opts[i] 39 | if opt.implSpecificOptFn != nil { 40 | s, ok := opt.implSpecificOptFn.(func(*T)) 41 | if ok { 42 | s(base) 43 | } 44 | } 45 | } 46 | 47 | return base 48 | } 49 | -------------------------------------------------------------------------------- /components/prompt/option_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package prompt 18 | 19 | import ( 20 | "testing" 21 | 22 | "github.com/smartystreets/goconvey/convey" 23 | ) 24 | 25 | type implOption struct { 26 | userID int64 27 | name string 28 | } 29 | 30 | func WithUserID(uid int64) Option { 31 | return WrapImplSpecificOptFn[implOption](func(i *implOption) { 32 | i.userID = uid 33 | }) 34 | } 35 | 36 | func WithName(n string) Option { 37 | return WrapImplSpecificOptFn[implOption](func(i *implOption) { 38 | i.name = n 39 | }) 40 | } 41 | 42 | func TestImplSpecificOption(t *testing.T) { 43 | convey.Convey("impl_specific_option", t, func() { 44 | opt := GetImplSpecificOptions(&implOption{}, WithUserID(101), WithName("Wang")) 45 | 46 | convey.So(opt, convey.ShouldEqual, &implOption{ 47 | userID: 101, 48 | name: "Wang", 49 | }) 50 | }) 51 | } 52 | -------------------------------------------------------------------------------- /components/retriever/callback_extra.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package retriever 18 | 19 | import ( 20 | "github.com/cloudwego/eino/callbacks" 21 | "github.com/cloudwego/eino/schema" 22 | ) 23 | 24 | // CallbackInput is the input for the retriever callback. 25 | type CallbackInput struct { 26 | // Query is the query for the retriever. 27 | Query string 28 | 29 | // TopK is the top k for the retriever, which means the top number of documents to retrieve. 30 | TopK int 31 | // Filter is the filter for the retriever. 32 | Filter string 33 | // ScoreThreshold is the score threshold for the retriever, eg 0.5 means the score of the document must be greater than 0.5. 34 | ScoreThreshold *float64 35 | 36 | // Extra is the extra information for the retriever. 37 | Extra map[string]any 38 | } 39 | 40 | // CallbackOutput is the output for the retriever callback. 41 | type CallbackOutput struct { 42 | // Docs is the documents for the retriever. 43 | Docs []*schema.Document 44 | // Extra is the extra information for the retriever. 45 | Extra map[string]any 46 | } 47 | 48 | // ConvCallbackInput converts the callback input to the retriever callback input. 49 | func ConvCallbackInput(src callbacks.CallbackInput) *CallbackInput { 50 | switch t := src.(type) { 51 | case *CallbackInput: 52 | return t 53 | case string: 54 | return &CallbackInput{ 55 | Query: t, 56 | } 57 | default: 58 | return nil 59 | } 60 | } 61 | 62 | // ConvCallbackOutput converts the callback output to the retriever callback output. 63 | func ConvCallbackOutput(src callbacks.CallbackOutput) *CallbackOutput { 64 | switch t := src.(type) { 65 | case *CallbackOutput: 66 | return t 67 | case []*schema.Document: 68 | return &CallbackOutput{ 69 | Docs: t, 70 | } 71 | default: 72 | return nil 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /components/retriever/callback_extra_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package retriever 18 | 19 | import ( 20 | "testing" 21 | 22 | "github.com/stretchr/testify/assert" 23 | 24 | "github.com/cloudwego/eino/schema" 25 | ) 26 | 27 | func TestConvRetriever(t *testing.T) { 28 | assert.NotNil(t, ConvCallbackInput(&CallbackInput{})) 29 | assert.NotNil(t, ConvCallbackInput("asd")) 30 | assert.Nil(t, ConvCallbackInput([]string{})) 31 | 32 | assert.NotNil(t, ConvCallbackOutput(&CallbackOutput{})) 33 | assert.NotNil(t, ConvCallbackOutput([]*schema.Document{})) 34 | assert.Nil(t, ConvCallbackOutput("asd")) 35 | } 36 | -------------------------------------------------------------------------------- /components/retriever/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package retriever 18 | -------------------------------------------------------------------------------- /components/retriever/interface.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package retriever 18 | 19 | import ( 20 | "context" 21 | 22 | "github.com/cloudwego/eino/schema" 23 | ) 24 | 25 | //go:generate mockgen -destination ../../internal/mock/components/retriever/retriever_mock.go --package retriever -source interface.go 26 | 27 | // Retriever is the interface for retriever. 28 | // It is used to retrieve documents from a source. 29 | // 30 | // e.g. 31 | // 32 | // retriever, err := redis.NewRetriever(ctx, &redis.RetrieverConfig{}) 33 | // if err != nil {...} 34 | // docs, err := retriever.Retrieve(ctx, "query") // <= using directly 35 | // docs, err := retriever.Retrieve(ctx, "query", retriever.WithTopK(3)) // <= using options 36 | // 37 | // graph := compose.NewGraph[inputType, outputType](compose.RunTypeDAG) 38 | // graph.AddRetrieverNode("retriever_node_key", retriever) // <= using in graph 39 | type Retriever interface { 40 | Retrieve(ctx context.Context, query string, opts ...Option) ([]*schema.Document, error) 41 | } 42 | -------------------------------------------------------------------------------- /components/retriever/option_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package retriever 18 | 19 | import ( 20 | "testing" 21 | 22 | "github.com/smartystreets/goconvey/convey" 23 | 24 | "github.com/cloudwego/eino/internal/mock/components/embedding" 25 | ) 26 | 27 | func TestOptions(t *testing.T) { 28 | convey.Convey("test options", t, func() { 29 | var ( 30 | index = "index" 31 | topK = 2 32 | scoreThreshold = 4.0 33 | subIndex = "sub_index" 34 | dslInfo = map[string]any{"dsl": "dsl"} 35 | e = &embedding.MockEmbedder{} 36 | defaultTopK = 1 37 | ) 38 | 39 | opts := GetCommonOptions( 40 | &Options{ 41 | TopK: &defaultTopK, 42 | }, 43 | WithIndex(index), 44 | WithTopK(topK), 45 | WithScoreThreshold(scoreThreshold), 46 | WithSubIndex(subIndex), 47 | WithDSLInfo(dslInfo), 48 | WithEmbedding(e), 49 | ) 50 | 51 | convey.So(opts, convey.ShouldResemble, &Options{ 52 | Index: &index, 53 | TopK: &topK, 54 | ScoreThreshold: &scoreThreshold, 55 | SubIndex: &subIndex, 56 | DSLInfo: dslInfo, 57 | Embedding: e, 58 | }) 59 | }) 60 | } 61 | -------------------------------------------------------------------------------- /components/tool/callback_extra.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package tool 18 | 19 | import ( 20 | "github.com/cloudwego/eino/callbacks" 21 | ) 22 | 23 | // CallbackInput is the input for the tool callback. 24 | type CallbackInput struct { 25 | // ArgumentsInJSON is the arguments in json format for the tool. 26 | ArgumentsInJSON string 27 | // Extra is the extra information for the tool. 28 | Extra map[string]any 29 | } 30 | 31 | // CallbackOutput is the output for the tool callback. 32 | type CallbackOutput struct { 33 | // Response is the response for the tool. 34 | Response string 35 | // Extra is the extra information for the tool. 36 | Extra map[string]any 37 | } 38 | 39 | // ConvCallbackInput converts the callback input to the tool callback input. 40 | func ConvCallbackInput(src callbacks.CallbackInput) *CallbackInput { 41 | switch t := src.(type) { 42 | case *CallbackInput: 43 | return t 44 | case string: 45 | return &CallbackInput{ArgumentsInJSON: t} 46 | default: 47 | return nil 48 | } 49 | } 50 | 51 | // ConvCallbackOutput converts the callback output to the tool callback output. 52 | func ConvCallbackOutput(src callbacks.CallbackOutput) *CallbackOutput { 53 | switch t := src.(type) { 54 | case *CallbackOutput: 55 | return t 56 | case string: 57 | return &CallbackOutput{Response: t} 58 | default: 59 | return nil 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /components/tool/callback_extra_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package tool 18 | 19 | import ( 20 | "testing" 21 | 22 | "github.com/stretchr/testify/assert" 23 | ) 24 | 25 | func TestConvCallbackInput(t *testing.T) { 26 | assert.NotNil(t, ConvCallbackInput(&CallbackInput{})) 27 | assert.NotNil(t, ConvCallbackInput("asd")) 28 | assert.Nil(t, ConvCallbackInput(123)) 29 | assert.Nil(t, ConvCallbackInput(nil)) 30 | } 31 | 32 | func TestConvCallbackOutput(t *testing.T) { 33 | assert.NotNil(t, ConvCallbackOutput(&CallbackOutput{})) 34 | assert.NotNil(t, ConvCallbackOutput("asd")) 35 | assert.Nil(t, ConvCallbackOutput(123)) 36 | assert.Nil(t, ConvCallbackOutput(nil)) 37 | } 38 | -------------------------------------------------------------------------------- /components/tool/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package tool 18 | -------------------------------------------------------------------------------- /components/tool/interface.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package tool 18 | 19 | import ( 20 | "context" 21 | 22 | "github.com/cloudwego/eino/schema" 23 | ) 24 | 25 | // BaseTool get tool info for ChatModel intent recognition. 26 | type BaseTool interface { 27 | Info(ctx context.Context) (*schema.ToolInfo, error) 28 | } 29 | 30 | // InvokableTool the tool for ChatModel intent recognition and ToolsNode execution. 31 | // nolint: byted_s_interface_name 32 | type InvokableTool interface { 33 | BaseTool 34 | 35 | // InvokableRun call function with arguments in JSON format 36 | InvokableRun(ctx context.Context, argumentsInJSON string, opts ...Option) (string, error) 37 | } 38 | 39 | // StreamableTool the stream tool for ChatModel intent recognition and ToolsNode execution. 40 | // nolint: byted_s_interface_name 41 | type StreamableTool interface { 42 | BaseTool 43 | 44 | StreamableRun(ctx context.Context, argumentsInJSON string, opts ...Option) (*schema.StreamReader[string], error) 45 | } 46 | -------------------------------------------------------------------------------- /components/tool/option.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package tool 18 | 19 | // Option defines call option for InvokableTool or StreamableTool component, which is part of component interface signature. 20 | // Each tool implementation could define its own options struct and option funcs within its own package, 21 | // then wrap the impl specific option funcs into this type, before passing to InvokableRun or StreamableRun. 22 | type Option struct { 23 | implSpecificOptFn any 24 | } 25 | 26 | // WrapImplSpecificOptFn wraps the impl specific option functions into Option type. 27 | // T: the type of the impl specific options struct. 28 | // Tool implementations are required to use this function to convert its own option functions into the unified Option type. 29 | // For example, if the tool defines its own options struct: 30 | // 31 | // type customOptions struct { 32 | // conf string 33 | // } 34 | // 35 | // Then the tool needs to provide an option function as such: 36 | // 37 | // func WithConf(conf string) Option { 38 | // return WrapImplSpecificOptFn(func(o *customOptions) { 39 | // o.conf = conf 40 | // } 41 | // } 42 | // 43 | // . 44 | func WrapImplSpecificOptFn[T any](optFn func(*T)) Option { 45 | return Option{ 46 | implSpecificOptFn: optFn, 47 | } 48 | } 49 | 50 | // GetImplSpecificOptions provides tool author the ability to extract their own custom options from the unified Option type. 51 | // T: the type of the impl specific options struct. 52 | // This function should be used within the tool implementation's InvokableRun or StreamableRun functions. 53 | // It is recommended to provide a base T as the first argument, within which the tool author can provide default values for the impl specific options. 54 | // eg. 55 | // 56 | // type customOptions struct { 57 | // conf string 58 | // } 59 | // defaultOptions := &customOptions{} 60 | // 61 | // customOptions := tool.GetImplSpecificOptions(defaultOptions, opts...) 62 | func GetImplSpecificOptions[T any](base *T, opts ...Option) *T { 63 | if base == nil { 64 | base = new(T) 65 | } 66 | 67 | for i := range opts { 68 | opt := opts[i] 69 | if opt.implSpecificOptFn != nil { 70 | optFn, ok := opt.implSpecificOptFn.(func(*T)) 71 | if ok { 72 | optFn(base) 73 | } 74 | } 75 | } 76 | 77 | return base 78 | } 79 | -------------------------------------------------------------------------------- /components/tool/option_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package tool 18 | 19 | import ( 20 | "testing" 21 | 22 | "github.com/smartystreets/goconvey/convey" 23 | ) 24 | 25 | func TestImplSpecificOpts(t *testing.T) { 26 | convey.Convey("TestImplSpecificOpts", t, func() { 27 | type implSpecificOptions struct { 28 | conf string 29 | index int 30 | } 31 | 32 | withConf := func(conf string) func(o *implSpecificOptions) { 33 | return func(o *implSpecificOptions) { 34 | o.conf = conf 35 | } 36 | } 37 | 38 | withIndex := func(index int) func(o *implSpecificOptions) { 39 | return func(o *implSpecificOptions) { 40 | o.index = index 41 | } 42 | } 43 | 44 | toolOption1 := WrapImplSpecificOptFn(withConf("test_conf")) 45 | toolOption2 := WrapImplSpecificOptFn(withIndex(1)) 46 | 47 | implSpecificOpts := GetImplSpecificOptions(&implSpecificOptions{}, toolOption1, toolOption2) 48 | 49 | convey.So(implSpecificOpts, convey.ShouldResemble, &implSpecificOptions{ 50 | conf: "test_conf", 51 | index: 1, 52 | }) 53 | }) 54 | } 55 | -------------------------------------------------------------------------------- /components/tool/utils/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package utils 18 | -------------------------------------------------------------------------------- /components/tool/utils/error_handler_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package utils 18 | 19 | import ( 20 | "context" 21 | "errors" 22 | "io" 23 | "testing" 24 | 25 | "github.com/stretchr/testify/assert" 26 | 27 | "github.com/cloudwego/eino/components/tool" 28 | "github.com/cloudwego/eino/schema" 29 | ) 30 | 31 | type testErrorTool struct{} 32 | 33 | func (t *testErrorTool) Info(ctx context.Context) (*schema.ToolInfo, error) { 34 | return nil, nil 35 | } 36 | 37 | func (t *testErrorTool) InvokableRun(ctx context.Context, argumentsInJSON string, opts ...tool.Option) (string, error) { 38 | return "", errors.New("test error") 39 | } 40 | 41 | func (t *testErrorTool) StreamableRun(ctx context.Context, argumentsInJSON string, opts ...tool.Option) (*schema.StreamReader[string], error) { 42 | return nil, errors.New("test stream error") 43 | } 44 | 45 | func TestErrorWrapper(t *testing.T) { 46 | ctx := context.Background() 47 | 48 | nt := WrapToolWithErrorHandler(&testErrorTool{}, func(_ context.Context, err error) string { 49 | return err.Error() 50 | }) 51 | result, err := nt.(tool.InvokableTool).InvokableRun(ctx, "") 52 | assert.NoError(t, err) 53 | assert.Equal(t, "test error", result) 54 | streamResult, err := nt.(tool.StreamableTool).StreamableRun(ctx, "") 55 | assert.NoError(t, err) 56 | chunk, err := streamResult.Recv() 57 | assert.NoError(t, err) 58 | assert.Equal(t, "test stream error", chunk) 59 | _, err = streamResult.Recv() 60 | assert.True(t, errors.Is(err, io.EOF)) 61 | 62 | wrappedTool := WrapInvokableToolWithErrorHandler(&testErrorTool{}, func(_ context.Context, err error) string { 63 | return err.Error() 64 | }) 65 | result, err = wrappedTool.InvokableRun(ctx, "") 66 | assert.NoError(t, err) 67 | assert.Equal(t, "test error", result) 68 | 69 | wrappedStreamTool := WrapStreamableToolWithErrorHandler(&testErrorTool{}, func(_ context.Context, err error) string { 70 | return err.Error() 71 | }) 72 | 73 | streamResult, err = wrappedStreamTool.StreamableRun(ctx, "") 74 | assert.NoError(t, err) 75 | 76 | chunk, err = streamResult.Recv() 77 | assert.NoError(t, err) 78 | assert.Equal(t, "test stream error", chunk) 79 | _, err = streamResult.Recv() 80 | assert.True(t, errors.Is(err, io.EOF)) 81 | } 82 | -------------------------------------------------------------------------------- /components/types.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | // components are the basic components supported by eino. 18 | package components 19 | 20 | // Typer get the type name of one component's implementation 21 | // if Typer exists, the full name of the component instance will be {Typer}{Component} by default 22 | // recommend using Camel Case Naming Style for Typer 23 | type Typer interface { 24 | GetType() string 25 | } 26 | 27 | func GetType(component any) (string, bool) { 28 | if typer, ok := component.(Typer); ok { 29 | return typer.GetType(), true 30 | } 31 | 32 | return "", false 33 | } 34 | 35 | // Checker tells callback aspect status of component's implementation 36 | // When the Checker interface is implemented and returns true, the framework will not start the default aspect. 37 | // Instead, the component will decide the callback execution location and the information to be injected. 38 | type Checker interface { 39 | IsCallbacksEnabled() bool 40 | } 41 | 42 | func IsCallbacksEnabled(i any) bool { 43 | if checker, ok := i.(Checker); ok { 44 | return checker.IsCallbacksEnabled() 45 | } 46 | 47 | return false 48 | } 49 | 50 | // Component the name of different kinds of components 51 | type Component string 52 | 53 | const ( 54 | ComponentOfPrompt Component = "ChatTemplate" 55 | ComponentOfChatModel Component = "ChatModel" 56 | ComponentOfEmbedding Component = "Embedding" 57 | ComponentOfIndexer Component = "Indexer" 58 | ComponentOfRetriever Component = "Retriever" 59 | ComponentOfLoader Component = "Loader" 60 | ComponentOfTransformer Component = "DocumentTransformer" 61 | ComponentOfTool Component = "Tool" 62 | ) 63 | -------------------------------------------------------------------------------- /compose/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package compose 18 | -------------------------------------------------------------------------------- /compose/graph_compile_options.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package compose 18 | 19 | type graphCompileOptions struct { 20 | maxRunSteps int 21 | graphName string 22 | nodeTriggerMode NodeTriggerMode // default to AnyPredecessor (pregel) 23 | 24 | callbacks []GraphCompileCallback 25 | 26 | origOpts []GraphCompileOption 27 | 28 | getStateEnabled bool 29 | 30 | checkPointStore CheckPointStore 31 | interruptBeforeNodes []string 32 | interruptAfterNodes []string 33 | } 34 | 35 | func newGraphCompileOptions(opts ...GraphCompileOption) *graphCompileOptions { 36 | option := &graphCompileOptions{} 37 | 38 | for _, o := range opts { 39 | o(option) 40 | } 41 | 42 | option.origOpts = opts 43 | 44 | return option 45 | } 46 | 47 | // GraphCompileOption options for compiling AnyGraph. 48 | type GraphCompileOption func(*graphCompileOptions) 49 | 50 | // WithMaxRunSteps sets the maximum number of steps that a graph can run. 51 | // This is useful to prevent infinite loops in graphs with cycles. 52 | // If the number of steps exceeds maxSteps, the graph execution will be terminated with an error. 53 | func WithMaxRunSteps(maxSteps int) GraphCompileOption { 54 | return func(o *graphCompileOptions) { 55 | o.maxRunSteps = maxSteps 56 | } 57 | } 58 | 59 | // WithGraphName sets a name for the graph. 60 | // The name is used for debugging and logging purposes. 61 | // If not set, a default name will be used. 62 | func WithGraphName(graphName string) GraphCompileOption { 63 | return func(o *graphCompileOptions) { 64 | o.graphName = graphName 65 | } 66 | } 67 | 68 | // WithNodeTriggerMode sets the trigger mode for nodes in the graph. 69 | // The trigger mode determines when a node is triggered during graph execution, ref: https://www.cloudwego.io/docs/eino/core_modules/chain_and_graph_orchestration/orchestration_design_principles/#internal-mechanism 70 | // AnyPredecessor by default. 71 | func WithNodeTriggerMode(triggerMode NodeTriggerMode) GraphCompileOption { 72 | return func(o *graphCompileOptions) { 73 | o.nodeTriggerMode = triggerMode 74 | } 75 | } 76 | 77 | // WithGraphCompileCallbacks sets callbacks for graph compilation. 78 | func WithGraphCompileCallbacks(cbs ...GraphCompileCallback) GraphCompileOption { 79 | return func(o *graphCompileOptions) { 80 | o.callbacks = append(o.callbacks, cbs...) 81 | } 82 | } 83 | 84 | // InitGraphCompileCallbacks set global graph compile callbacks, 85 | // which ONLY will be added to top level graph compile options 86 | func InitGraphCompileCallbacks(cbs []GraphCompileCallback) { 87 | globalGraphCompileCallbacks = cbs 88 | } 89 | 90 | var globalGraphCompileCallbacks []GraphCompileCallback 91 | -------------------------------------------------------------------------------- /compose/interrupt.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package compose 18 | 19 | import ( 20 | "errors" 21 | "fmt" 22 | ) 23 | 24 | func WithInterruptBeforeNodes(nodes []string) GraphCompileOption { 25 | return func(options *graphCompileOptions) { 26 | options.interruptBeforeNodes = nodes 27 | } 28 | } 29 | 30 | func WithInterruptAfterNodes(nodes []string) GraphCompileOption { 31 | return func(options *graphCompileOptions) { 32 | options.interruptAfterNodes = nodes 33 | } 34 | } 35 | 36 | type InterruptInfo struct { 37 | State any 38 | BeforeNodes []string 39 | AfterNodes []string 40 | RerunNodes []string 41 | SubGraphs map[string]*InterruptInfo 42 | } 43 | 44 | func ExtractInterruptInfo(err error) (info *InterruptInfo, existed bool) { 45 | if err == nil { 46 | return nil, false 47 | } 48 | var iE *interruptError 49 | if errors.As(err, &iE) { 50 | return iE.Info, true 51 | } 52 | return nil, false 53 | } 54 | 55 | type interruptError struct { 56 | Info *InterruptInfo 57 | } 58 | 59 | func (e *interruptError) Error() string { 60 | return fmt.Sprintf("interrupt happened, info: %+v", e.Info) 61 | } 62 | 63 | func isSubGraphInterrupt(err error) *subGraphInterruptError { 64 | if err == nil { 65 | return nil 66 | } 67 | var iE *subGraphInterruptError 68 | if errors.As(err, &iE) { 69 | return iE 70 | } 71 | return nil 72 | } 73 | 74 | type subGraphInterruptError struct { 75 | Info *InterruptInfo 76 | CheckPoint *checkpoint 77 | } 78 | 79 | func (e *subGraphInterruptError) Error() string { 80 | return fmt.Sprintf("interrupt happened, info: %+v", e.Info) 81 | } 82 | 83 | func isInterruptError(err error) bool { 84 | if _, ok := ExtractInterruptInfo(err); ok { 85 | return true 86 | } 87 | if info := isSubGraphInterrupt(err); info != nil { 88 | return true 89 | } 90 | if errors.Is(err, InterruptAndRerun) { 91 | return true 92 | } 93 | return false 94 | } 95 | -------------------------------------------------------------------------------- /compose/introspect.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package compose 18 | 19 | import ( 20 | "context" 21 | "reflect" 22 | 23 | "github.com/cloudwego/eino/components" 24 | ) 25 | 26 | // GraphNodeInfo the info which end users pass in when they are adding nodes to graph. 27 | type GraphNodeInfo struct { 28 | Component components.Component 29 | Instance any 30 | GraphAddNodeOpts []GraphAddNodeOpt 31 | InputType, OutputType reflect.Type // mainly for lambda, whose input and output types cannot be inferred by component type 32 | Name string 33 | InputKey, OutputKey string 34 | GraphInfo *GraphInfo 35 | Mappings []*FieldMapping 36 | } 37 | 38 | // GraphInfo the info which end users pass in when they are compiling a graph. 39 | // it is used in compile callback for user to get the node info and instance. 40 | // you may need all details info of the graph for observation. 41 | type GraphInfo struct { 42 | CompileOptions []GraphCompileOption 43 | Nodes map[string]GraphNodeInfo // node key -> node info 44 | Edges map[string][]string // edge start node key -> edge end node key, control edges 45 | DataEdges map[string][]string 46 | Branches map[string][]GraphBranch // branch start node key -> branch 47 | InputType, OutputType reflect.Type 48 | Name string 49 | 50 | NewGraphOptions []NewGraphOption 51 | GenStateFn func(context.Context) any 52 | } 53 | 54 | // GraphCompileCallback is the callback which will be called when graph compilation finishes. 55 | type GraphCompileCallback interface { // nolint: byted_s_interface_name 56 | OnFinish(ctx context.Context, info *GraphInfo) 57 | } 58 | -------------------------------------------------------------------------------- /compose/pregel.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package compose 18 | 19 | import "fmt" 20 | 21 | func pregelChannelBuilder(_ []string, _ []string, _ func() any, _ func() streamReader) channel { 22 | return &pregelChannel{Values: make(map[string]any)} 23 | } 24 | 25 | type pregelChannel struct { 26 | Values map[string]any 27 | } 28 | 29 | func (ch *pregelChannel) load(c channel) error { 30 | dc, ok := c.(*pregelChannel) 31 | if !ok { 32 | return fmt.Errorf("load pregel channel fail, got %T, want *pregelChannel", c) 33 | } 34 | ch.Values = dc.Values 35 | return nil 36 | } 37 | 38 | func (ch *pregelChannel) convertValues(fn func(map[string]any) error) error { 39 | return fn(ch.Values) 40 | } 41 | 42 | func (ch *pregelChannel) reportValues(ins map[string]any) error { 43 | for k, v := range ins { 44 | ch.Values[k] = v 45 | } 46 | return nil 47 | } 48 | 49 | func (ch *pregelChannel) get(_ bool) (any, bool, error) { 50 | if len(ch.Values) == 0 { 51 | return nil, false, nil 52 | } 53 | defer func() { ch.Values = map[string]any{} }() 54 | values := make([]any, 0, len(ch.Values)) 55 | for _, v := range ch.Values { 56 | values = append(values, v) 57 | } 58 | 59 | if len(values) == 1 { 60 | return values[0], true, nil 61 | } 62 | 63 | // merge 64 | v, err := mergeValues(values) 65 | if err != nil { 66 | return nil, false, err 67 | } 68 | return v, true, nil 69 | } 70 | 71 | func (ch *pregelChannel) reportSkip(_ []string) bool { 72 | return false 73 | } 74 | func (ch *pregelChannel) reportDependencies(_ []string) { 75 | return 76 | } 77 | -------------------------------------------------------------------------------- /compose/stream_concat.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package compose 18 | 19 | import ( 20 | "errors" 21 | "io" 22 | 23 | "github.com/cloudwego/eino/internal" 24 | "github.com/cloudwego/eino/schema" 25 | ) 26 | 27 | // RegisterStreamChunkConcatFunc registers a function to concat stream chunks. 28 | // It's required when you want to concat stream chunks of a specific type. 29 | // for example you call Invoke() but node only implements Stream(). 30 | // call at process init 31 | // not thread safe 32 | // nolint: byted_global_write_slicemap 33 | // eg. 34 | // 35 | // type testStruct struct { 36 | // field1 string 37 | // field2 int 38 | // } 39 | // compose.RegisterStreamChunkConcatFunc(func(items []testStruct) (testStruct, error) { 40 | // return testStruct{ 41 | // field1: items[1].field1, // may implement inplace logic by your scenario 42 | // field2: items[0].field2 + items[1].field2, 43 | // }, nil 44 | // }) 45 | func RegisterStreamChunkConcatFunc[T any](fn func([]T) (T, error)) { 46 | internal.RegisterStreamChunkConcatFunc(fn) 47 | } 48 | 49 | var emptyStreamConcatErr = errors.New("stream reader is empty, concat fail") 50 | 51 | func concatStreamReader[T any](sr *schema.StreamReader[T]) (T, error) { 52 | defer sr.Close() 53 | 54 | var items []T 55 | 56 | for { 57 | chunk, err := sr.Recv() 58 | if err != nil { 59 | if err == io.EOF { 60 | break 61 | } 62 | 63 | var t T 64 | return t, newStreamReadError(err) 65 | } 66 | 67 | items = append(items, chunk) 68 | } 69 | 70 | if len(items) == 0 { 71 | var t T 72 | return t, emptyStreamConcatErr 73 | } 74 | 75 | if len(items) == 1 { 76 | return items[0], nil 77 | } 78 | 79 | res, err := internal.ConcatItems(items) 80 | if err != nil { 81 | var t T 82 | return t, err 83 | } 84 | return res, nil 85 | } 86 | -------------------------------------------------------------------------------- /compose/stream_reader.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package compose 18 | 19 | import ( 20 | "reflect" 21 | 22 | "github.com/cloudwego/eino/internal/generic" 23 | "github.com/cloudwego/eino/schema" 24 | ) 25 | 26 | type streamReader interface { 27 | copy(n int) []streamReader 28 | getType() reflect.Type 29 | getChunkType() reflect.Type 30 | merge([]streamReader) streamReader 31 | withKey(string) streamReader 32 | close() 33 | toAnyStreamReader() *schema.StreamReader[any] 34 | } 35 | 36 | type streamReaderPacker[T any] struct { 37 | sr *schema.StreamReader[T] 38 | } 39 | 40 | func (srp streamReaderPacker[T]) close() { 41 | srp.sr.Close() 42 | } 43 | 44 | func (srp streamReaderPacker[T]) copy(n int) []streamReader { 45 | ret := make([]streamReader, n) 46 | srs := srp.sr.Copy(n) 47 | 48 | for i := 0; i < n; i++ { 49 | ret[i] = streamReaderPacker[T]{srs[i]} 50 | } 51 | 52 | return ret 53 | } 54 | 55 | func (srp streamReaderPacker[T]) getType() reflect.Type { 56 | return reflect.TypeOf(srp.sr) 57 | } 58 | 59 | func (srp streamReaderPacker[T]) getChunkType() reflect.Type { 60 | return generic.TypeOf[T]() 61 | } 62 | 63 | func (srp streamReaderPacker[T]) merge(isrs []streamReader) streamReader { 64 | srs := make([]*schema.StreamReader[T], len(isrs)+1) 65 | srs[0] = srp.sr 66 | for i := 1; i < len(srs); i++ { 67 | sr, ok := unpackStreamReader[T](isrs[i-1]) 68 | if !ok { 69 | return nil 70 | } 71 | 72 | srs[i] = sr 73 | } 74 | 75 | sr := schema.MergeStreamReaders(srs) 76 | 77 | return packStreamReader(sr) 78 | } 79 | 80 | func (srp streamReaderPacker[T]) withKey(key string) streamReader { 81 | convert := func(v T) (map[string]any, error) { 82 | return map[string]any{key: v}, nil 83 | } 84 | 85 | ret := schema.StreamReaderWithConvert[T, map[string]any](srp.sr, convert) 86 | 87 | return packStreamReader(ret) 88 | } 89 | 90 | func (srp streamReaderPacker[T]) toAnyStreamReader() *schema.StreamReader[any] { 91 | return schema.StreamReaderWithConvert(srp.sr, func(t T) (any, error) { 92 | return t, nil 93 | }) 94 | } 95 | 96 | func packStreamReader[T any](sr *schema.StreamReader[T]) streamReader { 97 | return streamReaderPacker[T]{sr} 98 | } 99 | 100 | func unpackStreamReader[T any](isr streamReader) (*schema.StreamReader[T], bool) { 101 | c, ok := isr.(streamReaderPacker[T]) 102 | if ok { 103 | return c.sr, true 104 | } 105 | 106 | typ := generic.TypeOf[T]() 107 | if typ.Kind() == reflect.Interface { 108 | return schema.StreamReaderWithConvert(isr.toAnyStreamReader(), func(t any) (T, error) { 109 | return t.(T), nil 110 | }), true 111 | } 112 | 113 | return nil, false 114 | } 115 | -------------------------------------------------------------------------------- /compose/stream_reader_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package compose 18 | 19 | import ( 20 | "io" 21 | "reflect" 22 | "testing" 23 | 24 | "github.com/cloudwego/eino/schema" 25 | "github.com/stretchr/testify/assert" 26 | ) 27 | 28 | func TestArrayStreamMerge(t *testing.T) { 29 | 30 | t.Run("unpack_to_equal_type", func(t *testing.T) { 31 | a1 := []int{1, 2, 3} 32 | a2 := []int{4, 5, 6} 33 | a3 := []int{7, 8, 9} 34 | s1 := schema.StreamReaderFromArray(a1) 35 | s2 := schema.StreamReaderFromArray(a2) 36 | s3 := schema.StreamReaderFromArray(a3) 37 | 38 | sp1 := streamReaderPacker[int]{sr: s1} 39 | sp2 := streamReaderPacker[int]{sr: s2} 40 | sp3 := streamReaderPacker[int]{sr: s3} 41 | 42 | sp := sp1.merge([]streamReader{sp2, sp3}) 43 | 44 | sr, ok := unpackStreamReader[int](sp) 45 | if !ok { 46 | t.Fatal("unexpected") 47 | } 48 | 49 | defer sr.Close() 50 | 51 | var result []int 52 | for { 53 | chunk, err := sr.Recv() 54 | if err == io.EOF { 55 | break 56 | } 57 | assert.Nil(t, err) 58 | result = append(result, chunk) 59 | } 60 | if !reflect.DeepEqual(result, append(append(a1, a2...), a3...)) { 61 | t.Fatalf("result: %v error", result) 62 | } 63 | }) 64 | 65 | t.Run("unpack_to_father_type", func(t *testing.T) { 66 | a1 := []*doctor{{say: "a"}, {say: "b"}, {say: "c"}} 67 | a2 := []*doctor{{say: "d"}, {say: "e"}, {say: "f"}} 68 | a3 := []*doctor{{say: "g"}, {say: "h"}, {say: "i"}} 69 | s1 := schema.StreamReaderFromArray(a1) 70 | s2 := schema.StreamReaderFromArray(a2) 71 | s3 := schema.StreamReaderFromArray(a3) 72 | 73 | sp1 := streamReaderPacker[*doctor]{sr: s1} 74 | sp2 := streamReaderPacker[*doctor]{sr: s2} 75 | sp3 := streamReaderPacker[*doctor]{sr: s3} 76 | 77 | sp := sp1.merge([]streamReader{sp2, sp3}) 78 | 79 | sr, ok := unpackStreamReader[person](sp) 80 | assert.True(t, ok) 81 | 82 | defer sr.Close() 83 | 84 | var result []person 85 | for { 86 | chunk, err := sr.Recv() 87 | if err == io.EOF { 88 | break 89 | } 90 | assert.Nil(t, err) 91 | result = append(result, chunk) 92 | } 93 | 94 | baseline := append(append(a1, a2...), a3...) 95 | 96 | assert.Len(t, result, len(baseline)) 97 | 98 | for idx := range result { 99 | assert.Equal(t, baseline[idx].say, result[idx].Say()) 100 | } 101 | }) 102 | } 103 | -------------------------------------------------------------------------------- /compose/types.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package compose 18 | 19 | import ( 20 | "github.com/cloudwego/eino/components" 21 | ) 22 | 23 | type component = components.Component 24 | 25 | // built-in component types in graph node. 26 | // it represents the type of the most primitive executable object provided by the user. 27 | const ( 28 | ComponentOfUnknown component = "Unknown" 29 | ComponentOfGraph component = "Graph" 30 | ComponentOfWorkflow component = "Workflow" 31 | ComponentOfChain component = "Chain" 32 | ComponentOfPassthrough component = "Passthrough" 33 | ComponentOfToolsNode component = "ToolsNode" 34 | ComponentOfLambda component = "Lambda" 35 | ) 36 | 37 | // NodeTriggerMode controls the triggering mode of graph nodes. 38 | type NodeTriggerMode string 39 | 40 | const ( 41 | // AnyPredecessor means that the node will be triggered when any of its predecessors is included in the previous completed super step. 42 | // Ref:https://www.cloudwego.io/docs/eino/core_modules/chain_and_graph_orchestration/orchestration_design_principles/#runtime-engine 43 | AnyPredecessor NodeTriggerMode = "any_predecessor" 44 | // AllPredecessor means that the current node will only be triggered when all of its predecessor nodes have finished running. 45 | AllPredecessor NodeTriggerMode = "all_predecessor" 46 | ) 47 | -------------------------------------------------------------------------------- /compose/types_composable.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package compose 18 | 19 | import ( 20 | "context" 21 | "reflect" 22 | ) 23 | 24 | // AnyGraph the identifiers for composable and compilable Graph[I, O]、Chain[I, O] in Eino. 25 | type AnyGraph interface { 26 | getGenericHelper() *genericHelper 27 | compile(ctx context.Context, options *graphCompileOptions) (*composableRunnable, error) 28 | inputType() reflect.Type 29 | outputType() reflect.Type 30 | component() component 31 | } 32 | -------------------------------------------------------------------------------- /compose/utils_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package compose 18 | 19 | import ( 20 | "testing" 21 | 22 | "github.com/stretchr/testify/assert" 23 | 24 | "github.com/cloudwego/eino/internal/generic" 25 | ) 26 | 27 | type good interface { 28 | ThisIsGood() bool 29 | } 30 | 31 | type good2 interface { 32 | ThisIsGood2() bool 33 | } 34 | 35 | type good3 interface { 36 | ThisIsGood() bool 37 | } 38 | 39 | type goodImpl struct{} 40 | 41 | func (g *goodImpl) ThisIsGood() bool { 42 | return true 43 | } 44 | 45 | type goodNotImpl struct{} 46 | 47 | func TestValidateType(t *testing.T) { 48 | 49 | t.Run("equal_type", func(t *testing.T) { 50 | arg := generic.TypeOf[int]() 51 | input := generic.TypeOf[int]() 52 | 53 | result := checkAssignable(input, arg) 54 | assert.Equal(t, assignableTypeMust, result) 55 | }) 56 | 57 | t.Run("unequal_type", func(t *testing.T) { 58 | arg := generic.TypeOf[int]() 59 | input := generic.TypeOf[string]() 60 | 61 | result := checkAssignable(input, arg) 62 | assert.Equal(t, assignableTypeMustNot, result) 63 | }) 64 | 65 | t.Run("implement_interface", func(t *testing.T) { 66 | arg := generic.TypeOf[good]() 67 | input := generic.TypeOf[*goodImpl]() 68 | 69 | result := checkAssignable(input, arg) 70 | assert.Equal(t, assignableTypeMust, result) 71 | }) 72 | 73 | t.Run("may_implement_interface", func(t *testing.T) { 74 | arg := generic.TypeOf[*goodImpl]() 75 | input := generic.TypeOf[good]() 76 | 77 | result := checkAssignable(input, arg) 78 | assert.Equal(t, assignableTypeMay, result) 79 | }) 80 | 81 | t.Run("not_implement_interface", func(t *testing.T) { 82 | arg := generic.TypeOf[good]() 83 | input := generic.TypeOf[*goodNotImpl]() 84 | 85 | result := checkAssignable(input, arg) 86 | assert.Equal(t, assignableTypeMustNot, result) 87 | }) 88 | 89 | t.Run("interface_unequal_interface", func(t *testing.T) { 90 | arg := generic.TypeOf[good]() 91 | input := generic.TypeOf[good2]() 92 | 93 | result := checkAssignable(input, arg) 94 | assert.Equal(t, assignableTypeMustNot, result) 95 | }) 96 | 97 | t.Run("interface_equal_interface", func(t *testing.T) { 98 | arg := generic.TypeOf[good]() 99 | input := generic.TypeOf[good3]() 100 | 101 | result := checkAssignable(input, arg) 102 | assert.Equal(t, assignableTypeMust, result) 103 | }) 104 | } 105 | 106 | func TestStreamChunkConvert(t *testing.T) { 107 | o, err := streamChunkConvertForCBOutput(1) 108 | assert.Nil(t, err) 109 | assert.Equal(t, o, 1) 110 | 111 | i, err := streamChunkConvertForCBInput(1) 112 | assert.Nil(t, err) 113 | assert.Equal(t, i, 1) 114 | } 115 | -------------------------------------------------------------------------------- /compose/values_merge.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package compose 18 | 19 | import ( 20 | "fmt" 21 | "reflect" 22 | 23 | "github.com/cloudwego/eino/internal" 24 | ) 25 | 26 | // RegisterValuesMergeFunc registers a function to merge outputs from multiple nodes when fan-in. 27 | // It's used to define how to merge for a specific type. 28 | // For maps that already have a default merge function, you don't need to register a new one unless you want to customize the merge logic. 29 | func RegisterValuesMergeFunc[T any](fn func([]T) (T, error)) { 30 | internal.RegisterValuesMergeFunc(fn) 31 | } 32 | 33 | // the caller should ensure len(vs) > 1 34 | func mergeValues(vs []any) (any, error) { 35 | v0 := reflect.ValueOf(vs[0]) 36 | t0 := v0.Type() 37 | 38 | if fn := internal.GetMergeFunc(t0); fn != nil { 39 | return fn(vs) 40 | } 41 | 42 | if s, ok := vs[0].(streamReader); ok { 43 | t := s.getChunkType() 44 | if internal.GetMergeFunc(t) == nil { 45 | return nil, fmt.Errorf("(mergeValues | stream type)"+ 46 | " unsupported chunk type: %v", t) 47 | } 48 | 49 | ss := make([]streamReader, len(vs)-1) 50 | for i := 0; i < len(ss); i++ { 51 | s_, ok_ := vs[i+1].(streamReader) 52 | if !ok_ { 53 | return nil, fmt.Errorf("(mergeStream) unexpected type. "+ 54 | "expect: %v, got: %v", t0, reflect.TypeOf(vs[i])) 55 | } 56 | 57 | if st := s_.getChunkType(); st != t { 58 | return nil, fmt.Errorf("(mergeStream) chunk type mismatch. "+ 59 | "expect: %v, got: %v", t, st) 60 | } 61 | 62 | ss[i] = s_ 63 | } 64 | 65 | ms := s.merge(ss) 66 | 67 | return ms, nil 68 | } 69 | 70 | return nil, fmt.Errorf("(mergeValues) unsupported type: %v", t0) 71 | } 72 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package eino 18 | -------------------------------------------------------------------------------- /flow/agent/agent_option.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package agent 18 | 19 | import "github.com/cloudwego/eino/compose" 20 | 21 | // AgentOption is the common option type for various agent and multi-agent implementations. 22 | // For options intended to use with underlying graph or components, use WithComposeOptions to specify. 23 | // For options intended to use with particular agent/multi-agent implementations, use WrapImplSpecificOptFn to specify. 24 | type AgentOption struct { 25 | implSpecificOptFn any 26 | composeOptions []compose.Option 27 | } 28 | 29 | // GetComposeOptions returns all compose options from the given agent options. 30 | func GetComposeOptions(opts ...AgentOption) []compose.Option { 31 | var result []compose.Option 32 | for _, opt := range opts { 33 | result = append(result, opt.composeOptions...) 34 | } 35 | 36 | return result 37 | } 38 | 39 | // WithComposeOptions returns an agent option that specifies compose options. 40 | func WithComposeOptions(opts ...compose.Option) AgentOption { 41 | return AgentOption{ 42 | composeOptions: opts, 43 | } 44 | } 45 | 46 | // WrapImplSpecificOptFn returns an agent option that specifies a function to modify the implementation-specific options. 47 | func WrapImplSpecificOptFn[T any](optFn func(*T)) AgentOption { 48 | return AgentOption{ 49 | implSpecificOptFn: optFn, 50 | } 51 | } 52 | 53 | // GetImplSpecificOptions returns the implementation-specific options from the given agent options. 54 | func GetImplSpecificOptions[T any](base *T, opts ...AgentOption) *T { 55 | if base == nil { 56 | base = new(T) 57 | } 58 | 59 | for i := range opts { 60 | opt := opts[i] 61 | if opt.implSpecificOptFn != nil { 62 | optFn, ok := opt.implSpecificOptFn.(func(*T)) 63 | if ok { 64 | optFn(base) 65 | } 66 | } 67 | } 68 | 69 | return base 70 | } 71 | -------------------------------------------------------------------------------- /flow/agent/multiagent/host/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package host 18 | -------------------------------------------------------------------------------- /flow/agent/multiagent/host/options.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package host 18 | 19 | import "github.com/cloudwego/eino/flow/agent" 20 | 21 | type options struct { 22 | agentCallbacks []MultiAgentCallback 23 | } 24 | 25 | func WithAgentCallbacks(agentCallbacks ...MultiAgentCallback) agent.AgentOption { 26 | return agent.WrapImplSpecificOptFn(func(opts *options) { 27 | opts.agentCallbacks = append(opts.agentCallbacks, agentCallbacks...) 28 | }) 29 | } 30 | -------------------------------------------------------------------------------- /flow/agent/react/callback.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package react 18 | 19 | import ( 20 | "github.com/cloudwego/eino/callbacks" 21 | template "github.com/cloudwego/eino/utils/callbacks" 22 | ) 23 | 24 | // BuildAgentCallback builds a callback handler for agent. 25 | // e.g. 26 | // 27 | // callback := BuildAgentCallback(modelHandler, toolHandler) 28 | // agent, err := react.NewAgent(ctx, &AgentConfig{}) 29 | // agent.Generate(ctx, input, agent.WithComposeOptions(compose.WithCallbacks(callback))) 30 | func BuildAgentCallback(modelHandler *template.ModelCallbackHandler, toolHandler *template.ToolCallbackHandler) callbacks.Handler { 31 | return template.NewHandlerHelper().ChatModel(modelHandler).Tool(toolHandler).Handler() 32 | } 33 | -------------------------------------------------------------------------------- /flow/agent/react/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package react 18 | -------------------------------------------------------------------------------- /flow/agent/utils.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package agent 18 | 19 | import ( 20 | "errors" 21 | 22 | "github.com/cloudwego/eino/components/model" 23 | "github.com/cloudwego/eino/schema" 24 | ) 25 | 26 | func ChatModelWithTools(model_ model.ChatModel, toolCallingModel model.ToolCallingChatModel, 27 | toolInfos []*schema.ToolInfo) (model.BaseChatModel, error) { 28 | 29 | if toolCallingModel != nil { 30 | return toolCallingModel.WithTools(toolInfos) 31 | } 32 | 33 | if model_ != nil { 34 | err := model_.BindTools(toolInfos) 35 | if err != nil { 36 | return nil, err 37 | } 38 | 39 | return model_, nil 40 | } 41 | 42 | return nil, errors.New("no chat model provided") 43 | } 44 | -------------------------------------------------------------------------------- /flow/indexer/parent/parent_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package parent 18 | 19 | import ( 20 | "context" 21 | "fmt" 22 | "reflect" 23 | "strconv" 24 | "strings" 25 | "testing" 26 | 27 | "github.com/cloudwego/eino/components/document" 28 | "github.com/cloudwego/eino/components/indexer" 29 | "github.com/cloudwego/eino/schema" 30 | ) 31 | 32 | type testIndexer struct{} 33 | 34 | func (t *testIndexer) Store(ctx context.Context, docs []*schema.Document, opts ...indexer.Option) (ids []string, err error) { 35 | ret := make([]string, len(docs)) 36 | for i, d := range docs { 37 | ret[i] = d.ID 38 | if !strings.HasPrefix(d.ID, d.MetaData["parent"].(string)) { 39 | return nil, fmt.Errorf("invalid parent key") 40 | } 41 | } 42 | return ret, nil 43 | } 44 | 45 | type testTransformer struct { 46 | } 47 | 48 | func (t *testTransformer) Transform(ctx context.Context, src []*schema.Document, opts ...document.TransformerOption) ([]*schema.Document, error) { 49 | var ret []*schema.Document 50 | for _, d := range src { 51 | ret = append(ret, &schema.Document{ 52 | ID: d.ID, 53 | Content: d.Content[:len(d.Content)/2], 54 | MetaData: deepCopyMap(d.MetaData), 55 | }, &schema.Document{ 56 | ID: d.ID, 57 | Content: d.Content[len(d.Content)/2:], 58 | MetaData: deepCopyMap(d.MetaData), 59 | }) 60 | } 61 | return ret, nil 62 | } 63 | 64 | func TestParentIndexer(t *testing.T) { 65 | tests := []struct { 66 | name string 67 | config *Config 68 | input []*schema.Document 69 | want []string 70 | }{ 71 | { 72 | name: "success", 73 | config: &Config{ 74 | Indexer: &testIndexer{}, 75 | Transformer: &testTransformer{}, 76 | ParentIDKey: "parent", 77 | SubIDGenerator: func(ctx context.Context, parentID string, num int) ([]string, error) { 78 | ret := make([]string, num) 79 | for i := range ret { 80 | ret[i] = parentID + strconv.Itoa(i) 81 | } 82 | return ret, nil 83 | }, 84 | }, 85 | input: []*schema.Document{{ 86 | ID: "id", 87 | Content: "1234567890", 88 | MetaData: map[string]interface{}{}, 89 | }, { 90 | ID: "ID", 91 | Content: "0987654321", 92 | MetaData: map[string]interface{}{}, 93 | }}, 94 | want: []string{"id0", "id1", "ID0", "ID1"}, 95 | }, 96 | } 97 | ctx := context.Background() 98 | for _, tt := range tests { 99 | t.Run(tt.name, func(t *testing.T) { 100 | index, err := NewIndexer(ctx, tt.config) 101 | if err != nil { 102 | t.Fatal(err) 103 | } 104 | ret, err := index.Store(ctx, tt.input) 105 | if err != nil { 106 | t.Fatal(err) 107 | } 108 | if !reflect.DeepEqual(ret, tt.want) { 109 | t.Errorf("NewHeaderSplitter() got = %v, want %v", ret, tt.want) 110 | } 111 | }) 112 | } 113 | } 114 | 115 | func deepCopyMap(in map[string]interface{}) map[string]interface{} { 116 | out := make(map[string]interface{}) 117 | for k, v := range in { 118 | out[k] = v 119 | } 120 | return out 121 | } 122 | -------------------------------------------------------------------------------- /flow/retriever/multiquery/multi_query_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package multiquery 18 | 19 | import ( 20 | "context" 21 | "strings" 22 | "testing" 23 | 24 | "github.com/cloudwego/eino/components/model" 25 | "github.com/cloudwego/eino/components/retriever" 26 | "github.com/cloudwego/eino/compose" 27 | "github.com/cloudwego/eino/schema" 28 | ) 29 | 30 | type mockRetriever struct { 31 | } 32 | 33 | func (m *mockRetriever) Retrieve(ctx context.Context, query string, opts ...retriever.Option) ([]*schema.Document, error) { 34 | var ret []*schema.Document 35 | if strings.Contains(query, "1") { 36 | ret = append(ret, &schema.Document{ID: "1"}) 37 | } 38 | if strings.Contains(query, "2") { 39 | ret = append(ret, &schema.Document{ID: "2"}) 40 | } 41 | if strings.Contains(query, "3") { 42 | ret = append(ret, &schema.Document{ID: "3"}) 43 | } 44 | if strings.Contains(query, "4") { 45 | ret = append(ret, &schema.Document{ID: "4"}) 46 | } 47 | if strings.Contains(query, "5") { 48 | ret = append(ret, &schema.Document{ID: "5"}) 49 | } 50 | return ret, nil 51 | } 52 | 53 | type mockModel struct { 54 | } 55 | 56 | func (m *mockModel) Generate(ctx context.Context, input []*schema.Message, opts ...model.Option) (*schema.Message, error) { 57 | return &schema.Message{ 58 | Content: "12\n23\n34\n14\n23\n45", 59 | }, nil 60 | } 61 | 62 | func (m *mockModel) Stream(ctx context.Context, input []*schema.Message, opts ...model.Option) (*schema.StreamReader[*schema.Message], error) { 63 | panic("implement me") 64 | } 65 | 66 | func (m *mockModel) BindTools(tools []*schema.ToolInfo) error { 67 | panic("implement me") 68 | } 69 | 70 | func TestMultiQueryRetriever(t *testing.T) { 71 | ctx := context.Background() 72 | 73 | // use default llm 74 | mqr, err := NewRetriever(ctx, &Config{ 75 | RewriteLLM: &mockModel{}, 76 | OrigRetriever: &mockRetriever{}, 77 | }) 78 | if err != nil { 79 | t.Fatal(err) 80 | } 81 | c := compose.NewChain[string, []*schema.Document]() 82 | cr, err := c.AppendRetriever(mqr).Compile(ctx) 83 | if err != nil { 84 | t.Fatal(err) 85 | } 86 | 87 | result, err := cr.Invoke(ctx, "query") 88 | if err != nil { 89 | t.Fatal(err) 90 | } 91 | if len(result) != 4 { 92 | t.Fatal("default llm retrieve result is unexpected") 93 | } 94 | 95 | // use custom 96 | mqr, err = NewRetriever(ctx, &Config{ 97 | RewriteHandler: func(ctx context.Context, query string) ([]string, error) { 98 | return []string{"1", "3", "5"}, nil 99 | }, 100 | OrigRetriever: &mockRetriever{}, 101 | }) 102 | if err != nil { 103 | t.Fatal(err) 104 | } 105 | c = compose.NewChain[string, []*schema.Document]() 106 | cr, err = c.AppendRetriever(mqr).Compile(ctx) 107 | if err != nil { 108 | t.Fatal(err) 109 | } 110 | 111 | result, err = cr.Invoke(ctx, "query") 112 | if err != nil { 113 | t.Fatal(err) 114 | } 115 | if len(result) != 3 { 116 | t.Fatal("default llm retrieve result is unexpected") 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /flow/retriever/parent/parent_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package parent 18 | 19 | import ( 20 | "context" 21 | "reflect" 22 | "testing" 23 | 24 | "github.com/cloudwego/eino/components/retriever" 25 | "github.com/cloudwego/eino/schema" 26 | ) 27 | 28 | type testRetriever struct{} 29 | 30 | func (t *testRetriever) Retrieve(ctx context.Context, query string, opts ...retriever.Option) ([]*schema.Document, error) { 31 | ret := make([]*schema.Document, 0) 32 | for i := range query { 33 | ret = append(ret, &schema.Document{ 34 | ID: "", 35 | Content: "", 36 | MetaData: map[string]interface{}{ 37 | "parent": query[i : i+1], 38 | }, 39 | }) 40 | } 41 | return ret, nil 42 | } 43 | 44 | func TestParentRetriever(t *testing.T) { 45 | tests := []struct { 46 | name string 47 | config *Config 48 | input string 49 | want []*schema.Document 50 | }{ 51 | { 52 | name: "success", 53 | config: &Config{ 54 | Retriever: &testRetriever{}, 55 | ParentIDKey: "parent", 56 | OrigDocGetter: func(ctx context.Context, ids []string) ([]*schema.Document, error) { 57 | var ret []*schema.Document 58 | for i := range ids { 59 | ret = append(ret, &schema.Document{ID: ids[i]}) 60 | } 61 | return ret, nil 62 | }, 63 | }, 64 | input: "123233", 65 | want: []*schema.Document{ 66 | {ID: "1"}, 67 | {ID: "2"}, 68 | {ID: "3"}, 69 | }, 70 | }, 71 | } 72 | ctx := context.Background() 73 | for _, tt := range tests { 74 | t.Run(tt.name, func(t *testing.T) { 75 | r, err := NewRetriever(ctx, tt.config) 76 | if err != nil { 77 | t.Fatal(err) 78 | } 79 | ret, err := r.Retrieve(ctx, tt.input) 80 | if err != nil { 81 | t.Fatal(err) 82 | } 83 | if !reflect.DeepEqual(ret, tt.want) { 84 | t.Errorf("got %v, want %v", ret, tt.want) 85 | } 86 | }) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /flow/retriever/utils/utils.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package utils 18 | 19 | import ( 20 | "context" 21 | "fmt" 22 | "sync" 23 | 24 | "github.com/cloudwego/eino/callbacks" 25 | "github.com/cloudwego/eino/components" 26 | "github.com/cloudwego/eino/components/retriever" 27 | "github.com/cloudwego/eino/schema" 28 | ) 29 | 30 | // RetrieveTask is a task for retrieving documents. 31 | type RetrieveTask struct { 32 | Name string 33 | Retriever retriever.Retriever 34 | Query string 35 | RetrieveOptions []retriever.Option 36 | Result []*schema.Document 37 | Err error 38 | } 39 | 40 | // ConcurrentRetrieveWithCallback concurrently retrieves documents with callback. 41 | func ConcurrentRetrieveWithCallback(ctx context.Context, tasks []*RetrieveTask) { 42 | wg := sync.WaitGroup{} 43 | for i := range tasks { 44 | wg.Add(1) 45 | go func(ctx context.Context, t *RetrieveTask) { 46 | ctx = ctxWithRetrieverRunInfo(ctx, t.Retriever) 47 | 48 | defer func() { 49 | if e := recover(); e != nil { 50 | t.Err = fmt.Errorf("retrieve panic, query: %s, error: %v", t.Query, e) 51 | ctx = callbacks.OnError(ctx, t.Err) 52 | } 53 | wg.Done() 54 | }() 55 | 56 | ctx = callbacks.OnStart(ctx, t.Query) 57 | docs, err := t.Retriever.Retrieve(ctx, t.Query, t.RetrieveOptions...) 58 | if err != nil { 59 | callbacks.OnError(ctx, err) 60 | t.Err = err 61 | return 62 | } 63 | 64 | callbacks.OnEnd(ctx, docs) 65 | t.Result = docs 66 | }(ctx, tasks[i]) 67 | } 68 | wg.Wait() 69 | } 70 | 71 | func ctxWithRetrieverRunInfo(ctx context.Context, r retriever.Retriever) context.Context { 72 | runInfo := &callbacks.RunInfo{ 73 | Component: components.ComponentOfRetriever, 74 | } 75 | 76 | if typ, okk := components.GetType(r); okk { 77 | runInfo.Type = typ 78 | } 79 | 80 | runInfo.Name = runInfo.Type + string(runInfo.Component) 81 | 82 | return callbacks.ReuseHandlers(ctx, runInfo) 83 | } 84 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/cloudwego/eino 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/bytedance/sonic v1.13.2 7 | github.com/getkin/kin-openapi v0.118.0 8 | github.com/nikolalohinski/gonja v1.5.3 9 | github.com/slongfield/pyfmt v0.0.0-20220222012616-ea85ff4c361f 10 | github.com/smartystreets/goconvey v1.8.1 11 | github.com/stretchr/testify v1.9.0 12 | go.uber.org/mock v0.4.0 13 | ) 14 | 15 | require ( 16 | github.com/bytedance/sonic/loader v0.2.4 // indirect 17 | github.com/cloudwego/base64x v0.1.5 // indirect 18 | github.com/davecgh/go-spew v1.1.1 // indirect 19 | github.com/dustin/go-humanize v1.0.1 // indirect 20 | github.com/go-openapi/jsonpointer v0.19.5 // indirect 21 | github.com/go-openapi/swag v0.19.5 // indirect 22 | github.com/goph/emperror v0.17.2 // indirect 23 | github.com/gopherjs/gopherjs v1.17.2 // indirect 24 | github.com/invopop/yaml v0.1.0 // indirect 25 | github.com/josharian/intern v1.0.0 // indirect 26 | github.com/json-iterator/go v1.1.12 // indirect 27 | github.com/jtolds/gls v4.20.0+incompatible // indirect 28 | github.com/klauspost/cpuid/v2 v2.0.9 // indirect 29 | github.com/mailru/easyjson v0.7.7 // indirect 30 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 31 | github.com/modern-go/reflect2 v1.0.2 // indirect 32 | github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect 33 | github.com/pelletier/go-toml/v2 v2.0.9 // indirect 34 | github.com/perimeterx/marshmallow v1.1.4 // indirect 35 | github.com/pkg/errors v0.9.1 // indirect 36 | github.com/pmezard/go-difflib v1.0.0 // indirect 37 | github.com/sirupsen/logrus v1.9.3 // indirect 38 | github.com/smarty/assertions v1.15.0 // indirect 39 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect 40 | github.com/yargevad/filepathx v1.0.0 // indirect 41 | golang.org/x/arch v0.11.0 // indirect 42 | golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 // indirect 43 | golang.org/x/sys v0.26.0 // indirect 44 | gopkg.in/yaml.v2 v2.4.0 // indirect 45 | gopkg.in/yaml.v3 v3.0.1 // indirect 46 | ) 47 | -------------------------------------------------------------------------------- /internal/callbacks/interface.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package callbacks 18 | 19 | import ( 20 | "context" 21 | 22 | "github.com/cloudwego/eino/components" 23 | "github.com/cloudwego/eino/schema" 24 | ) 25 | 26 | type RunInfo struct { 27 | Name string 28 | Type string 29 | Component components.Component 30 | } 31 | 32 | type CallbackInput any 33 | 34 | type CallbackOutput any 35 | 36 | type Handler interface { 37 | OnStart(ctx context.Context, info *RunInfo, input CallbackInput) context.Context 38 | OnEnd(ctx context.Context, info *RunInfo, output CallbackOutput) context.Context 39 | 40 | OnError(ctx context.Context, info *RunInfo, err error) context.Context 41 | 42 | OnStartWithStreamInput(ctx context.Context, info *RunInfo, 43 | input *schema.StreamReader[CallbackInput]) context.Context 44 | OnEndWithStreamOutput(ctx context.Context, info *RunInfo, 45 | output *schema.StreamReader[CallbackOutput]) context.Context 46 | } 47 | 48 | type CallbackTiming uint8 49 | 50 | type TimingChecker interface { 51 | Needed(ctx context.Context, info *RunInfo, timing CallbackTiming) bool 52 | } 53 | -------------------------------------------------------------------------------- /internal/callbacks/manager.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package callbacks 18 | 19 | import "context" 20 | 21 | type manager struct { 22 | globalHandlers []Handler 23 | handlers []Handler 24 | runInfo *RunInfo 25 | } 26 | 27 | var GlobalHandlers []Handler 28 | 29 | func newManager(runInfo *RunInfo, handlers ...Handler) (*manager, bool) { 30 | if len(handlers)+len(GlobalHandlers) == 0 { 31 | return nil, false 32 | } 33 | 34 | hs := make([]Handler, len(GlobalHandlers)) 35 | copy(hs, GlobalHandlers) 36 | 37 | return &manager{ 38 | globalHandlers: hs, 39 | handlers: handlers, 40 | runInfo: runInfo, 41 | }, true 42 | } 43 | 44 | func ctxWithManager(ctx context.Context, manager *manager) context.Context { 45 | return context.WithValue(ctx, CtxManagerKey{}, manager) 46 | } 47 | 48 | func (m *manager) withRunInfo(runInfo *RunInfo) *manager { 49 | if m == nil { 50 | return nil 51 | } 52 | 53 | return &manager{ 54 | globalHandlers: m.globalHandlers, 55 | handlers: m.handlers, 56 | runInfo: runInfo, 57 | } 58 | } 59 | 60 | func managerFromCtx(ctx context.Context) (*manager, bool) { 61 | v := ctx.Value(CtxManagerKey{}) 62 | m, ok := v.(*manager) 63 | if ok && m != nil { 64 | return &manager{ 65 | globalHandlers: m.globalHandlers, 66 | handlers: m.handlers, 67 | runInfo: m.runInfo, 68 | }, true 69 | } 70 | 71 | return nil, false 72 | } 73 | -------------------------------------------------------------------------------- /internal/channel.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package internal 18 | 19 | import "sync" 20 | 21 | // UnboundedChan represents a channel with unlimited capacity 22 | type UnboundedChan[T any] struct { 23 | buffer []T // Internal buffer to store data 24 | mutex sync.Mutex // Mutex to protect buffer access 25 | notEmpty *sync.Cond // Condition variable to wait for data 26 | closed bool // Indicates if the channel has been closed 27 | } 28 | 29 | // NewUnboundedChan initializes and returns an UnboundedChan 30 | func NewUnboundedChan[T any]() *UnboundedChan[T] { 31 | ch := &UnboundedChan[T]{} 32 | ch.notEmpty = sync.NewCond(&ch.mutex) 33 | return ch 34 | } 35 | 36 | // Send puts an item into the channel 37 | func (ch *UnboundedChan[T]) Send(value T) { 38 | ch.mutex.Lock() 39 | defer ch.mutex.Unlock() 40 | 41 | if ch.closed { 42 | panic("send on closed channel") 43 | } 44 | 45 | ch.buffer = append(ch.buffer, value) 46 | ch.notEmpty.Signal() // Wake up one goroutine waiting to receive 47 | } 48 | 49 | // Receive gets an item from the channel (blocks if empty) 50 | func (ch *UnboundedChan[T]) Receive() (T, bool) { 51 | ch.mutex.Lock() 52 | defer ch.mutex.Unlock() 53 | 54 | for len(ch.buffer) == 0 && !ch.closed { 55 | ch.notEmpty.Wait() // Wait until data is available 56 | } 57 | 58 | if len(ch.buffer) == 0 { 59 | // Channel is closed and empty 60 | var zero T 61 | return zero, false 62 | } 63 | 64 | val := ch.buffer[0] 65 | ch.buffer = ch.buffer[1:] 66 | return val, true 67 | } 68 | 69 | // Close marks the channel as closed 70 | func (ch *UnboundedChan[T]) Close() { 71 | ch.mutex.Lock() 72 | defer ch.mutex.Unlock() 73 | 74 | if !ch.closed { 75 | ch.closed = true 76 | ch.notEmpty.Broadcast() // Wake up all waiting goroutines 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /internal/generic/generic.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package generic 18 | 19 | import ( 20 | "reflect" 21 | ) 22 | 23 | // NewInstance create an instance of the given type T. 24 | // the main purpose of this function is to create an instance of a type, can handle the type of T is a pointer or not. 25 | // eg. NewInstance[int] returns 0. 26 | // eg. NewInstance[*int] returns *0 (will be ptr of 0, not nil!). 27 | func NewInstance[T any]() T { 28 | typ := TypeOf[T]() 29 | 30 | switch typ.Kind() { 31 | case reflect.Map: 32 | return reflect.MakeMap(typ).Interface().(T) 33 | case reflect.Slice, reflect.Array: 34 | return reflect.MakeSlice(typ, 0, 0).Interface().(T) 35 | case reflect.Ptr: 36 | typ = typ.Elem() 37 | origin := reflect.New(typ) 38 | inst := origin 39 | 40 | for typ.Kind() == reflect.Ptr { 41 | typ = typ.Elem() 42 | inst = inst.Elem() 43 | inst.Set(reflect.New(typ)) 44 | } 45 | 46 | return origin.Interface().(T) 47 | default: 48 | var t T 49 | return t 50 | } 51 | } 52 | 53 | // TypeOf returns the type of T. 54 | // eg. TypeOf[int] returns reflect.TypeOf(int). 55 | // eg. TypeOf[*int] returns reflect.TypeOf(*int). 56 | func TypeOf[T any]() reflect.Type { 57 | return reflect.TypeOf((*T)(nil)).Elem() 58 | } 59 | 60 | // PtrOf returns a pointer of T. 61 | // useful when you want to get a pointer of a value, in some config, for example. 62 | // eg. PtrOf[int] returns *int. 63 | // eg. PtrOf[*int] returns **int. 64 | func PtrOf[T any](v T) *T { 65 | return &v 66 | } 67 | 68 | type Pair[F, S any] struct { 69 | First F 70 | Second S 71 | } 72 | 73 | // Reverse returns a new slice with elements in reversed order. 74 | func Reverse[S ~[]E, E any](s S) S { 75 | d := make(S, len(s)) 76 | for i := 0; i < len(s); i++ { 77 | d[i] = s[len(s)-i-1] 78 | } 79 | 80 | return d 81 | } 82 | -------------------------------------------------------------------------------- /internal/generic/generic_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package generic 18 | 19 | import ( 20 | "testing" 21 | 22 | "github.com/stretchr/testify/assert" 23 | ) 24 | 25 | func TestNewInstance(t *testing.T) { 26 | t.Run("struct", func(t *testing.T) { 27 | type Test struct{} 28 | 29 | inst := NewInstance[Test]() 30 | 31 | assert.IsType(t, Test{}, inst) 32 | }) 33 | 34 | t.Run("pointer", func(t *testing.T) { 35 | type Test struct{} 36 | 37 | inst := NewInstance[*Test]() 38 | 39 | assert.IsType(t, &Test{}, inst) 40 | }) 41 | 42 | t.Run("interface", func(t *testing.T) { 43 | type Test interface{} 44 | 45 | inst := NewInstance[Test]() 46 | assert.IsType(t, Test(nil), inst) 47 | }) 48 | 49 | t.Run("pointer of pointer of pointer", func(t *testing.T) { 50 | type Test struct { 51 | Value int 52 | } 53 | 54 | inst := NewInstance[***Test]() 55 | 56 | ptr := &Test{} 57 | ptrOfPtr := &ptr 58 | assert.NotNil(t, inst) 59 | assert.NotNil(t, *inst) 60 | assert.IsType(t, ptrOfPtr, *inst) 61 | assert.NotNil(t, **inst) 62 | assert.Equal(t, Test{Value: 0}, ***inst) 63 | }) 64 | 65 | t.Run("primitive_map", func(t *testing.T) { 66 | inst := NewInstance[map[string]any]() 67 | assert.NotNil(t, inst) 68 | inst["a"] = 1 69 | assert.Equal(t, map[string]any{"a": 1}, inst) 70 | }) 71 | 72 | t.Run("primitive_slice", func(t *testing.T) { 73 | inst := NewInstance[[]int]() 74 | assert.NotNil(t, inst) 75 | inst = append(inst, 1) 76 | assert.Equal(t, []int{1}, inst) 77 | }) 78 | 79 | t.Run("primitive_string", func(t *testing.T) { 80 | inst := NewInstance[string]() 81 | assert.Equal(t, "", inst) 82 | }) 83 | 84 | t.Run("primitive_int64", func(t *testing.T) { 85 | inst := NewInstance[int64]() 86 | assert.Equal(t, int64(0), inst) 87 | }) 88 | } 89 | 90 | func TestReverse(t *testing.T) { 91 | t.Run("reverse int slice", func(t *testing.T) { 92 | input := []int{1, 2, 3, 4, 5} 93 | expected := []int{5, 4, 3, 2, 1} 94 | result := Reverse(input) 95 | assert.Equal(t, expected, result) 96 | }) 97 | 98 | t.Run("reverse string slice", func(t *testing.T) { 99 | input := []string{"a", "b", "c"} 100 | expected := []string{"c", "b", "a"} 101 | result := Reverse(input) 102 | assert.Equal(t, expected, result) 103 | }) 104 | } 105 | -------------------------------------------------------------------------------- /internal/generic/type_name.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package generic 18 | 19 | import ( 20 | "reflect" 21 | "regexp" 22 | "runtime" 23 | "strings" 24 | ) 25 | 26 | var ( 27 | regOfAnonymousFunc = regexp.MustCompile(`^func[0-9]+`) 28 | regOfNumber = regexp.MustCompile(`^\d+$`) 29 | ) 30 | 31 | // ParseTypeName returns the name of the type of the given value. 32 | // It takes a reflect.Value as input and processes it to determine the underlying type. If the type is a pointer, it dereferences it to get the actual type. (the optimization of this function) 33 | // eg: ParseTypeName(reflect.ValueOf(&&myStruct{})) returns "myStruct" (not "**myStruct") 34 | // 35 | // If the type is a function, it retrieves the function's name, handling both named and anonymous functions. 36 | // examples of function paths: [package_path].[receiver_type].[func_name] 37 | // named function: xxx/utils.ParseTypeName 38 | // method: xxx/utils.(*MyStruct).Method 39 | // anonymous function: xxx/utils.TestParseTypeName.func6.1 40 | func ParseTypeName(val reflect.Value) string { 41 | typ := val.Type() 42 | 43 | for typ.Kind() == reflect.Pointer { 44 | typ = typ.Elem() 45 | } 46 | 47 | if typ.Kind() == reflect.Func { 48 | funcName := runtime.FuncForPC(val.Pointer()).Name() 49 | idx := strings.LastIndex(funcName, ".") 50 | if idx < 0 { 51 | if funcName != "" { 52 | return funcName 53 | } 54 | return "" 55 | } 56 | 57 | name := funcName[idx+1:] 58 | 59 | if regOfAnonymousFunc.MatchString(name) { 60 | return "" 61 | } 62 | 63 | if regOfNumber.MatchString(name) { 64 | return "" 65 | } 66 | 67 | return name 68 | } 69 | 70 | return typ.Name() 71 | } 72 | -------------------------------------------------------------------------------- /internal/generic/type_name_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package generic 18 | 19 | import ( 20 | "reflect" 21 | "testing" 22 | 23 | "github.com/stretchr/testify/assert" 24 | ) 25 | 26 | func TestParseTypeName(t *testing.T) { 27 | t.Run("named_struct", func(t *testing.T) { 28 | type OpenAI struct{} 29 | model := &OpenAI{} 30 | name := ParseTypeName(reflect.Indirect(reflect.ValueOf(model))) 31 | assert.Equal(t, "OpenAI", name) 32 | }) 33 | 34 | t.Run("anonymous_struct", func(t *testing.T) { 35 | model := &struct{}{} 36 | name := ParseTypeName(reflect.ValueOf(model)) 37 | assert.Equal(t, "", name) 38 | }) 39 | 40 | t.Run("anonymous_struct_from_func", func(t *testing.T) { 41 | model := genStruct() 42 | name := ParseTypeName(reflect.ValueOf(model)) 43 | assert.Equal(t, "", name) 44 | }) 45 | 46 | t.Run("named_interface", func(t *testing.T) { 47 | type OpenAI interface{} 48 | model := OpenAI(&struct{}{}) 49 | name := ParseTypeName(reflect.ValueOf(model)) 50 | assert.Equal(t, "", name) 51 | 52 | name = ParseTypeName(reflect.ValueOf((*OpenAI)(nil))) 53 | assert.Equal(t, "OpenAI", name) 54 | }) 55 | 56 | t.Run("named_function", func(t *testing.T) { 57 | f := genOpenAI 58 | name := ParseTypeName(reflect.ValueOf(f)) 59 | assert.Equal(t, "genOpenAI", name) 60 | }) 61 | 62 | t.Run("anonymous_function", func(t *testing.T) { 63 | f := genAnonymousFunc() 64 | name := ParseTypeName(reflect.ValueOf(f)) 65 | assert.Equal(t, "", name) 66 | 67 | ff := func(n string) { 68 | _ = n 69 | } 70 | 71 | name = ParseTypeName(reflect.ValueOf(ff)) 72 | assert.Equal(t, "", name) 73 | }) 74 | } 75 | 76 | func genStruct() *struct{} { 77 | return &struct{}{} 78 | } 79 | 80 | func genOpenAI() {} 81 | 82 | func genAnonymousFunc() func(n string) { 83 | return func(n string) { 84 | _ = n 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /internal/gmap/gmap.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package gmap 18 | 19 | // Concat returns the unions of maps as a new map. 20 | // 21 | // 💡 NOTE: 22 | // 23 | // - Once the key conflicts, the newer value always replace the older one ([DiscardOld]), 24 | // - If the result is an empty set, always return an empty map instead of nil 25 | // 26 | // 🚀 EXAMPLE: 27 | // 28 | // m := map[int]int{1: 1, 2: 2} 29 | // Concat(m, nil) ⏩ map[int]int{1: 1, 2: 2} 30 | // Concat(m, map[int]{3: 3}) ⏩ map[int]int{1: 1, 2: 2, 3: 3} 31 | // Concat(m, map[int]{2: -1}) ⏩ map[int]int{1: 1, 2: -1} // "2:2" is replaced by the newer "2:-1" 32 | // 33 | // 💡 AKA: Merge, Union, Combine 34 | func Concat[K comparable, V any](ms ...map[K]V) map[K]V { 35 | // FastPath: no map or only one map given. 36 | if len(ms) == 0 { 37 | return make(map[K]V) 38 | } 39 | if len(ms) == 1 { 40 | return cloneWithoutNilCheck(ms[0]) 41 | } 42 | 43 | var maxLen int 44 | for _, m := range ms { 45 | if len(m) > maxLen { 46 | maxLen = len(m) 47 | } 48 | } 49 | ret := make(map[K]V, maxLen) 50 | // FastPath: all maps are empty. 51 | if maxLen == 0 { 52 | return ret 53 | } 54 | 55 | // Concat all maps. 56 | for _, m := range ms { 57 | for k, v := range m { 58 | ret[k] = v 59 | } 60 | } 61 | return ret 62 | } 63 | 64 | // Map applies function f to each key and value of map m. 65 | // Results of f are returned as a new map. 66 | // 67 | // 🚀 EXAMPLE: 68 | // 69 | // f := func(k, v int) (string, string) { return strconv.Itoa(k), strconv.Itoa(v) } 70 | // Map(map[int]int{1: 1}, f) ⏩ map[string]string{"1": "1"} 71 | // Map(map[int]int{}, f) ⏩ map[string]string{} 72 | func Map[K1, K2 comparable, V1, V2 any](m map[K1]V1, f func(K1, V1) (K2, V2)) map[K2]V2 { 73 | r := make(map[K2]V2, len(m)) 74 | for k, v := range m { 75 | k2, v2 := f(k, v) 76 | r[k2] = v2 77 | } 78 | return r 79 | } 80 | 81 | // Values returns the values of the map m. 82 | // 83 | // 🚀 EXAMPLE: 84 | // 85 | // m := map[int]string{1: "1", 2: "2", 3: "3", 4: "4"} 86 | // Values(m) ⏩ []string{"1", "4", "2", "3"} //⚠️INDETERMINATE ORDER⚠️ 87 | // 88 | // ⚠️ WARNING: The keys values be in an indeterminate order, 89 | func Values[K comparable, V any](m map[K]V) []V { 90 | r := make([]V, 0, len(m)) 91 | for _, v := range m { 92 | r = append(r, v) 93 | } 94 | return r 95 | } 96 | 97 | // Clone returns a shallow copy of map. 98 | // If the given map is nil, nil is returned. 99 | // 100 | // 🚀 EXAMPLE: 101 | // 102 | // Clone(map[int]int{1: 1, 2: 2}) ⏩ map[int]int{1: 1, 2: 2} 103 | // Clone(map[int]int{}) ⏩ map[int]int{} 104 | // Clone[int, int](nil) ⏩ nil 105 | // 106 | // 💡 HINT: Both keys and values are copied using assignment (=), so this is a shallow clone. 107 | // 💡 AKA: Copy 108 | func Clone[K comparable, V any, M ~map[K]V](m M) M { 109 | if m == nil { 110 | return nil 111 | } 112 | return cloneWithoutNilCheck(m) 113 | } 114 | 115 | func cloneWithoutNilCheck[K comparable, V any, M ~map[K]V](m M) M { 116 | r := make(M, len(m)) 117 | for k, v := range m { 118 | r[k] = v 119 | } 120 | return r 121 | } 122 | -------------------------------------------------------------------------------- /internal/gmap/gmap_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package gmap 18 | 19 | import ( 20 | "fmt" 21 | "sort" 22 | "strconv" 23 | "testing" 24 | 25 | "github.com/stretchr/testify/assert" 26 | ) 27 | 28 | func TestMerge(t *testing.T) { 29 | assert.Equal(t, map[int]int{1: 1, 2: 2, 3: 3, 4: 4}, 30 | Concat(map[int]int{1: 1, 2: 2, 3: 3, 4: 4}, nil)) 31 | assert.Equal(t, map[int]int{1: 1, 2: 2, 3: 3, 4: 4}, 32 | Concat[int, int](nil, map[int]int{1: 1, 2: 2, 3: 3, 4: 4})) 33 | assert.Equal(t, map[int]int{}, Concat[int, int](nil, nil)) 34 | assert.Equal(t, map[int]int{1: 1, 2: 2, 3: 3, 4: 4}, 35 | Concat(map[int]int{1: 1, 2: 2, 3: 3, 4: 4}, map[int]int{1: 1, 2: 2, 3: 3, 4: 4})) 36 | assert.Equal(t, map[int]int{1: 1, 2: 2, 3: 3, 4: 4}, 37 | Concat(map[int]int{1: 0, 2: 0}, map[int]int{1: 1, 2: 2, 3: 3, 4: 4})) 38 | assert.Equal(t, map[int]int{1: 1, 2: 2, 3: 3, 4: 4}, 39 | Concat(map[int]int{1: 1, 2: 1}, map[int]int{2: 2, 3: 3, 4: 4})) 40 | } 41 | 42 | func TestMap(t *testing.T) { 43 | assert.Equal(t, 44 | map[string]string{"1": "1", "2": "2"}, 45 | Map(map[int]int{1: 1, 2: 2}, func(k, v int) (string, string) { 46 | return strconv.Itoa(k), strconv.Itoa(v) 47 | })) 48 | assert.Equal(t, 49 | map[string]string{}, 50 | Map(map[int]int{}, func(k, v int) (string, string) { 51 | return strconv.Itoa(k), strconv.Itoa(v) 52 | })) 53 | } 54 | 55 | func TestValues(t *testing.T) { 56 | { 57 | keys := Values(map[int]string{1: "1", 2: "2", 3: "3", 4: "4"}) 58 | sort.Strings(keys) 59 | assert.Equal(t, []string{"1", "2", "3", "4"}, keys) 60 | } 61 | assert.Equal(t, []string{}, Values(map[int]string{})) 62 | assert.Equal(t, []string{}, Values[int, string](nil)) 63 | } 64 | 65 | func TestClone(t *testing.T) { 66 | assert.Equal(t, map[int]int{1: 1, 2: 2}, Clone(map[int]int{1: 1, 2: 2})) 67 | var nilMap map[int]int 68 | assert.Equal(t, map[int]int{}, Clone(map[int]int{})) 69 | assert.NotEqual(t, (map[int]int)(nil), Clone(map[int]int{})) 70 | assert.Equal(t, (map[int]int)(nil), Clone(nilMap)) 71 | assert.NotEqual(t, map[int]int{}, Clone(nilMap)) 72 | 73 | // Test new type. 74 | type I2I map[int]int 75 | assert.Equal(t, I2I{1: 1, 2: 2}, Clone(I2I{1: 1, 2: 2})) 76 | assert.Equal(t, "gmap.I2I", fmt.Sprintf("%T", Clone(I2I{}))) 77 | 78 | // Test shallow clone. 79 | src := map[int]*int{1: ptr(1), 2: ptr(2)} 80 | dst := Clone(src) 81 | assert.Equal(t, src, dst) 82 | assert.True(t, src[1] == dst[1]) 83 | assert.True(t, src[2] == dst[2]) 84 | } 85 | 86 | // Ptr returns a pointer to the given value. 87 | func ptr[T any](v T) *T { 88 | return &v 89 | } 90 | -------------------------------------------------------------------------------- /internal/gslice/gslice.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package gslice 18 | 19 | // ToMap collects elements of slice to map, both map keys and values are produced 20 | // by mapping function f. 21 | // 22 | // 🚀 EXAMPLE: 23 | // 24 | // type Foo struct { 25 | // ID int 26 | // Name string 27 | // } 28 | // mapper := func(f Foo) (int, string) { return f.ID, f.Name } 29 | // ToMap([]Foo{}, mapper) ⏩ map[int]string{} 30 | // s := []Foo{{1, "one"}, {2, "two"}, {3, "three"}} 31 | // ToMap(s, mapper) ⏩ map[int]string{1: "one", 2: "two", 3: "three"} 32 | func ToMap[T, V any, K comparable](s []T, f func(T) (K, V)) map[K]V { 33 | m := make(map[K]V, len(s)) 34 | for _, e := range s { 35 | k, v := f(e) 36 | m[k] = v 37 | } 38 | return m 39 | } 40 | -------------------------------------------------------------------------------- /internal/gslice/gslice_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package gslice 18 | 19 | import ( 20 | "testing" 21 | 22 | "github.com/stretchr/testify/assert" 23 | ) 24 | 25 | func TestToMap(t *testing.T) { 26 | type Foo struct { 27 | ID int 28 | Name string 29 | } 30 | mapper := func(f Foo) (int, string) { return f.ID, f.Name } 31 | assert.Equal(t, map[int]string{}, ToMap([]Foo{}, mapper)) 32 | assert.Equal(t, map[int]string{}, ToMap(nil, mapper)) 33 | assert.Equal(t, 34 | map[int]string{1: "one", 2: "two", 3: "three"}, 35 | ToMap([]Foo{{1, "one"}, {2, "two"}, {3, "three"}}, mapper)) 36 | } 37 | -------------------------------------------------------------------------------- /internal/merge.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package internal 18 | 19 | import ( 20 | "fmt" 21 | "reflect" 22 | 23 | "github.com/cloudwego/eino/internal/generic" 24 | ) 25 | 26 | var mergeFuncs = map[reflect.Type]any{} 27 | 28 | func RegisterValuesMergeFunc[T any](fn func([]T) (T, error)) { 29 | mergeFuncs[generic.TypeOf[T]()] = fn 30 | } 31 | 32 | func GetMergeFunc(typ reflect.Type) func([]any) (any, error) { 33 | if fn, ok := mergeFuncs[typ]; ok { 34 | return func(vs []any) (any, error) { 35 | rvs := reflect.MakeSlice(reflect.SliceOf(typ), 0, len(vs)) 36 | for _, v := range vs { 37 | if t := reflect.TypeOf(v); t != typ { 38 | return nil, fmt.Errorf( 39 | "(values merge) field type mismatch. expected: '%v', got: '%v'", typ, t) 40 | } 41 | rvs = reflect.Append(rvs, reflect.ValueOf(v)) 42 | } 43 | 44 | rets := reflect.ValueOf(fn).Call([]reflect.Value{rvs}) 45 | var err error 46 | if !rets[1].IsNil() { 47 | err = rets[1].Interface().(error) 48 | } 49 | return rets[0].Interface(), err 50 | } 51 | } 52 | 53 | if typ.Kind() == reflect.Map { 54 | return func(vs []any) (any, error) { 55 | return mergeMap(typ, vs) 56 | } 57 | } 58 | 59 | return nil 60 | } 61 | 62 | func mergeMap(typ reflect.Type, vs []any) (any, error) { 63 | merged := reflect.MakeMap(typ) 64 | for _, v := range vs { 65 | if t := reflect.TypeOf(v); t != typ { 66 | return nil, fmt.Errorf( 67 | "(values merge map) field type mismatch. expected: '%v', got: '%v'", typ, t) 68 | } 69 | 70 | iter := reflect.ValueOf(v).MapRange() 71 | for iter.Next() { 72 | key, val := iter.Key(), iter.Value() 73 | if merged.MapIndex(key).IsValid() { 74 | return nil, fmt.Errorf("(values merge map) duplicated key ('%v') found", key.Interface()) 75 | } 76 | merged.SetMapIndex(key, val) 77 | } 78 | } 79 | 80 | return merged.Interface(), nil 81 | } 82 | -------------------------------------------------------------------------------- /internal/mock/components/embedding/Embedding_mock.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | // Code generated by MockGen. DO NOT EDIT. 18 | // Source: interface.go 19 | // 20 | // Generated by this command: 21 | // 22 | // mockgen -destination ../../internal/mock/components/embedding/Embedding_mock.go --package embedding -source interface.go 23 | // 24 | 25 | // Package embedding is a generated GoMock package. 26 | package embedding 27 | 28 | import ( 29 | context "context" 30 | reflect "reflect" 31 | 32 | embedding "github.com/cloudwego/eino/components/embedding" 33 | gomock "go.uber.org/mock/gomock" 34 | ) 35 | 36 | // MockEmbedder is a mock of Embedder interface. 37 | type MockEmbedder struct { 38 | ctrl *gomock.Controller 39 | recorder *MockEmbedderMockRecorder 40 | } 41 | 42 | // MockEmbedderMockRecorder is the mock recorder for MockEmbedder. 43 | type MockEmbedderMockRecorder struct { 44 | mock *MockEmbedder 45 | } 46 | 47 | // NewMockEmbedder creates a new mock instance. 48 | func NewMockEmbedder(ctrl *gomock.Controller) *MockEmbedder { 49 | mock := &MockEmbedder{ctrl: ctrl} 50 | mock.recorder = &MockEmbedderMockRecorder{mock} 51 | return mock 52 | } 53 | 54 | // EXPECT returns an object that allows the caller to indicate expected use. 55 | func (m *MockEmbedder) EXPECT() *MockEmbedderMockRecorder { 56 | return m.recorder 57 | } 58 | 59 | // EmbedStrings mocks base method. 60 | func (m *MockEmbedder) EmbedStrings(ctx context.Context, texts []string, opts ...embedding.Option) ([][]float64, error) { 61 | m.ctrl.T.Helper() 62 | varargs := []any{ctx, texts} 63 | for _, a := range opts { 64 | varargs = append(varargs, a) 65 | } 66 | ret := m.ctrl.Call(m, "EmbedStrings", varargs...) 67 | ret0, _ := ret[0].([][]float64) 68 | ret1, _ := ret[1].(error) 69 | return ret0, ret1 70 | } 71 | 72 | // EmbedStrings indicates an expected call of EmbedStrings. 73 | func (mr *MockEmbedderMockRecorder) EmbedStrings(ctx, texts any, opts ...any) *gomock.Call { 74 | mr.mock.ctrl.T.Helper() 75 | varargs := append([]any{ctx, texts}, opts...) 76 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EmbedStrings", reflect.TypeOf((*MockEmbedder)(nil).EmbedStrings), varargs...) 77 | } 78 | -------------------------------------------------------------------------------- /internal/mock/components/indexer/indexer_mock.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | // Code generated by MockGen. DO NOT EDIT. 18 | // Source: interface.go 19 | // 20 | // Generated by this command: 21 | // 22 | // mockgen -destination ../../internal/mock/components/indexer/indexer_mock.go --package indexer -source interface.go 23 | // 24 | 25 | // Package indexer is a generated GoMock package. 26 | package indexer 27 | 28 | import ( 29 | context "context" 30 | reflect "reflect" 31 | 32 | indexer "github.com/cloudwego/eino/components/indexer" 33 | schema "github.com/cloudwego/eino/schema" 34 | gomock "go.uber.org/mock/gomock" 35 | ) 36 | 37 | // MockIndexer is a mock of Indexer interface. 38 | type MockIndexer struct { 39 | ctrl *gomock.Controller 40 | recorder *MockIndexerMockRecorder 41 | } 42 | 43 | // MockIndexerMockRecorder is the mock recorder for MockIndexer. 44 | type MockIndexerMockRecorder struct { 45 | mock *MockIndexer 46 | } 47 | 48 | // NewMockIndexer creates a new mock instance. 49 | func NewMockIndexer(ctrl *gomock.Controller) *MockIndexer { 50 | mock := &MockIndexer{ctrl: ctrl} 51 | mock.recorder = &MockIndexerMockRecorder{mock} 52 | return mock 53 | } 54 | 55 | // EXPECT returns an object that allows the caller to indicate expected use. 56 | func (m *MockIndexer) EXPECT() *MockIndexerMockRecorder { 57 | return m.recorder 58 | } 59 | 60 | // Store mocks base method. 61 | func (m *MockIndexer) Store(ctx context.Context, docs []*schema.Document, opts ...indexer.Option) ([]string, error) { 62 | m.ctrl.T.Helper() 63 | varargs := []any{ctx, docs} 64 | for _, a := range opts { 65 | varargs = append(varargs, a) 66 | } 67 | ret := m.ctrl.Call(m, "Store", varargs...) 68 | ret0, _ := ret[0].([]string) 69 | ret1, _ := ret[1].(error) 70 | return ret0, ret1 71 | } 72 | 73 | // Store indicates an expected call of Store. 74 | func (mr *MockIndexerMockRecorder) Store(ctx, docs any, opts ...any) *gomock.Call { 75 | mr.mock.ctrl.T.Helper() 76 | varargs := append([]any{ctx, docs}, opts...) 77 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Store", reflect.TypeOf((*MockIndexer)(nil).Store), varargs...) 78 | } 79 | -------------------------------------------------------------------------------- /internal/mock/components/retriever/retriever_mock.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | // Code generated by MockGen. DO NOT EDIT. 18 | // Source: interface.go 19 | // 20 | // Generated by this command: 21 | // 22 | // mockgen -destination ../../internal/mock/components/retriever/retriever_mock.go --package retriever -source interface.go 23 | // 24 | 25 | // Package retriever is a generated GoMock package. 26 | package retriever 27 | 28 | import ( 29 | context "context" 30 | reflect "reflect" 31 | 32 | retriever "github.com/cloudwego/eino/components/retriever" 33 | schema "github.com/cloudwego/eino/schema" 34 | gomock "go.uber.org/mock/gomock" 35 | ) 36 | 37 | // MockRetriever is a mock of Retriever interface. 38 | type MockRetriever struct { 39 | ctrl *gomock.Controller 40 | recorder *MockRetrieverMockRecorder 41 | } 42 | 43 | // MockRetrieverMockRecorder is the mock recorder for MockRetriever. 44 | type MockRetrieverMockRecorder struct { 45 | mock *MockRetriever 46 | } 47 | 48 | // NewMockRetriever creates a new mock instance. 49 | func NewMockRetriever(ctrl *gomock.Controller) *MockRetriever { 50 | mock := &MockRetriever{ctrl: ctrl} 51 | mock.recorder = &MockRetrieverMockRecorder{mock} 52 | return mock 53 | } 54 | 55 | // EXPECT returns an object that allows the caller to indicate expected use. 56 | func (m *MockRetriever) EXPECT() *MockRetrieverMockRecorder { 57 | return m.recorder 58 | } 59 | 60 | // Retrieve mocks base method. 61 | func (m *MockRetriever) Retrieve(ctx context.Context, query string, opts ...retriever.Option) ([]*schema.Document, error) { 62 | m.ctrl.T.Helper() 63 | varargs := []any{ctx, query} 64 | for _, a := range opts { 65 | varargs = append(varargs, a) 66 | } 67 | ret := m.ctrl.Call(m, "Retrieve", varargs...) 68 | ret0, _ := ret[0].([]*schema.Document) 69 | ret1, _ := ret[1].(error) 70 | return ret0, ret1 71 | } 72 | 73 | // Retrieve indicates an expected call of Retrieve. 74 | func (mr *MockRetrieverMockRecorder) Retrieve(ctx, query any, opts ...any) *gomock.Call { 75 | mr.mock.ctrl.T.Helper() 76 | varargs := append([]any{ctx, query}, opts...) 77 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Retrieve", reflect.TypeOf((*MockRetriever)(nil).Retrieve), varargs...) 78 | } 79 | -------------------------------------------------------------------------------- /internal/mock/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | // Package mock provides mock implementations for testing purposes. 18 | // 19 | // This package aims to provide mock implementations for interfaces in the components package, 20 | // making it easier to use in testing environments. It includes mock implementations for 21 | // various core components such as retrievers, tools, message handlers, and graph runners. 22 | // 23 | // Directory Structure: 24 | // - components/: Contains mock implementations for various components 25 | // - retriever/: Provides mock implementation for the Retriever interface 26 | // - retriever_mock.go: Mock implementation for document retrieval 27 | // - tool/: Mock implementations for tool-related interfaces 28 | // - message/: Mock implementations for message handling components 29 | // - graph/: Mock implementations for graph execution components 30 | // - stream/: Mock implementations for streaming components 31 | // 32 | // Usage: 33 | // These mock implementations are primarily used in unit tests and integration tests, 34 | // allowing developers to conduct tests without depending on actual external services. 35 | // Each mock component strictly follows the contract of its corresponding interface 36 | // while providing controllable behaviors and results. 37 | // 38 | // Examples: 39 | // 40 | // - Using mock retriever: 41 | // retriever := mock.NewMockRetriever() 42 | // // Configure retriever behavior 43 | // 44 | // - Using mock tool: 45 | // tool := mock.NewMockTool() 46 | // // Configure tool behavior 47 | // 48 | // - Using mock graph runner: 49 | // runner := mock.NewMockGraphRunner() 50 | // // Configure runner behavior 51 | package mock 52 | -------------------------------------------------------------------------------- /internal/safe/panic.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package safe 18 | 19 | import ( 20 | "fmt" 21 | ) 22 | 23 | type panicErr struct { 24 | info any 25 | stack []byte 26 | } 27 | 28 | func (p *panicErr) Error() string { 29 | return fmt.Sprintf("panic error: %v, \nstack: %s", p.info, string(p.stack)) 30 | } 31 | 32 | // NewPanicErr creates a new panic error. 33 | // panicErr is a wrapper of panic info and stack trace. 34 | // it implements the error interface, can print error message of info and stack trace. 35 | func NewPanicErr(info any, stack []byte) error { 36 | return &panicErr{ 37 | info: info, 38 | stack: stack, 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /internal/safe/panic_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package safe 18 | 19 | import ( 20 | "testing" 21 | 22 | "github.com/stretchr/testify/assert" 23 | ) 24 | 25 | func TestPanicErr(t *testing.T) { 26 | err := NewPanicErr("info", []byte("stack")) 27 | assert.Equal(t, "panic error: info, \nstack: stack", err.Error()) 28 | } 29 | -------------------------------------------------------------------------------- /internal/serialization/serialization_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package serialization 18 | 19 | import ( 20 | "testing" 21 | 22 | "github.com/stretchr/testify/assert" 23 | ) 24 | 25 | type myInterface interface { 26 | Method() 27 | } 28 | type myStruct struct { 29 | A string 30 | } 31 | 32 | func (m *myStruct) Method() {} 33 | 34 | type myStruct2 struct { 35 | A any 36 | B myInterface 37 | C map[string]**myStruct 38 | D map[myStruct]any 39 | E []any 40 | f string 41 | } 42 | 43 | func TestSerialization(t *testing.T) { 44 | GenericRegister[myStruct]("myStruct") 45 | GenericRegister[myStruct2]("myStruct2") 46 | GenericRegister[myInterface]("myInterface") 47 | ms := myStruct{A: "test"} 48 | pms := &ms 49 | pointerOfPointerOfMyStruct := &pms 50 | 51 | ms1 := myStruct{A: "1"} 52 | ms2 := myStruct{A: "2"} 53 | ms3 := myStruct{A: "3"} 54 | ms4 := myStruct{A: "4"} 55 | values := []any{ 56 | 10, 57 | "test", 58 | ms, 59 | pms, 60 | pointerOfPointerOfMyStruct, 61 | myInterface(pms), 62 | []int{1, 2, 3}, 63 | []any{1, "test"}, 64 | []myInterface{nil, &myStruct{A: "1"}, &myStruct{A: "2"}}, 65 | map[string]string{"123": "123", "abc": "abc"}, 66 | map[string]myInterface{"1": nil, "2": pms}, 67 | map[string]any{"123": 1, "abc": &myStruct{A: "1"}, "bcd": nil}, 68 | map[myStruct]any{ 69 | ms1: 1, 70 | ms2: &myStruct{ 71 | A: "2", 72 | }, 73 | ms3: nil, 74 | ms4: []any{ 75 | 1, 76 | pointerOfPointerOfMyStruct, 77 | "123", &myStruct{ 78 | A: "1", 79 | }, 80 | nil, 81 | map[myStruct]any{ 82 | ms1: 1, 83 | ms2: nil, 84 | }, 85 | }, 86 | }, 87 | myStruct2{ 88 | A: "123", 89 | B: &myStruct{ 90 | A: "test", 91 | }, 92 | C: map[string]**myStruct{ 93 | "a": pointerOfPointerOfMyStruct, 94 | }, 95 | D: map[myStruct]any{{"a"}: 1}, 96 | E: []any{1, "2", 3}, 97 | f: "", 98 | }, 99 | map[string]map[string][]map[string][][]string{ 100 | "1": { 101 | "a": []map[string][][]string{ 102 | {"b": { 103 | {"c"}, 104 | {"d"}, 105 | }}, 106 | }, 107 | }, 108 | }, 109 | } 110 | 111 | for _, value := range values { 112 | data, err := Marshal(value) 113 | assert.NoError(t, err) 114 | nValue, err := Unmarshal(data) 115 | assert.NoError(t, err) 116 | assert.Equal(t, value, nValue) 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /schema/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package schema 18 | -------------------------------------------------------------------------------- /schema/document_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package schema 18 | 19 | import ( 20 | "testing" 21 | 22 | "github.com/smartystreets/goconvey/convey" 23 | ) 24 | 25 | func TestDocument(t *testing.T) { 26 | convey.Convey("test document", t, func() { 27 | var ( 28 | subIndexes = []string{"hello", "bye"} 29 | score = 1.1 30 | extraInfo = "asd" 31 | dslInfo = map[string]any{"hello": true} 32 | vector = []float64{1.1, 2.2} 33 | ) 34 | 35 | d := &Document{ 36 | ID: "asd", 37 | Content: "qwe", 38 | MetaData: nil, 39 | } 40 | 41 | d.WithSubIndexes(subIndexes). 42 | WithDenseVector(vector). 43 | WithScore(score). 44 | WithExtraInfo(extraInfo). 45 | WithDSLInfo(dslInfo) 46 | 47 | convey.So(d.SubIndexes(), convey.ShouldEqual, subIndexes) 48 | convey.So(d.Score(), convey.ShouldEqual, score) 49 | convey.So(d.ExtraInfo(), convey.ShouldEqual, extraInfo) 50 | convey.So(d.DSLInfo(), convey.ShouldEqual, dslInfo) 51 | convey.So(d.DenseVector(), convey.ShouldEqual, vector) 52 | }) 53 | } 54 | -------------------------------------------------------------------------------- /schema/select.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package schema 18 | 19 | const maxSelectNum = 5 20 | 21 | func receiveN[T any](chosenList []int, ss []*stream[T]) (int, *streamItem[T], bool) { 22 | return []func(chosenList []int, ss []*stream[T]) (index int, item *streamItem[T], ok bool){ 23 | nil, 24 | func(chosenList []int, ss []*stream[T]) (int, *streamItem[T], bool) { 25 | select { 26 | case item, ok := <-ss[chosenList[0]].items: 27 | return chosenList[0], &item, ok 28 | } 29 | }, 30 | func(chosenList []int, ss []*stream[T]) (int, *streamItem[T], bool) { 31 | select { 32 | case item, ok := <-ss[chosenList[0]].items: 33 | return chosenList[0], &item, ok 34 | case item, ok := <-ss[chosenList[1]].items: 35 | return chosenList[1], &item, ok 36 | } 37 | }, 38 | func(chosenList []int, ss []*stream[T]) (int, *streamItem[T], bool) { 39 | select { 40 | case item, ok := <-ss[chosenList[0]].items: 41 | return chosenList[0], &item, ok 42 | case item, ok := <-ss[chosenList[1]].items: 43 | return chosenList[1], &item, ok 44 | case item, ok := <-ss[chosenList[2]].items: 45 | return chosenList[2], &item, ok 46 | } 47 | }, 48 | func(chosenList []int, ss []*stream[T]) (int, *streamItem[T], bool) { 49 | select { 50 | case item, ok := <-ss[chosenList[0]].items: 51 | return chosenList[0], &item, ok 52 | case item, ok := <-ss[chosenList[1]].items: 53 | return chosenList[1], &item, ok 54 | case item, ok := <-ss[chosenList[2]].items: 55 | return chosenList[2], &item, ok 56 | case item, ok := <-ss[chosenList[3]].items: 57 | return chosenList[3], &item, ok 58 | } 59 | }, 60 | func(chosenList []int, ss []*stream[T]) (int, *streamItem[T], bool) { 61 | select { 62 | case item, ok := <-ss[chosenList[0]].items: 63 | return chosenList[0], &item, ok 64 | case item, ok := <-ss[chosenList[1]].items: 65 | return chosenList[1], &item, ok 66 | case item, ok := <-ss[chosenList[2]].items: 67 | return chosenList[2], &item, ok 68 | case item, ok := <-ss[chosenList[3]].items: 69 | return chosenList[3], &item, ok 70 | case item, ok := <-ss[chosenList[4]].items: 71 | return chosenList[4], &item, ok 72 | } 73 | }, 74 | }[len(chosenList)](chosenList, ss) 75 | } 76 | -------------------------------------------------------------------------------- /schema/stream_copy_external_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package schema 18 | 19 | import ( 20 | "fmt" 21 | "io" 22 | "runtime" 23 | "sort" 24 | "sync" 25 | "sync/atomic" 26 | "testing" 27 | "time" 28 | ) 29 | 30 | func TestStream1(t *testing.T) { 31 | runtime.GOMAXPROCS(1) 32 | 33 | sr, sw := Pipe[int](0) 34 | go func() { 35 | for i := 0; i < 100; i++ { 36 | sw.Send(i, nil) 37 | time.Sleep(3 * time.Millisecond) 38 | } 39 | sw.Close() 40 | }() 41 | copied := sr.Copy(2) 42 | var ( 43 | now = time.Now().UnixMilli() 44 | ts = []int64{now, now} 45 | tsOld = []int64{now, now} 46 | ) 47 | var count int32 48 | wg := sync.WaitGroup{} 49 | wg.Add(2) 50 | go func() { 51 | s := copied[0] 52 | for { 53 | n, e := s.Recv() 54 | if e != nil { 55 | if e == io.EOF { 56 | break 57 | } 58 | } 59 | tsOld[0] = ts[0] 60 | ts[0] = time.Now().UnixMilli() 61 | interval := ts[0] - tsOld[0] 62 | if interval >= 6 { 63 | atomic.AddInt32(&count, 1) 64 | } 65 | t.Logf("reader= 0, index= %d, interval= %v", n, interval) 66 | } 67 | wg.Done() 68 | }() 69 | go func() { 70 | s := copied[1] 71 | for { 72 | n, e := s.Recv() 73 | if e != nil { 74 | if e == io.EOF { 75 | break 76 | } 77 | } 78 | tsOld[1] = ts[1] 79 | ts[1] = time.Now().UnixMilli() 80 | interval := ts[1] - tsOld[1] 81 | if interval >= 6 { 82 | atomic.AddInt32(&count, 1) 83 | } 84 | t.Logf("reader= 1, index= %d, interval= %v", n, interval) 85 | } 86 | wg.Done() 87 | }() 88 | wg.Wait() 89 | t.Logf("count= %d", count) 90 | } 91 | 92 | type info struct { 93 | idx int 94 | ts int64 95 | after int64 96 | content string 97 | } 98 | 99 | func TestCopyDelay(t *testing.T) { 100 | runtime.GOMAXPROCS(10) 101 | n := 3 102 | //m := 100 103 | s := newStream[string](0) 104 | scp := s.asReader().Copy(n) 105 | go func() { 106 | s.send("1", nil) 107 | s.send("2", nil) 108 | time.Sleep(time.Second) 109 | s.send("3", nil) 110 | s.closeSend() 111 | }() 112 | wg := sync.WaitGroup{} 113 | wg.Add(n) 114 | infoList := make([][]info, n) 115 | for i := 0; i < n; i++ { 116 | j := i 117 | go func() { 118 | defer func() { 119 | scp[j].Close() 120 | wg.Done() 121 | }() 122 | for { 123 | lastTime := time.Now() 124 | str, err := scp[j].Recv() 125 | if err == io.EOF { 126 | break 127 | } 128 | now := time.Now() 129 | infoList[j] = append(infoList[j], info{ 130 | idx: j, 131 | ts: now.UnixMicro(), 132 | after: now.Sub(lastTime).Milliseconds(), 133 | content: str, 134 | }) 135 | } 136 | }() 137 | } 138 | wg.Wait() 139 | infos := make([]info, 0) 140 | for _, infoL := range infoList { 141 | for _, info := range infoL { 142 | infos = append(infos, info) 143 | } 144 | } 145 | sort.Slice(infos, func(i, j int) bool { 146 | return infos[i].ts < infos[j].ts 147 | }) 148 | for _, info := range infos { 149 | fmt.Printf("child[%d] ts[%d] after[%5dms] content[%s]\n", info.idx, info.ts, info.after, info.content) 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /schema/tool_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package schema 18 | 19 | import ( 20 | "testing" 21 | 22 | "github.com/getkin/kin-openapi/openapi3" 23 | "github.com/smartystreets/goconvey/convey" 24 | ) 25 | 26 | func TestParamsOneOfToJSONSchema(t *testing.T) { 27 | convey.Convey("TestParamsOneOfToJSONSchema", t, func() { 28 | var ( 29 | oneOf ParamsOneOf 30 | converted any 31 | err error 32 | ) 33 | 34 | convey.Convey("user provides openAPIV3.0 json schema directly, use what the user provides", func() { 35 | oneOf.openAPIV3 = &openapi3.Schema{ 36 | Type: openapi3.TypeString, 37 | Description: "this is the only argument", 38 | } 39 | converted, err = oneOf.ToOpenAPIV3() 40 | convey.So(err, convey.ShouldBeNil) 41 | convey.So(converted, convey.ShouldResemble, oneOf.openAPIV3) 42 | }) 43 | 44 | convey.Convey("user provides map[string]ParameterInfo, converts to json schema", func() { 45 | oneOf.params = map[string]*ParameterInfo{ 46 | "arg1": { 47 | Type: String, 48 | Desc: "this is the first argument", 49 | Required: true, 50 | Enum: []string{"1", "2"}, 51 | }, 52 | "arg2": { 53 | Type: Object, 54 | Desc: "this is the second argument", 55 | SubParams: map[string]*ParameterInfo{ 56 | "sub_arg1": { 57 | Type: String, 58 | Desc: "this is the sub argument", 59 | Required: true, 60 | Enum: []string{"1", "2"}, 61 | }, 62 | "sub_arg2": { 63 | Type: String, 64 | Desc: "this is the sub argument 2", 65 | }, 66 | }, 67 | Required: true, 68 | }, 69 | "arg3": { 70 | Type: Array, 71 | Desc: "this is the third argument", 72 | ElemInfo: &ParameterInfo{ 73 | Type: String, 74 | Desc: "this is the element of the third argument", 75 | Required: true, 76 | Enum: []string{"1", "2"}, 77 | }, 78 | Required: true, 79 | }, 80 | } 81 | converted, err = oneOf.ToOpenAPIV3() 82 | convey.So(err, convey.ShouldBeNil) 83 | }) 84 | }) 85 | } 86 | --------------------------------------------------------------------------------